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.lang.module.ModuleDescriptor; 022import java.util.Optional; 023 024import static ch.qos.logback.core.CoreConstants.CODES_URL; 025import static ch.qos.logback.core.CoreConstants.NA; 026 027// depender depends on dependency 028 029// dependency synonym dependee (only use dependency) 030// depender synonym dependent (only use depender) 031 032/** 033 * Utility class for handling and validating version information of various artifacts. 034 * 035 * <p>It is used by logback-classic, logback-access-common, logback-access-jetty11, logback-access-tomcat, etc. 036 * to alert users about version discrepancies between depender and dependency artifacts. 037 * </p> 038 * 039 * @since 1.5.25 040 */ 041public class VersionUtil { 042 043 static final String DEPENDER_S_WAS_EXPECTING_PATTERN = "Depender [%s] was expecting version %s for dependency [%s] but found version %s"; 044 /** 045 * @since 1.5.30 046 */ 047 Context context; 048 049 /** 050 * Instance methods allow for polymorphism, static methods do not. 051 * 052 * 053 * @param context 054 * @since 1.5.30 055 */ 056 protected VersionUtil(Context context) { 057 this.context = context; 058 } 059 060 static public String nonNull(String input) { 061 if (input == null) { 062 return NA; 063 } else { 064 return input; 065 } 066 } 067 068 /** 069 * Retrieves the version of an artifact from the artifact's module metadata. 070 * 071 * <p>If the module or its descriptor does not provide a version, the method returns null. 072 * </p> 073 * 074 * @param aClass a class from which to retrieve the version information 075 * @return the version of class' module as a string, or null if the version cannot be determined 076 */ 077 static private String getVersionOfClassByModule(Class<?> aClass) { 078 Module module = aClass.getModule(); 079 if (module == null) 080 return null; 081 082 ModuleDescriptor md = module.getDescriptor(); 083 if (md == null) 084 return null; 085 Optional<String> opt = md.rawVersion(); 086 return opt.orElse(null); 087 } 088 089 protected String getExpectedVersionOfDependencyByProperties(Class<?> dependerClass, String propertiesFileName, String dependencyNameAsKey) { 090 // derived classes should override 091 return null; 092 } 093 094 095 /** 096 * Compares the versions of a depender and a dependency to determine if they are equal. 097 * Updates the context's status manager with version information and logs a warning 098 * if the versions differ. 099 * 100 * @since 1.5.26 101 */ 102 static public void checkForVersionEquality(Context context, String dependerVersion, String dependencyVersion, String dependerName, String dependencyName) { 103 // the depender depends on the dependency 104 addFoundVersionStatus(context, dependerName, dependerVersion); 105 106 dependerVersion = nonNull(dependerVersion); 107 108 if (dependerVersion.equals(NA) || !dependerVersion.equals(dependencyVersion)) { 109 addFoundVersionStatus(context, dependencyName, dependencyVersion); 110 String discrepancyMsg = String.format("Versions of %s and %s are different or unknown.", dependencyName, dependerVersion); 111 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 112 } 113 } 114 115 116 protected static void addFoundVersionStatus(Context context, String name, String version) { 117 String foundDependent = String.format("Found %s version %s", name, nonNull(version)); 118 context.getStatusManager().add(new InfoStatus(foundDependent, context)); 119 } 120 121 protected static String nameToPropertiesFilename(String name) { 122 return name + "-dependencies.properties"; 123 } 124 125 /** 126 * Compares the expected version of a dependency with the actual version found and updates the status context. 127 * If the versions do not match, a warning is added to the context's status manager. 128 * 129 * <p>Note: This method is used be logback-access-jetty11/12 and logback-access-tomcat.</p> 130 * 131 */ 132 public void compareExpectedAndFoundVersion(String actualDependencyVersion, Class<?> dependerClass, String dependerVersion, 133 String dependerName, String dependencyName) { 134 135 String propertiesFileName = nameToPropertiesFilename(dependerName); 136 137 String expectedDependencyVersion = this.getExpectedVersionOfDependencyByProperties(dependerClass, propertiesFileName, dependencyName); 138 String safeExpectedDependencyVersion = nonNull(expectedDependencyVersion); 139 140 addFoundVersionStatus(context, dependencyName, actualDependencyVersion); 141 addFoundVersionStatus(context, dependerName, dependerVersion); 142 143 if (!safeExpectedDependencyVersion.equals(actualDependencyVersion)) { 144 String discrepancyMsg = String.format(DEPENDER_S_WAS_EXPECTING_PATTERN, dependerName, 145 safeExpectedDependencyVersion, dependencyName, actualDependencyVersion); 146 context.getStatusManager().add(new WarnStatus(discrepancyMsg, context)); 147 148 String seeMsg = "See also "+CODES_URL+"#versionMismatch"; 149 context.getStatusManager().add(new WarnStatus(seeMsg, context)); 150 151 } 152 } 153}