/* * The MIT License * * Copyright 2015 Johannes Ernst http://upon2020.com/ * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package jenkins.util; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.EnvVars; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Centralizes calls to {@link System#getProperty()} and related calls. * This allows us to get values not just from environment variables but also from * the {@link ServletContext}, so properties like {@code hudson.DNSMultiCast.disabled} * can be set in {@code context.xml} and the app server's boot script does not * have to be changed. * * <p>This should be used to obtain hudson/jenkins "app"-level parameters * (e.g. {@code hudson.DNSMultiCast.disabled}), but not for system parameters * (e.g. {@code os.name}). * * <p>If you run multiple instances of Jenkins in the same virtual machine and wish * to obtain properties from {@code context.xml}, make sure these Jenkins instances use * different ClassLoaders. Tomcat, for example, does this automatically. If you do * not use different ClassLoaders, the values of properties specified in * {@code context.xml} is undefined. * * <p>Property access is logged on {@link Level#CONFIG}. Note that some properties * may be accessed by Jenkins before logging is configured properly, so early access to * some properties may not be logged. * * <p>While it looks like it on first glance, this cannot be mapped to {@link EnvVars}, * because {@link EnvVars} is only for build variables, not Jenkins itself variables. * * @author Johannes Ernst * @since TODO */ //TODO: Define a correct design of this engine later. Should be accessible in libs (remoting, stapler) and Jenkins modules too @Restricted(NoExternalUse.class) public class SystemProperties implements ServletContextListener { // this class implements ServletContextListener and is declared in WEB-INF/web.xml /** * The ServletContext to get the "init" parameters from. */ @CheckForNull private static ServletContext theContext; /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(SystemProperties.class.getName()); /** * Public for the servlet container. */ public SystemProperties() {} /** * Called by the servlet container to initialize the {@link ServletContext}. */ @Override public void contextInitialized(ServletContextEvent event) { theContext = event.getServletContext(); } /** * Gets the system property indicated by the specified key. * This behaves just like {@link System#getProperty(java.lang.String)}, except that it * also consults the {@link ServletContext}'s "init" parameters. * * @param key the name of the system property. * @return the string value of the system property, * or {@code null} if there is no property with that key. * * @exception NullPointerException if {@code key} is {@code null}. * @exception IllegalArgumentException if {@code key} is empty. */ @CheckForNull public static String getString(String key) { String value = System.getProperty(key); // keep passing on any exceptions if (value != null) { if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (system): {0} => {1}", new Object[] {key, value}); } return value; } value = tryGetValueFromContext(key); if (value != null) { if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (context): {0} => {1}", new Object[]{key, value}); } return value; } if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (not found): {0} => {1}", new Object[] {key, value}); } return null; } /** * Gets the system property indicated by the specified key, or a default value. * This behaves just like {@link System#getProperty(java.lang.String, java.lang.String)}, except * that it also consults the {@link ServletContext}'s "init" parameters. * * @param key the name of the system property. * @param def a default value. * @return the string value of the system property, * or {@code null} if the the property is missing and the default value is {@code null}. * * @exception NullPointerException if {@code key} is {@code null}. * @exception IllegalArgumentException if {@code key} is empty. */ public static String getString(String key, @CheckForNull String def) { String value = System.getProperty(key); // keep passing on any exceptions if (value != null) { if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (system): {0} => {1}", new Object[] {key, value}); } return value; } value = tryGetValueFromContext(key); if (value != null) { if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (context): {0} => {1}", new Object[]{key, value}); } return value; } value = def; if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property (default): {0} => {1}", new Object[] {key, value}); } return value; } /** * Returns {@code true} if the system property * named by the argument exists and is equal to the string * {@code "true"}. If the system property does not exist, return * {@code "false"}. if a property by this name exists in the {@link ServletContext} * and is equal to the string {@code "true"}. * * This behaves just like {@link Boolean#getBoolean(java.lang.String)}, except that it * also consults the {@link ServletContext}'s "init" parameters. * * @param name the system property name. * @return the {@code boolean} value of the system property. */ public static boolean getBoolean(String name) { return getBoolean(name, false); } /** * Returns {@code true} if the system property * named by the argument exists and is equal to the string * {@code "true"}, or a default value. If the system property does not exist, return * {@code "true"} if a property by this name exists in the {@link ServletContext} * and is equal to the string {@code "true"}. If that property does not * exist either, return the default value. * * This behaves just like {@link Boolean#getBoolean(java.lang.String)} with a default * value, except that it also consults the {@link ServletContext}'s "init" parameters. * * @param name the system property name. * @param def a default value. * @return the {@code boolean} value of the system property. */ public static boolean getBoolean(String name, boolean def) { String v = getString(name); if (v != null) { return Boolean.parseBoolean(v); } return def; } /** * Returns {@link Boolean#TRUE} if the named system property exists and is equal to the string {@code "true} * (ignoring case), returns {@link Boolean#FALSE} if the system property exists and doesn't equal {@code "true} * otherwise returns {@code null} if the named system property does not exist. * * @param name the system property name. * @return {@link Boolean#TRUE}, {@link Boolean#FALSE} or {@code null} * @since 2.16 */ @CheckForNull public static Boolean optBoolean(String name) { String v = getString(name); return v == null ? null : Boolean.parseBoolean(v); } /** * Determines the integer value of the system property with the * specified name. * * This behaves just like {@link Integer#getInteger(java.lang.String)}, except that it * also consults the {@link ServletContext}'s "init" parameters. * * @param name property name. * @return the {@code Integer} value of the property. */ @CheckForNull public static Integer getInteger(String name) { return getInteger(name, null); } /** * Determines the integer value of the system property with the * specified name, or a default value. * * This behaves just like <code>Integer.getInteger(String,Integer)</code>, except that it * also consults the <code>ServletContext</code>'s "init" parameters. If neither exist, * return the default value. * * @param name property name. * @param def a default value. * @return the {@code Integer} value of the property. * If the property is missing, return the default value. * Result may be {@code null} only if the default value is {@code null}. */ public static Integer getInteger(String name, Integer def) { String v = getString(name); if (v != null) { try { return Integer.decode(v); } catch (NumberFormatException e) { // Ignore, fallback to default if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property. Value is not integer: {0} => {1}", new Object[] {name, v}); } } } return def; } /** * Determines the long value of the system property with the * specified name. * * This behaves just like {@link Long#getLong(java.lang.String)}, except that it * also consults the {@link ServletContext}'s "init" parameters. * * @param name property name. * @return the {@code Long} value of the property. */ @CheckForNull public static Long getLong(String name) { return getLong(name, null); } /** * Determines the integer value of the system property with the * specified name, or a default value. * * This behaves just like <code>Long.getLong(String,Long)</code>, except that it * also consults the <code>ServletContext</code>'s "init" parameters. If neither exist, * return the default value. * * @param name property name. * @param def a default value. * @return the {@code Long} value of the property. * If the property is missing, return the default value. * Result may be {@code null} only if the default value is {@code null}. */ public static Long getLong(String name, Long def) { String v = getString(name); if (v != null) { try { return Long.decode(v); } catch (NumberFormatException e) { // Ignore, fallback to default if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.log(Level.CONFIG, "Property. Value is not long: {0} => {1}", new Object[] {name, v}); } } } return def; } @CheckForNull private static String tryGetValueFromContext(String key) { if (StringUtils.isNotBlank(key) && theContext != null) { try { String value = theContext.getInitParameter(key); if (value != null) { return value; } } catch (SecurityException ex) { // Log exception and go on LOGGER.log(Level.CONFIG, "Access to the property {0} is prohibited", key); } } return null; } @Override public void contextDestroyed(ServletContextEvent event) { // nothing to do } }