1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package org.apache.hc.core5.util; 29 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Properties; 36 37 /** 38 * Provides access to version information for HTTP components. 39 * Static methods are used to extract version information from property 40 * files that are automatically packaged with HTTP component release JARs. 41 * <p> 42 * All available version information is provided in strings, where 43 * the string format is informal and subject to change without notice. 44 * Version information is provided for debugging output and interpretation 45 * by humans, not for automated processing in applications. 46 * </p> 47 * 48 * @since 4.0 49 */ 50 public class VersionInfo { 51 52 /** A string constant for unavailable information. */ 53 public final static String UNAVAILABLE = "UNAVAILABLE"; 54 55 /** The filename of the version information files. */ 56 public final static String VERSION_PROPERTY_FILE = "version.properties"; 57 58 // the property names 59 public final static String PROPERTY_MODULE = "info.module"; 60 public final static String PROPERTY_RELEASE = "info.release"; 61 /** 62 * @deprecated This will be removed in 6.0. 63 */ 64 @Deprecated 65 public final static String PROPERTY_TIMESTAMP = "info.timestamp"; 66 67 68 /** The package that contains the version information. */ 69 private final String infoPackage; 70 71 /** The module from the version info. */ 72 private final String infoModule; 73 74 /** The release from the version info. */ 75 private final String infoRelease; 76 77 /** The timestamp from the version info. */ 78 private final String infoTimestamp; 79 80 /** The classloader from which the version info was obtained. */ 81 private final String infoClassloader; 82 83 /** 84 * An empty immutable {@code VersionInfo} array. 85 */ 86 private static final VersionInfonfo">VersionInfo[] EMPTY_VERSION_INFO_ARRAY = new VersionInfo[0]; 87 88 89 /** 90 * Instantiates version information. 91 * 92 * @param pckg the package 93 * @param module the module, or {@code null} 94 * @param release the release, or {@code null} 95 * @param time the build time, or {@code null} 96 * @param clsldr the class loader, or {@code null} 97 */ 98 protected VersionInfo(final String pckg, final String module, 99 final String release, final String time, final String clsldr) { 100 Args.notNull(pckg, "Package identifier"); 101 infoPackage = pckg; 102 infoModule = (module != null) ? module : UNAVAILABLE; 103 infoRelease = (release != null) ? release : UNAVAILABLE; 104 infoTimestamp = (time != null) ? time : UNAVAILABLE; 105 infoClassloader = (clsldr != null) ? clsldr : UNAVAILABLE; 106 } 107 108 109 /** 110 * Obtains the package name. 111 * The package name identifies the module or informal unit. 112 * 113 * @return the package name, never {@code null} 114 */ 115 public final String getPackage() { 116 return infoPackage; 117 } 118 119 /** 120 * Obtains the name of the versioned module or informal unit. 121 * This data is read from the version information for the package. 122 * 123 * @return the module name, never {@code null} 124 */ 125 public final String getModule() { 126 return infoModule; 127 } 128 129 /** 130 * Obtains the release of the versioned module or informal unit. 131 * This data is read from the version information for the package. 132 * 133 * @return the release version, never {@code null} 134 */ 135 public final String getRelease() { 136 return infoRelease; 137 } 138 139 /** 140 * Obtains the timestamp of the versioned module or informal unit. 141 * This data is read from the version information for the package. 142 * 143 * @return the timestamp, never {@code null} 144 * @deprecated This will be removed in 6.0. 145 */ 146 @Deprecated 147 public final String getTimestamp() { 148 return infoTimestamp; 149 } 150 151 /** 152 * Obtains the classloader used to read the version information. 153 * This is just the {@code toString} output of the classloader, 154 * since the version information should not keep a reference to 155 * the classloader itself. That could prevent garbage collection. 156 * 157 * @return the classloader description, never {@code null} 158 */ 159 public final String getClassloader() { 160 return infoClassloader; 161 } 162 163 164 /** 165 * Provides the version information in human-readable format. 166 * 167 * @return a string holding this version information 168 */ 169 @Override 170 public String toString() { 171 final StringBuilder sb = new StringBuilder 172 (20 + infoPackage.length() + infoModule.length() + 173 infoRelease.length() + infoTimestamp.length() + 174 infoClassloader.length()); 175 176 sb.append("VersionInfo(") 177 .append(infoPackage).append(':').append(infoModule); 178 179 // If version info is missing, a single "UNAVAILABLE" for the module 180 // is sufficient. Everything else just clutters the output. 181 if (!UNAVAILABLE.equals(infoRelease)) { 182 sb.append(':').append(infoRelease); 183 } 184 185 sb.append(')'); 186 187 if (!UNAVAILABLE.equals(infoClassloader)) { 188 sb.append('@').append(infoClassloader); 189 } 190 191 return sb.toString(); 192 } 193 194 195 /** 196 * Loads version information for a list of packages. 197 * 198 * @param pckgs the packages for which to load version info 199 * @param clsldr the classloader to load from, or 200 * {@code null} for the thread context classloader 201 * 202 * @return the version information for all packages found, 203 * never {@code null} 204 */ 205 public static VersionInfo[] loadVersionInfo(final String[] pckgs, 206 final ClassLoader clsldr) { 207 Args.notNull(pckgs, "Package identifier array"); 208 final List<VersionInfo> vil = new ArrayList<>(pckgs.length); 209 for (final String pckg : pckgs) { 210 final VersionInfo vi = loadVersionInfo(pckg, clsldr); 211 if (vi != null) { 212 vil.add(vi); 213 } 214 } 215 216 return vil.toArray(EMPTY_VERSION_INFO_ARRAY); 217 } 218 219 220 /** 221 * Loads version information for a package. 222 * 223 * @param pckg the package for which to load version information, 224 * for example "org.apache.http". 225 * The package name should NOT end with a dot. 226 * @param clsldr the classloader to load from, or 227 * {@code null} for the thread context classloader 228 * 229 * @return the version information for the argument package, or 230 * {@code null} if not available 231 */ 232 public static VersionInfo loadVersionInfo(final String pckg, final ClassLoader clsldr) { 233 Args.notNull(pckg, "Package identifier"); 234 final ClassLoader cl = clsldr != null ? clsldr : Thread.currentThread().getContextClassLoader(); 235 236 Properties vip = null; // version info properties, if available 237 try { 238 // org.apache.http becomes 239 // org/apache/http/version.properties 240 try (final InputStream is = cl.getResourceAsStream(pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE)) { 241 if (is != null) { 242 final Properties props = new Properties(); 243 props.load(is); 244 vip = props; 245 } 246 } 247 } catch (final IOException ex) { 248 // shamelessly munch this exception 249 } 250 251 VersionInfo result = null; 252 if (vip != null) { 253 result = fromMap(pckg, vip, cl); 254 } 255 256 return result; 257 } 258 259 /** 260 * Instantiates version information from properties. 261 * 262 * @param pckg the package for the version information 263 * @param info the map from string keys to string values, 264 * for example {@link java.util.Properties} 265 * @param clsldr the classloader, or {@code null} 266 * 267 * @return the version information 268 */ 269 protected static VersionInfo fromMap(final String pckg, final Map<?, ?> info, 270 final ClassLoader clsldr) { 271 Args.notNull(pckg, "Package identifier"); 272 String module = null; 273 String release = null; 274 275 if (info != null) { 276 module = (String) info.get(PROPERTY_MODULE); 277 if ((module != null) && (module.length() < 1)) { 278 module = null; 279 } 280 281 release = (String) info.get(PROPERTY_RELEASE); 282 if ((release != null) && ((release.length() < 1) || 283 (release.equals("${project.version}")))) { 284 release = null; 285 } 286 } // if info 287 288 String clsldrstr = null; 289 if (clsldr != null) { 290 clsldrstr = clsldr.toString(); 291 } 292 293 return new VersionInfo(pckg, module, release, null, clsldrstr); 294 } 295 296 /** 297 * Gets software information as {@code "<name>/<release> (Java/<java.version>)"}. If release is 298 * {@link #UNAVAILABLE}, it will be omitted. 299 * <p> 300 * For example: 301 * <pre>"Apache-HttpClient/4.3 (Java/1.6.0_35)"</pre> 302 * 303 * @param name the component name, like "Apache-HttpClient". 304 * @param pkg 305 * the package for which to load version information, for example "org.apache.http". The package name 306 * should NOT end with a dot. 307 * @param cls 308 * the class' class loader to load from, or {@code null} for the thread context class loader 309 * @since 4.3 310 */ 311 public static String getSoftwareInfo(final String name, final String pkg, final Class<?> cls) { 312 // determine the release version from packaged version info 313 final VersionInfo vi = VersionInfo.loadVersionInfo(pkg, cls.getClassLoader()); 314 final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; 315 final String javaVersion = System.getProperty("java.version"); 316 317 String nameAndRelease = name; 318 if (!UNAVAILABLE.equals(release)) { 319 nameAndRelease += "/" + release; 320 } 321 322 return String.format("%s (Java/%s)", nameAndRelease, javaVersion); 323 } 324 325 }