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 /** 85 * Instantiates version information. 86 * 87 * @param pckg the package 88 * @param module the module, or {@code null} 89 * @param release the release, or {@code null} 90 * @param time the build time, or {@code null} 91 * @param clsldr the class loader, or {@code null} 92 */ 93 protected VersionInfo(final String pckg, final String module, 94 final String release, final String time, final String clsldr) { 95 Args.notNull(pckg, "Package identifier"); 96 infoPackage = pckg; 97 infoModule = (module != null) ? module : UNAVAILABLE; 98 infoRelease = (release != null) ? release : UNAVAILABLE; 99 infoTimestamp = (time != null) ? time : UNAVAILABLE; 100 infoClassloader = (clsldr != null) ? clsldr : UNAVAILABLE; 101 } 102 103 104 /** 105 * Obtains the package name. 106 * The package name identifies the module or informal unit. 107 * 108 * @return the package name, never {@code null} 109 */ 110 public final String getPackage() { 111 return infoPackage; 112 } 113 114 /** 115 * Obtains the name of the versioned module or informal unit. 116 * This data is read from the version information for the package. 117 * 118 * @return the module name, never {@code null} 119 */ 120 public final String getModule() { 121 return infoModule; 122 } 123 124 /** 125 * Obtains the release of the versioned module or informal unit. 126 * This data is read from the version information for the package. 127 * 128 * @return the release version, never {@code null} 129 */ 130 public final String getRelease() { 131 return infoRelease; 132 } 133 134 /** 135 * Obtains the timestamp of the versioned module or informal unit. 136 * This data is read from the version information for the package. 137 * 138 * @return the timestamp, never {@code null} 139 * @deprecated This will be removed in 6.0. 140 */ 141 @Deprecated 142 public final String getTimestamp() { 143 return infoTimestamp; 144 } 145 146 /** 147 * Obtains the classloader used to read the version information. 148 * This is just the {@code toString} output of the classloader, 149 * since the version information should not keep a reference to 150 * the classloader itself. That could prevent garbage collection. 151 * 152 * @return the classloader description, never {@code null} 153 */ 154 public final String getClassloader() { 155 return infoClassloader; 156 } 157 158 159 /** 160 * Provides the version information in human-readable format. 161 * 162 * @return a string holding this version information 163 */ 164 @Override 165 public String toString() { 166 final StringBuilder sb = new StringBuilder 167 (20 + infoPackage.length() + infoModule.length() + 168 infoRelease.length() + infoTimestamp.length() + 169 infoClassloader.length()); 170 171 sb.append("VersionInfo(") 172 .append(infoPackage).append(':').append(infoModule); 173 174 // If version info is missing, a single "UNAVAILABLE" for the module 175 // is sufficient. Everything else just clutters the output. 176 if (!UNAVAILABLE.equals(infoRelease)) { 177 sb.append(':').append(infoRelease); 178 } 179 180 sb.append(')'); 181 182 if (!UNAVAILABLE.equals(infoClassloader)) { 183 sb.append('@').append(infoClassloader); 184 } 185 186 return sb.toString(); 187 } 188 189 190 /** 191 * Loads version information for a list of packages. 192 * 193 * @param pckgs the packages for which to load version info 194 * @param clsldr the classloader to load from, or 195 * {@code null} for the thread context classloader 196 * 197 * @return the version information for all packages found, 198 * never {@code null} 199 */ 200 public static VersionInfo[] loadVersionInfo(final String[] pckgs, 201 final ClassLoader clsldr) { 202 Args.notNull(pckgs, "Package identifier array"); 203 final List<VersionInfo> vil = new ArrayList<>(pckgs.length); 204 for (final String pckg : pckgs) { 205 final VersionInfo vi = loadVersionInfo(pckg, clsldr); 206 if (vi != null) { 207 vil.add(vi); 208 } 209 } 210 211 return vil.toArray(new VersionInfo[vil.size()]); 212 } 213 214 215 /** 216 * Loads version information for a package. 217 * 218 * @param pckg the package for which to load version information, 219 * for example "org.apache.http". 220 * The package name should NOT end with a dot. 221 * @param clsldr the classloader to load from, or 222 * {@code null} for the thread context classloader 223 * 224 * @return the version information for the argument package, or 225 * {@code null} if not available 226 */ 227 public static VersionInfo loadVersionInfo(final String pckg, final ClassLoader clsldr) { 228 Args.notNull(pckg, "Package identifier"); 229 final ClassLoader cl = clsldr != null ? clsldr : Thread.currentThread().getContextClassLoader(); 230 231 Properties vip = null; // version info properties, if available 232 try { 233 // org.apache.http becomes 234 // org/apache/http/version.properties 235 try (final InputStream is = cl.getResourceAsStream(pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE)) { 236 if (is != null) { 237 final Properties props = new Properties(); 238 props.load(is); 239 vip = props; 240 } 241 } 242 } catch (final IOException ex) { 243 // shamelessly munch this exception 244 } 245 246 VersionInfo result = null; 247 if (vip != null) { 248 result = fromMap(pckg, vip, cl); 249 } 250 251 return result; 252 } 253 254 /** 255 * Instantiates version information from properties. 256 * 257 * @param pckg the package for the version information 258 * @param info the map from string keys to string values, 259 * for example {@link java.util.Properties} 260 * @param clsldr the classloader, or {@code null} 261 * 262 * @return the version information 263 */ 264 protected static VersionInfo fromMap(final String pckg, final Map<?, ?> info, 265 final ClassLoader clsldr) { 266 Args.notNull(pckg, "Package identifier"); 267 String module = null; 268 String release = null; 269 270 if (info != null) { 271 module = (String) info.get(PROPERTY_MODULE); 272 if ((module != null) && (module.length() < 1)) { 273 module = null; 274 } 275 276 release = (String) info.get(PROPERTY_RELEASE); 277 if ((release != null) && ((release.length() < 1) || 278 (release.equals("${project.version}")))) { 279 release = null; 280 } 281 } // if info 282 283 String clsldrstr = null; 284 if (clsldr != null) { 285 clsldrstr = clsldr.toString(); 286 } 287 288 return new VersionInfo(pckg, module, release, null, clsldrstr); 289 } 290 291 /** 292 * Gets software information as {@code "<name>/<release> (Java/<java.version>)"}. If release is 293 * {@link #UNAVAILABLE}, it will be omitted. 294 * <p> 295 * For example: 296 * <pre>"Apache-HttpClient/4.3 (Java/1.6.0_35)"</pre> 297 * 298 * @param name the component name, like "Apache-HttpClient". 299 * @param pkg 300 * the package for which to load version information, for example "org.apache.http". The package name 301 * should NOT end with a dot. 302 * @param cls 303 * the class' class loader to load from, or {@code null} for the thread context class loader 304 * @since 4.3 305 */ 306 public static String getSoftwareInfo(final String name, final String pkg, final Class<?> cls) { 307 // determine the release version from packaged version info 308 final VersionInfo vi = VersionInfo.loadVersionInfo(pkg, cls.getClassLoader()); 309 final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; 310 final String javaVersion = System.getProperty("java.version"); 311 312 String nameAndRelease = name; 313 if (!UNAVAILABLE.equals(release)) { 314 nameAndRelease += "/" + release; 315 } 316 317 return String.format("%s (Java/%s)", nameAndRelease, javaVersion); 318 } 319 320 }