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 v1.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 030/** 031 * Utility class for handling and validating version information of various artifacts. 032 * 033 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc. 034 * to alert users about version discrepancies between dependent and dependee artifacts. 035 * </p> 036 * 037 * @since 1.5.25 038 */ 039public class VersionUtil { 040 041 /** 042 * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc. 043 * 044 * <p>The aClass parameter is assumed to be part of the artifact. 045 * </p> 046 * 047 * <p>The method first attempts to get the version from the module information. If the module version 048 * is not available, it falls back to retrieving the implementation version from the package. 049 * </p> 050 * 051 * @param aClass the class from which to retrieve the version information 052 * @return the version of the artifact where aClass is found, or null if the version cannot be determined 053 */ 054 static public String getVersionOfArtifact(Class<?> aClass) { 055 String moduleVersion = getVersionOfClassByModule(aClass); 056 if (moduleVersion != null) 057 return moduleVersion; 058 059 Package pkg = aClass.getPackage(); 060 if (pkg == null) { 061 return null; 062 } 063 return pkg.getImplementationVersion(); 064 } 065 066 static public String nonNull(String input) { 067 if (input == null) { 068 return NA; 069 } else { 070 return input; 071 } 072 } 073 074 /** 075 * Retrieves the version of an artifact from the artifact's module metadata. 076 * 077 * <p>If the module or its descriptor does not provide a version, the method returns null. 078 * </p> 079 * 080 * @param aClass a class from which to retrieve the version information 081 * @return the version of class' module as a string, or null if the version cannot be determined 082 */ 083 static private String getVersionOfClassByModule(Class<?> aClass) { 084 Module module = aClass.getModule(); 085 if (module == null) 086 return null; 087 088 ModuleDescriptor md = module.getDescriptor(); 089 if (md == null) 090 return null; 091 Optional<String> opt = md.rawVersion(); 092 return opt.orElse(null); 093 } 094 095 /** 096 * Retrieves the version of a module using a properties file associated with the module. 097 * 098 * <p>Unfortunately, this code cannot be called by other modules. It needs to be duplicated.</p> 099 * 100 * <p>The method looks for a properties file with a name derived from the <code>moduleName</code> parameter, 101 * in the same location, e.g. package, as the <code>aClass</code> parameter. It attempts to load the properties file 102 * and fetch the version information using a specific key. 103 * </p> 104 * 105 * <p>The properties file is expected to be in the same package as the class provided, and named 106 * <code>moduleName-version.properties</code>. The properties file should contain a single key-value pair, 107 * where the key is <code>moduleName-version</code>, and the value is the module version. 108 * 109 * @param aClass the class used to locate the resource file, the properties file is expected to be in the same package 110 * @param moduleName the name of the module, which is used to construct the properties file name and the key 111 * @return the version of the module as a string, or null if the version cannot be determined 112 * @since 1.5.26 113 */ 114 static public String getArtifactVersionBySelfDeclaredProperties(Class<?> aClass, String moduleName) { 115 Properties props = new Properties(); 116 // example propertiesFileName: logback-core-version.properties 117 // 118 String propertiesFileName = moduleName + "-version.properties"; 119 String propertyKey = moduleName + "-version"; 120 try (InputStream is = aClass.getResourceAsStream(propertiesFileName)) { 121 if (is != null) { 122 props.load(is); 123 return props.getProperty(propertyKey); 124 } else { 125 return null; 126 } 127 } catch (IOException e) { 128 return null; 129 } 130 } 131 132 133 // dependency synonym dependee 134 // depender synonym dependent 135 136 static String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) { 137 Properties props = new Properties(); 138 // propertiesFileName : logback-access-common-dependees.properties 139 try (InputStream is = dependerClass.getClassLoader() 140 .getResourceAsStream(propertiesFileName)) { 141 if (is != null) { 142 props.load(is); 143 return props.getProperty(dependencyNameAsKey); 144 } else { 145 return null; 146 } 147 } catch (IOException e) { 148 return null; 149 } 150 } 151 152 153 static public void checkForVersionEquality(Context context, Class<?> dependerClass, Class<?> dependencyClass, String dependentName, String dependencyName) { 154 // the depender depends on the dependency 155 String dependerVersion = nonNull(getVersionOfArtifact(dependerClass)); 156 String dependencyVersion = nonNull(getVersionOfArtifact(dependencyClass)); 157 158 checkForVersionEquality(context, dependerVersion, dependencyVersion, dependentName, dependencyName); 159 } 160 161 // depender depends on dependency 162 // dependency synonym dependee 163 // depender synonym dependent 164 static public void checkForVersionEquality(Context context, Class<?> dependerClass, String dependencyVersion, String dependentName, String dependencyName) { 165 String dependerVersion = nonNull(getVersionOfArtifact(dependerClass)); 166 checkForVersionEquality(context, dependerVersion, dependencyVersion, dependentName, dependencyName); 167 } 168 169 170 /** 171 * Compares the versions of a dependent and a dependency to determine if they are equal. 172 * Updates the context's status manager with version information and logs a warning if the versions differ. 173 * 174 * @param context the logging context to which status messages are added 175 * @param dependentVersion the version string of the dependent component 176 * @param dependencyVersion the version string of the dependency component 177 * @param dependentName the name of the dependent component 178 * @param dependencyName the name of the dependency component 179 * @since 1.5.26 180 */ 181 static public void checkForVersionEquality(Context context, String dependentVersion, String dependencyVersion, String dependentName, String dependencyName) { 182 // the dependent depends on the dependency 183 addFoundVersionStatus(context, dependentName, dependentVersion); 184 185 if (dependentVersion.equals(NA) || !dependentVersion.equals(dependencyVersion)) { 186 addFoundVersionStatus(context, dependencyName, dependencyVersion); 187 String discrepancyMsg = String.format("Versions of %s and %s are different!", dependencyName, dependentName); 188 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 189 } 190 } 191 192 193 194 195 196 private static void addFoundVersionStatus(Context context, String name, String version) { 197 String foundDependent = String.format("Found %s version %s", name, version); 198 context.getStatusManager().add(new InfoStatus(foundDependent, context)); 199 } 200 201 202 private static String nameToFilename(String name) { 203 return name + "-dependencies.properties"; 204 } 205 206// // dependency synonym dependee 207// // depender synonym dependent 208// static public void compareExpectedAndFoundVersion(Context context, Class<?> dependerClass, Class<?> dependencyClass, 209// String dependerName, String dependencyName) { 210// String actualDependencyVersion = nonNull(getVersionOfArtifact(dependencyClass)); 211// String dependerVersion = nonNull(getVersionOfArtifact(dependerClass)); 212// 213// compareExpectedAndFoundVersion(context, actualDependencyVersion, dependerClass, dependerVersion, dependerName, dependencyName); 214// } 215 216 // dependency synonym dependee 217 // depender synonym dependent 218 static public void compareExpectedAndFoundVersion(Context context, String actualDependencyVersion, Class<?>dependerClass, String dependerVersion, 219 String dependerName, String dependencyName) { 220 221 String expectedDependencyVersion = nonNull(getExpectedVersionOfDependencyByProperties(dependerClass, nameToFilename(dependerName), dependencyName)); 222 223 addFoundVersionStatus(context, dependencyName, actualDependencyVersion); 224 addFoundVersionStatus(context, dependerName, dependerVersion); 225 226 if (!expectedDependencyVersion.equals(actualDependencyVersion)) { 227 String discrepancyMsg = String.format("Expected version of %s is %s but found %s", dependencyName, expectedDependencyVersion, actualDependencyVersion); 228 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 229 } 230 } 231}