001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.util; 029 030import java.io.IOException; 031import java.io.InputStream; 032import java.util.ArrayList; 033import java.util.List; 034import java.util.Map; 035import java.util.Properties; 036 037/** 038 * Provides access to version information for HTTP components. 039 * Static methods are used to extract version information from property 040 * files that are automatically packaged with HTTP component release JARs. 041 * <p> 042 * All available version information is provided in strings, where 043 * the string format is informal and subject to change without notice. 044 * Version information is provided for debugging output and interpretation 045 * by humans, not for automated processing in applications. 046 * </p> 047 * 048 * @since 4.0 049 */ 050public class VersionInfo { 051 052 /** A string constant for unavailable information. */ 053 public final static String UNAVAILABLE = "UNAVAILABLE"; 054 055 /** The filename of the version information files. */ 056 public final static String VERSION_PROPERTY_FILE = "version.properties"; 057 058 // the property names 059 public final static String PROPERTY_MODULE = "info.module"; 060 public final static String PROPERTY_RELEASE = "info.release"; 061 public final static String PROPERTY_TIMESTAMP = "info.timestamp"; 062 063 064 /** The package that contains the version information. */ 065 private final String infoPackage; 066 067 /** The module from the version info. */ 068 private final String infoModule; 069 070 /** The release from the version info. */ 071 private final String infoRelease; 072 073 /** The timestamp from the version info. */ 074 private final String infoTimestamp; 075 076 /** The classloader from which the version info was obtained. */ 077 private final String infoClassloader; 078 079 080 /** 081 * Instantiates version information. 082 * 083 * @param pckg the package 084 * @param module the module, or {@code null} 085 * @param release the release, or {@code null} 086 * @param time the build time, or {@code null} 087 * @param clsldr the class loader, or {@code null} 088 */ 089 protected VersionInfo(final String pckg, final String module, 090 final String release, final String time, final String clsldr) { 091 Args.notNull(pckg, "Package identifier"); 092 infoPackage = pckg; 093 infoModule = (module != null) ? module : UNAVAILABLE; 094 infoRelease = (release != null) ? release : UNAVAILABLE; 095 infoTimestamp = (time != null) ? time : UNAVAILABLE; 096 infoClassloader = (clsldr != null) ? clsldr : UNAVAILABLE; 097 } 098 099 100 /** 101 * Obtains the package name. 102 * The package name identifies the module or informal unit. 103 * 104 * @return the package name, never {@code null} 105 */ 106 public final String getPackage() { 107 return infoPackage; 108 } 109 110 /** 111 * Obtains the name of the versioned module or informal unit. 112 * This data is read from the version information for the package. 113 * 114 * @return the module name, never {@code null} 115 */ 116 public final String getModule() { 117 return infoModule; 118 } 119 120 /** 121 * Obtains the release of the versioned module or informal unit. 122 * This data is read from the version information for the package. 123 * 124 * @return the release version, never {@code null} 125 */ 126 public final String getRelease() { 127 return infoRelease; 128 } 129 130 /** 131 * Obtains the timestamp of the versioned module or informal unit. 132 * This data is read from the version information for the package. 133 * 134 * @return the timestamp, never {@code null} 135 */ 136 public final String getTimestamp() { 137 return infoTimestamp; 138 } 139 140 /** 141 * Obtains the classloader used to read the version information. 142 * This is just the {@code toString} output of the classloader, 143 * since the version information should not keep a reference to 144 * the classloader itself. That could prevent garbage collection. 145 * 146 * @return the classloader description, never {@code null} 147 */ 148 public final String getClassloader() { 149 return infoClassloader; 150 } 151 152 153 /** 154 * Provides the version information in human-readable format. 155 * 156 * @return a string holding this version information 157 */ 158 @Override 159 public String toString() { 160 final StringBuilder sb = new StringBuilder 161 (20 + infoPackage.length() + infoModule.length() + 162 infoRelease.length() + infoTimestamp.length() + 163 infoClassloader.length()); 164 165 sb.append("VersionInfo(") 166 .append(infoPackage).append(':').append(infoModule); 167 168 // If version info is missing, a single "UNAVAILABLE" for the module 169 // is sufficient. Everything else just clutters the output. 170 if (!UNAVAILABLE.equals(infoRelease)) { 171 sb.append(':').append(infoRelease); 172 } 173 if (!UNAVAILABLE.equals(infoTimestamp)) { 174 sb.append(':').append(infoTimestamp); 175 } 176 177 sb.append(')'); 178 179 if (!UNAVAILABLE.equals(infoClassloader)) { 180 sb.append('@').append(infoClassloader); 181 } 182 183 return sb.toString(); 184 } 185 186 187 /** 188 * Loads version information for a list of packages. 189 * 190 * @param pckgs the packages for which to load version info 191 * @param clsldr the classloader to load from, or 192 * {@code null} for the thread context classloader 193 * 194 * @return the version information for all packages found, 195 * never {@code null} 196 */ 197 public static VersionInfo[] loadVersionInfo(final String[] pckgs, 198 final ClassLoader clsldr) { 199 Args.notNull(pckgs, "Package identifier array"); 200 final List<VersionInfo> vil = new ArrayList<VersionInfo>(pckgs.length); 201 for (final String pckg : pckgs) { 202 final VersionInfo vi = loadVersionInfo(pckg, clsldr); 203 if (vi != null) { 204 vil.add(vi); 205 } 206 } 207 208 return vil.toArray(new VersionInfo[vil.size()]); 209 } 210 211 212 /** 213 * Loads version information for a package. 214 * 215 * @param pckg the package for which to load version information, 216 * for example "org.apache.http". 217 * The package name should NOT end with a dot. 218 * @param clsldr the classloader to load from, or 219 * {@code null} for the thread context classloader 220 * 221 * @return the version information for the argument package, or 222 * {@code null} if not available 223 */ 224 public static VersionInfo loadVersionInfo(final String pckg, 225 final ClassLoader clsldr) { 226 Args.notNull(pckg, "Package identifier"); 227 final ClassLoader cl = clsldr != null ? clsldr : Thread.currentThread().getContextClassLoader(); 228 229 Properties vip = null; // version info properties, if available 230 try { 231 // org.apache.http becomes 232 // org/apache/http/version.properties 233 final InputStream is = cl.getResourceAsStream 234 (pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE); 235 if (is != null) { 236 try { 237 final Properties props = new Properties(); 238 props.load(is); 239 vip = props; 240 } finally { 241 is.close(); 242 } 243 } 244 } catch (final IOException ex) { 245 // shamelessly munch this exception 246 } 247 248 VersionInfo result = null; 249 if (vip != null) { 250 result = fromMap(pckg, vip, cl); 251 } 252 253 return result; 254 } 255 256 257 /** 258 * Instantiates version information from properties. 259 * 260 * @param pckg the package for the version information 261 * @param info the map from string keys to string values, 262 * for example {@link java.util.Properties} 263 * @param clsldr the classloader, or {@code null} 264 * 265 * @return the version information 266 */ 267 protected static VersionInfo fromMap(final String pckg, final Map<?, ?> info, 268 final ClassLoader clsldr) { 269 Args.notNull(pckg, "Package identifier"); 270 String module = null; 271 String release = null; 272 String timestamp = null; 273 274 if (info != null) { 275 module = (String) info.get(PROPERTY_MODULE); 276 if ((module != null) && (module.length() < 1)) { 277 module = null; 278 } 279 280 release = (String) info.get(PROPERTY_RELEASE); 281 if ((release != null) && ((release.length() < 1) || 282 (release.equals("${pom.version}")))) { 283 release = null; 284 } 285 286 timestamp = (String) info.get(PROPERTY_TIMESTAMP); 287 if ((timestamp != null) && 288 ((timestamp.length() < 1) || 289 (timestamp.equals("${mvn.timestamp}"))) 290 ) { 291 timestamp = null; 292 } 293 } // if info 294 295 String clsldrstr = null; 296 if (clsldr != null) { 297 clsldrstr = clsldr.toString(); 298 } 299 300 return new VersionInfo(pckg, module, release, timestamp, clsldrstr); 301 } 302 303 /** 304 * Sets the user agent to {@code "<name>/<release> (Java/<java.version>)"}. 305 * <p> 306 * For example: 307 * <pre>"Apache-HttpClient/4.3 (Java/1.6.0_35)"</pre> 308 * 309 * @param name the component name, like "Apache-HttpClient". 310 * @param pkg 311 * the package for which to load version information, for example "org.apache.http". The package name 312 * should NOT end with a dot. 313 * @param cls 314 * the class' class loader to load from, or {@code null} for the thread context class loader 315 * @since 4.3 316 */ 317 public static String getUserAgent(final String name, final String pkg, final Class<?> cls) { 318 // determine the release version from packaged version info 319 final VersionInfo vi = VersionInfo.loadVersionInfo(pkg, cls.getClassLoader()); 320 final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; 321 final String javaVersion = System.getProperty("java.version"); 322 323 return String.format("%s/%s (Java/%s)", name, release, javaVersion); 324 } 325 326} // class VersionInfo