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/** 032 * Utility class for handling and validating version information of various artifacts. 033 * 034 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc. 035 * to alert users about version discrepancies between dependent and dependee artifacts. 036 * </p> 037 * 038 * @since 1.5.25 039 */ 040public class VersionUtil { 041 042 /** 043 * Retrieves the version of an artifact, such as logback-core.jar, logback-access-common.jar etc. 044 * 045 * <p>The aClass parameter is assumed to be part of the artifact. 046 * </p> 047 * 048 * <p>The method first attempts to get the version from the module information. If the module version 049 * is not available, it falls back to retrieving the implementation version from the package. 050 * </p> 051 * 052 * @param aClass the class from which to retrieve the version information 053 * @return the version of the artifact where aClass is found, or null if the version cannot be determined 054 * @since 2.0.9 055 */ 056 static public String getVersionOfArtifact(Class<?> aClass) { 057 String moduleVersion = getVersionOfClassByModule(aClass); 058 if (moduleVersion != null) 059 return moduleVersion; 060 061 Package pkg = aClass.getPackage(); 062 if (pkg == null) { 063 return null; 064 } 065 return pkg.getImplementationVersion(); 066 } 067 068 static public String nonNull(String input) { 069 if (input == null) { 070 return NA; 071 } else { 072 return input; 073 } 074 } 075 076 /** 077 * Retrieves the version of an artifact from the artifact's module metadata. 078 * 079 * <p>If the module or its descriptor does not provide a version, the method returns null. 080 * </p> 081 * 082 * @param aClass a class from which to retrieve the version information 083 * @return the version of class' module as a string, or null if the version cannot be determined 084 * @since 2.0.9 085 */ 086 static private String getVersionOfClassByModule(Class<?> aClass) { 087 Module module = aClass.getModule(); 088 if (module == null) 089 return null; 090 091 ModuleDescriptor md = module.getDescriptor(); 092 if (md == null) 093 return null; 094 Optional<String> opt = md.rawVersion(); 095 return opt.orElse(null); 096 } 097 098 static String getExpectedVersionOfDependeeByProperties(Class<?> dependentClass, String propertiesFileName, String dependeeNameAsKey) { 099 Properties props = new Properties(); 100 // propertiesFileName : logback-access-common-dependees.properties 101 try (InputStream is = dependentClass.getClassLoader() 102 .getResourceAsStream(propertiesFileName)) { 103 if (is != null) { 104 props.load(is); 105 return props.getProperty(dependeeNameAsKey); 106 } else { 107 return null; 108 } 109 } catch (IOException e) { 110 return null; 111 } 112 } 113 114 static public void checkForVersionEquality(Context context, Class<?> dependentClass, Class<?> dependeeClass, String dependentName, String dependeeName) { 115 // the dependent depends on the dependee 116 String dependentVersion = nonNull(getVersionOfArtifact(dependentClass)); 117 String dependeeVersion = nonNull(getVersionOfArtifact(dependeeClass)); 118 119 addFoundVersionStatus(context, dependentName, dependentVersion); 120 121 if (dependentVersion.equals(NA) || !dependentVersion.equals(dependeeVersion)) { 122 addFoundVersionStatus(context, dependeeName, dependeeVersion); 123 String discrepancyMsg = String.format("Versions of %s and %s are different!", dependeeName, dependentName); 124 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 125 } 126 } 127 128 private static void addFoundVersionStatus(Context context, String name, String version) { 129 String foundDependent = String.format("Found %s version %s", name, version); 130 context.getStatusManager().add(new InfoStatus(foundDependent, context)); 131 } 132 133 134 135 private static String nameToFilename(String name) { 136 return name+"-dependees.properties"; 137 } 138 139 static public void compareExpectedAndFoundVersion(Context context, Class<?> dependentClass, Class<?> dependeeClass, 140 String dependentName, String dependeeName) { 141 142 String expectedDependeeVersion = nonNull(getExpectedVersionOfDependeeByProperties(dependentClass, nameToFilename(dependentName), dependeeName)); 143 String actualDependeeVersion = nonNull(getVersionOfArtifact(dependeeClass)); 144 String dependentVersion = nonNull(getVersionOfArtifact(dependentClass)); 145 146 addFoundVersionStatus(context, dependeeName, actualDependeeVersion); 147 addFoundVersionStatus(context, dependentName, dependentVersion); 148 149 if (!expectedDependeeVersion.equals(actualDependeeVersion)) { 150 String discrepancyMsg = String.format("Expected version of %s is %s but found %s", dependeeName, expectedDependeeVersion, actualDependeeVersion); 151 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 152 } 153 154 } 155}