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