001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.log4j.helpers; 019 020import org.apache.log4j.Level; 021import org.apache.logging.log4j.LogManager; 022import org.apache.logging.log4j.Logger; 023import org.apache.logging.log4j.core.lookup.StrSubstitutor; 024import org.apache.logging.log4j.util.LoaderUtil; 025 026import java.io.InterruptedIOException; 027import java.lang.reflect.InvocationTargetException; 028import java.util.Properties; 029 030/** 031 * A convenience class to convert property values to specific types. 032 */ 033public class OptionConverter { 034 035 static String DELIM_START = "${"; 036 static char DELIM_STOP = '}'; 037 static int DELIM_START_LEN = 2; 038 static int DELIM_STOP_LEN = 1; 039 private static final Logger LOGGER = LogManager.getLogger(OptionConverter.class); 040 private static final CharMap[] charMap = new CharMap[] { 041 new CharMap('n', '\n'), 042 new CharMap('r', '\r'), 043 new CharMap('t', '\t'), 044 new CharMap('f', '\f'), 045 new CharMap('\b', '\b'), 046 new CharMap('\"', '\"'), 047 new CharMap('\'', '\''), 048 new CharMap('\\', '\\') 049 }; 050 051 /** 052 * OptionConverter is a static class. 053 */ 054 private OptionConverter() { 055 } 056 057 public static String[] concatanateArrays(String[] l, String[] r) { 058 int len = l.length + r.length; 059 String[] a = new String[len]; 060 061 System.arraycopy(l, 0, a, 0, l.length); 062 System.arraycopy(r, 0, a, l.length, r.length); 063 064 return a; 065 } 066 067 public static String convertSpecialChars(String s) { 068 char c; 069 int len = s.length(); 070 StringBuilder sbuf = new StringBuilder(len); 071 072 int i = 0; 073 while (i < len) { 074 c = s.charAt(i++); 075 if (c == '\\') { 076 c = s.charAt(i++); 077 for (CharMap entry : charMap) { 078 if (entry.key == c) { 079 c = entry.replacement; 080 } 081 } 082 } 083 sbuf.append(c); 084 } 085 return sbuf.toString(); 086 } 087 088 089 /** 090 * Very similar to <code>System.getProperty</code> except 091 * that the {@link SecurityException} is hidden. 092 * 093 * @param key The key to search for. 094 * @param def The default value to return. 095 * @return the string value of the system property, or the default 096 * value if there is no property with that key. 097 * @since 1.1 098 */ 099 public static String getSystemProperty(String key, String def) { 100 try { 101 return System.getProperty(key, def); 102 } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx 103 LOGGER.debug("Was not allowed to read system property \"{}\".", key); 104 return def; 105 } 106 } 107 108 /** 109 * If <code>value</code> is "true", then <code>true</code> is 110 * returned. If <code>value</code> is "false", then 111 * <code>true</code> is returned. Otherwise, <code>default</code> is 112 * returned. 113 * 114 * <p>Case of value is unimportant. 115 * @param value The value to convert. 116 * @param dEfault The default value. 117 * @return the value of the result. 118 */ 119 public static boolean toBoolean(String value, boolean dEfault) { 120 if (value == null) { 121 return dEfault; 122 } 123 String trimmedVal = value.trim(); 124 if ("true".equalsIgnoreCase(trimmedVal)) { 125 return true; 126 } 127 if ("false".equalsIgnoreCase(trimmedVal)) { 128 return false; 129 } 130 return dEfault; 131 } 132 133 /** 134 * Converts a standard or custom priority level to a Level 135 * object. <p> If <code>value</code> is of form 136 * "level#classname", then the specified class' toLevel method 137 * is called to process the specified level string; if no '#' 138 * character is present, then the default {@link org.apache.log4j.Level} 139 * class is used to process the level value. 140 * 141 * <p>As a special case, if the <code>value</code> parameter is 142 * equal to the string "NULL", then the value <code>null</code> will 143 * be returned. 144 * 145 * <p> If any error occurs while converting the value to a level, 146 * the <code>defaultValue</code> parameter, which may be 147 * <code>null</code>, is returned. 148 * 149 * <p> Case of <code>value</code> is insignificant for the level level, but is 150 * significant for the class name part, if present. 151 * @param value The value to convert. 152 * @param defaultValue The default value. 153 * @return the value of the result. 154 * 155 * @since 1.1 156 */ 157 public static Level toLevel(String value, Level defaultValue) { 158 if (value == null) { 159 return defaultValue; 160 } 161 162 value = value.trim(); 163 164 int hashIndex = value.indexOf('#'); 165 if (hashIndex == -1) { 166 if ("NULL".equalsIgnoreCase(value)) { 167 return null; 168 } 169 // no class name specified : use standard Level class 170 return Level.toLevel(value, defaultValue); 171 } 172 173 Level result = defaultValue; 174 175 String clazz = value.substring(hashIndex + 1); 176 String levelName = value.substring(0, hashIndex); 177 178 // This is degenerate case but you never know. 179 if ("NULL".equalsIgnoreCase(levelName)) { 180 return null; 181 } 182 183 LOGGER.debug("toLevel" + ":class=[" + clazz + "]" 184 + ":pri=[" + levelName + "]"); 185 186 try { 187 Class<?> customLevel = LoaderUtil.loadClass(clazz); 188 189 // get a ref to the specified class' static method 190 // toLevel(String, org.apache.log4j.Level) 191 Class<?>[] paramTypes = new Class[] { String.class, org.apache.log4j.Level.class }; 192 java.lang.reflect.Method toLevelMethod = 193 customLevel.getMethod("toLevel", paramTypes); 194 195 // now call the toLevel method, passing level string + default 196 Object[] params = new Object[]{levelName, defaultValue}; 197 Object o = toLevelMethod.invoke(null, params); 198 199 result = (Level) o; 200 } catch (ClassNotFoundException e) { 201 LOGGER.warn("custom level class [" + clazz + "] not found."); 202 } catch (NoSuchMethodException e) { 203 LOGGER.warn("custom level class [" + clazz + "]" 204 + " does not have a class function toLevel(String, Level)", e); 205 } catch (java.lang.reflect.InvocationTargetException e) { 206 if (e.getTargetException() instanceof InterruptedException 207 || e.getTargetException() instanceof InterruptedIOException) { 208 Thread.currentThread().interrupt(); 209 } 210 LOGGER.warn("custom level class [" + clazz + "]" 211 + " could not be instantiated", e); 212 } catch (ClassCastException e) { 213 LOGGER.warn("class [" + clazz 214 + "] is not a subclass of org.apache.log4j.Level", e); 215 } catch (IllegalAccessException e) { 216 LOGGER.warn("class [" + clazz + 217 "] cannot be instantiated due to access restrictions", e); 218 } catch (RuntimeException e) { 219 LOGGER.warn("class [" + clazz + "], level [" + levelName + 220 "] conversion failed.", e); 221 } 222 return result; 223 } 224 225 /** 226 * Instantiate an object given a class name. Check that the 227 * <code>className</code> is a subclass of 228 * <code>superClass</code>. If that test fails or the object could 229 * not be instantiated, then <code>defaultValue</code> is returned. 230 * 231 * @param className The fully qualified class name of the object to instantiate. 232 * @param superClass The class to which the new object should belong. 233 * @param defaultValue The object to return in case of non-fulfillment 234 * @return The created object. 235 */ 236 public static Object instantiateByClassName(String className, Class<?> superClass, 237 Object defaultValue) { 238 if (className != null) { 239 try { 240 Object obj = LoaderUtil.newInstanceOf(className); 241 if (!superClass.isAssignableFrom(obj.getClass())) { 242 LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable", className, 243 superClass.getName()); 244 return defaultValue; 245 } 246 return obj; 247 } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException 248 | InstantiationException | InvocationTargetException e) { 249 LOGGER.error("Could not instantiate class [" + className + "].", e); 250 } 251 } 252 return defaultValue; 253 } 254 255 256 /** 257 * Perform variable substitution in string <code>val</code> from the 258 * values of keys found in the system propeties. 259 * 260 * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>. 261 * 262 * <p>For example, if the System properties contains "key=value", then 263 * the call 264 * <pre> 265 * String s = OptionConverter.substituteVars("Value of key is ${key}."); 266 * </pre> 267 * <p> 268 * will set the variable <code>s</code> to "Value of key is value.". 269 * 270 * <p>If no value could be found for the specified key, then the 271 * <code>props</code> parameter is searched, if the value could not 272 * be found there, then substitution defaults to the empty string. 273 * 274 * <p>For example, if system propeties contains no value for the key 275 * "inexistentKey", then the call 276 * 277 * <pre> 278 * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]"); 279 * </pre> 280 * will set <code>s</code> to "Value of inexistentKey is []" 281 * 282 * <p>An {@link IllegalArgumentException} is thrown if 283 * <code>val</code> contains a start delimeter "${" which is not 284 * balanced by a stop delimeter "}". </p> 285 * 286 * <p><b>Author</b> Avy Sharell</p> 287 * 288 * @param val The string on which variable substitution is performed. 289 * @param props The properties to use for the substitution. 290 * @return The substituted string. 291 * @throws IllegalArgumentException if <code>val</code> is malformed. 292 */ 293 public static String substVars(String val, Properties props) throws IllegalArgumentException { 294 return StrSubstitutor.replace(val, props); 295 } 296 297 public static org.apache.logging.log4j.Level convertLevel(String level, 298 org.apache.logging.log4j.Level defaultLevel) { 299 Level l = toLevel(level, null); 300 return l != null ? convertLevel(l) : defaultLevel; 301 } 302 303 public static org.apache.logging.log4j.Level convertLevel(Level level) { 304 if (level == null) { 305 return org.apache.logging.log4j.Level.ERROR; 306 } 307 if (level.isGreaterOrEqual(Level.FATAL)) { 308 return org.apache.logging.log4j.Level.FATAL; 309 } else if (level.isGreaterOrEqual(Level.ERROR)) { 310 return org.apache.logging.log4j.Level.ERROR; 311 } else if (level.isGreaterOrEqual(Level.WARN)) { 312 return org.apache.logging.log4j.Level.WARN; 313 } else if (level.isGreaterOrEqual(Level.INFO)) { 314 return org.apache.logging.log4j.Level.INFO; 315 } else if (level.isGreaterOrEqual(Level.DEBUG)) { 316 return org.apache.logging.log4j.Level.DEBUG; 317 } else if (level.isGreaterOrEqual(Level.TRACE)) { 318 return org.apache.logging.log4j.Level.TRACE; 319 } 320 return org.apache.logging.log4j.Level.ALL; 321 } 322 323 public static Level convertLevel(org.apache.logging.log4j.Level level) { 324 if (level == null) { 325 return Level.ERROR; 326 } 327 switch (level.getStandardLevel()) { 328 case FATAL: 329 return Level.FATAL; 330 case WARN: 331 return Level.WARN; 332 case INFO: 333 return Level.INFO; 334 case DEBUG: 335 return Level.DEBUG; 336 case TRACE: 337 return Level.TRACE; 338 case ALL: 339 return Level.ALL; 340 case OFF: 341 return Level.OFF; 342 default: 343 return Level.ERROR; 344 } 345 } 346 347 /** 348 * Find the value corresponding to <code>key</code> in 349 * <code>props</code>. Then perform variable substitution on the 350 * found value. 351 * @param key The key used to locate the substitution string. 352 * @param props The properties to use in the substitution. 353 * @return The substituted string. 354 */ 355 public static String findAndSubst(String key, Properties props) { 356 String value = props.getProperty(key); 357 if (value == null) { 358 return null; 359 } 360 361 try { 362 return substVars(value, props); 363 } catch (IllegalArgumentException e) { 364 LOGGER.error("Bad option value [{}].", value, e); 365 return value; 366 } 367 } 368 369 private static class CharMap { 370 final char key; 371 final char replacement; 372 373 public CharMap(char key, char replacement) { 374 this.key = key; 375 this.replacement = replacement; 376 } 377 } 378}