org.eclipse.angus.mail.util.logging.LogManagerProperties Maven / Gradle / Ivy
/*
* Copyright (c) 2009, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2024 Jason Mehrens. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.eclipse.angus.mail.util.logging;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.ObjectStreamException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
* An adapter class to allow the Mail API to access the LogManager properties.
* The LogManager properties are treated as the root of all properties. First,
* the parent properties are searched. If no value is found, then, the
* LogManager is searched with prefix value. If not found, then, just the key
* itself is searched in the LogManager. If a value is found in the LogManager
* it is then copied to this properties object with no key prefix. If no value
* is found in the LogManager or the parent properties, then this properties
* object is searched only by passing the key value.
*
*
* This class also emulates the LogManager functions for creating new objects
* from string class names. This is to support initial setup of objects such as
* log filters, formatters, error managers, etc.
*
*
* This class should never be exposed outside of this package. Keep this class
* package private (default access).
*
* @author Jason Mehrens
* @since JavaMail 1.4.3
*/
final class LogManagerProperties extends Properties {
/**
* Generated serial id.
*/
private static final long serialVersionUID = -2239983349056806252L;
/**
* Holds the method used to get the LogRecord instant if running on JDK 9 or
* later.
*/
private static final Method LR_GET_INSTANT;
/**
* Holds the method used to get the long thread id if running on JDK 16 or
* later.
*/
private static final Method LR_GET_LONG_TID;
/**
* Holds the method used to get the default time zone if running on JDK 9 or
* later.
*/
private static final Method ZI_SYSTEM_DEFAULT;
/**
* Holds the method used to convert and instant to a zoned date time if
* running on JDK 9 later.
*/
private static final Method ZDT_OF_INSTANT;
/**
* MethodHandle is available starting at JDK7 and Android API 26.
*/
static { //Added in JDK16 see JDK-8245302
Method lrtid = null;
try {
lrtid = LogRecord.class.getMethod("getLongThreadID");
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) { //No need for specific catch.
} catch (final LinkageError ignore) {
}
LR_GET_LONG_TID = lrtid;
}
static { //Added in JDK9 see JDK-8072645
Method lrgi = null;
Method zisd = null;
Method zdtoi = null;
try {
lrgi = LogRecord.class.getMethod("getInstant");
assert Comparable.class
.isAssignableFrom(lrgi.getReturnType()) : lrgi;
zisd = findClass("java.time.ZoneId")
.getMethod("systemDefault");
if (!Modifier.isStatic(zisd.getModifiers())) {
throw new NoSuchMethodException(zisd.toString());
}
zdtoi = findClass("java.time.ZonedDateTime")
.getMethod("ofInstant", findClass("java.time.Instant"),
findClass("java.time.ZoneId"));
if (!Modifier.isStatic(zdtoi.getModifiers())) {
throw new NoSuchMethodException(zdtoi.toString());
}
if (!Comparable.class.isAssignableFrom(zdtoi.getReturnType())) {
throw new NoSuchMethodException(zdtoi.toString());
}
} catch (final RuntimeException | LinkageError ignore) {
} catch (final Exception ignore) { //No need for specific catch.
} finally {
if (lrgi == null || zisd == null || zdtoi == null) {
lrgi = null; //If any are null then clear all.
zisd = null;
zdtoi = null;
}
}
LR_GET_INSTANT = lrgi;
ZI_SYSTEM_DEFAULT = zisd;
ZDT_OF_INSTANT = zdtoi;
}
/**
* Caches the read only reflection class names string array. Declared
* volatile for safe publishing only. The VO_VOLATILE_REFERENCE_TO_ARRAY
* warning is a false positive.
*/
@SuppressWarnings("VolatileArrayField")
private static volatile String[] REFLECT_NAMES;
/**
* Caches the LogManager or Properties so we only read the configuration
* once.
*/
private static final Object LOG_MANAGER = loadLogManager();
/**
* Get the LogManager or loads a Properties object to use as the LogManager.
*
* @return the LogManager or a loaded Properties object.
* @since JavaMail 1.5.3
*/
private static Object loadLogManager() {
Object m;
try {
m = LogManager.getLogManager();
} catch (final LinkageError | RuntimeException restricted) {
m = readConfiguration(); //GAE will forbid access to LogManager
}
return m;
}
/**
* Create a properties object from the default logging configuration file.
* Since the LogManager is not available in restricted environments, only
* the default configuration is applicable.
*
* @return a properties object loaded with the default configuration.
* @since JavaMail 1.5.3
*/
private static Properties readConfiguration() {
/**
* Load the properties file so the default settings are available when
* user code creates a logging object. The class loader for the
* restricted LogManager can't access these classes to attach them to a
* logger or handler on startup. Creating logging objects at this point
* is both useless and risky.
*/
final Properties props = new Properties();
try {
String n = System.getProperty("java.util.logging.config.file");
if (n != null) {
final File f = new File(n).getCanonicalFile();
try (InputStream in = new FileInputStream(f)) {
props.load(in);
}
}
} catch (final LinkageError | Exception permissionsOrMalformed) {
}
return props;
}
/**
* Gets LogManger property for the running JVM. If the LogManager doesn't
* exist then the default LogManger properties are used.
*
* @param name the property name.
* @return the LogManager.
* @throws NullPointerException if the given name is null.
* @since JavaMail 1.5.3
*/
static String fromLogManager(final String name) {
Objects.requireNonNull(name);
final Object m = LOG_MANAGER;
try {
if (m instanceof Properties) {
return ((Properties) m).getProperty(name);
}
} catch (final RuntimeException unexpected) {
}
if (m != null) {
try {
if (m instanceof LogManager) {
return ((LogManager) m).getProperty(name);
}
} catch (final LinkageError | RuntimeException restricted) {
} //GAE will forbid access to LogManager
}
return null;
}
/**
* Check that the current context is trusted to modify the logging
* configuration. This requires LoggingPermission("control").
*
* @throws SecurityException if a security manager exists and the caller
* does not have {@code LoggingPermission("control")}.
* @since JavaMail 1.5.3
*/
static void checkLogManagerAccess() {
boolean checked = false;
final Object m = LOG_MANAGER;
if (m != null) {
try {
if (m instanceof LogManager) {
try {
LogManager.class.getMethod("checkAccess").invoke(m);
checked = true;
} catch (InvocationTargetException ite) {
Throwable cause = ite.getCause();
if (cause instanceof SecurityException) {
checked = true;
throw (SecurityException) cause;
}
if (cause instanceof UnsupportedOperationException) {
checked = true;
}
} catch (NoSuchMethodException removed) {
checked = true;
} catch (ReflectiveOperationException fallthrough) {
}
}
} catch (final SecurityException notAllowed) {
if (checked) {
throw notAllowed;
}
} catch (final LinkageError | RuntimeException restricted) {
} //GAE will forbid access to LogManager
}
/**
* Some environments selectively enforce logging permissions by allowing
* access to loggers but not allowing access to handlers. This is an
* indirect way of checking for LoggingPermission when the LogManager is
* not present. The root logger will lazy create handlers so the global
* logger is used instead as it is a known named logger with well
* defined behavior. Contractually, Logger::remove will check
* permission before checking if the argument is null.
* See JDK-8023168
*/
if (!checked) {
try {
Logger.getGlobal().removeHandler((Handler) null);
} catch (final NullPointerException unexpected) {
}
}
}
/**
* Determines if access to the {@code java.util.logging.LogManager} class is
* restricted by the class loader.
*
* @return true if a LogManager is present.
* @since JavaMail 1.5.3
*/
static boolean hasLogManager() {
final Object m = LOG_MANAGER;
return m != null && !(m instanceof Properties);
}
/**
* Gets the ZonedDateTime from the given log record.
*
* @param record used to generate the zoned date time.
* @return null if LogRecord doesn't support nanoseconds otherwise a new
* zoned date time is returned.
* @throws NullPointerException if record is null.
* @since JavaMail 1.5.6
*/
@SuppressWarnings("UseSpecificCatch")
static Comparable> getZonedDateTime(LogRecord record) {
Objects.requireNonNull(record);
final Method m = ZDT_OF_INSTANT;
if (m != null) {
try {
return (Comparable>) m.invoke((Object) null,
LR_GET_INSTANT.invoke(record),
ZI_SYSTEM_DEFAULT.invoke((Object) null));
} catch (final RuntimeException ignore) {
assert LR_GET_INSTANT != null
&& ZI_SYSTEM_DEFAULT != null : ignore;
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof Error) {
throw (Error) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else { //Should never happen.
throw new UndeclaredThrowableException(ite);
}
} catch (final Exception ignore) {
}
}
return null;
}
/**
* Gets the long thread id from the given log record.
*
* @param record used to get the long thread id.
* @return null if LogRecord doesn't support long thread ids.
* @throws NullPointerException if record is null.
* @since Angus Mail 2.0.3
*/
static Long getLongThreadID(final LogRecord record) {
Objects.requireNonNull(record);
final Method m = LR_GET_LONG_TID;
if (m != null) {
try {
return (Long) m.invoke(record);
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof Error) {
throw (Error) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else { //Should never happen.
throw new UndeclaredThrowableException(ite);
}
} catch (final RuntimeException ignore) {
} catch (final Exception ignore) {
}
}
return null;
}
/**
* Gets the local host name from the given service.
*
* @param s the service to examine.
* @return the local host name or null.
* @throws IllegalAccessException if the method is inaccessible.
* @throws InvocationTargetException if the method throws an exception.
* @throws LinkageError if the linkage fails.
* @throws NullPointerException if the given service is null.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception if there is a problem.
* @throws NoSuchMethodException if the given service does not have a method
* to get the local host name as a string.
* @throws SecurityException if unable to inspect properties of object.
* @since JavaMail 1.5.3
*/
static String getLocalHost(final Object s) throws Exception {
Objects.requireNonNull(s);
try {
final Method m = s.getClass().getMethod("getLocalHost");
if (!Modifier.isStatic(m.getModifiers())
&& m.getReturnType() == String.class) {
return (String) m.invoke(s);
} else {
throw new NoSuchMethodException(m.toString());
}
} catch (final ExceptionInInitializerError EIIE) {
throw wrapOrThrow(EIIE);
} catch (final InvocationTargetException ite) {
throw paramOrError(ite);
}
}
/**
* Used to parse an ISO-8601 duration format of {@code PnDTnHnMn.nS}.
*
* @param value an ISO-8601 duration character sequence.
* @return the number of milliseconds parsed from the duration.
* @throws ArithmeticException if the duration is too large or too small.
* @throws ClassNotFoundException if the java.time classes are not present.
* @throws IllegalAccessException if the method is inaccessible.
* @throws InvocationTargetException if the method throws an exception.
* @throws LinkageError if the linkage fails.
* @throws NullPointerException if the given duration is null.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception if there is a problem.
* @throws NoSuchMethodException if the correct time methods are missing.
* @throws SecurityException if reflective access to the java.time classes
* are not allowed.
* @since JavaMail 1.5.5
*/
static long parseDurationToMillis(final CharSequence value) throws Exception {
Objects.requireNonNull(value);
try {
final Class> k = findClass("java.time.Duration");
final Method parse = k.getMethod("parse", CharSequence.class);
if (!k.isAssignableFrom(parse.getReturnType())
|| !Modifier.isStatic(parse.getModifiers())) {
throw new NoSuchMethodException(parse.toString());
}
final Method toMillis = k.getMethod("toMillis");
if (!Long.TYPE.isAssignableFrom(toMillis.getReturnType())
|| Modifier.isStatic(toMillis.getModifiers())) {
throw new NoSuchMethodException(toMillis.toString());
}
return (Long) toMillis.invoke(parse.invoke(null, value));
} catch (final ExceptionInInitializerError EIIE) {
throw wrapOrThrow(EIIE);
} catch (final InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof ArithmeticException) {
throw (ArithmeticException) cause;
} else {
throw paramOrError(ite);
}
}
}
/**
* Converts a locale to a language tag.
*
* @param locale the locale to convert.
* @return the language tag.
* @throws NullPointerException if the given locale is null.
* @since JavaMail 1.4.5
*/
static String toLanguageTag(final Locale locale) {
final String l = locale.getLanguage();
final String c = locale.getCountry();
final String v = locale.getVariant();
final char[] b = new char[l.length() + c.length() + v.length() + 2];
int count = l.length();
l.getChars(0, count, b, 0);
if (c.length() != 0 || (l.length() != 0 && v.length() != 0)) {
b[count] = '-';
++count; //be nice to the client compiler.
c.getChars(0, c.length(), b, count);
count += c.length();
}
if (v.length() != 0 && (l.length() != 0 || c.length() != 0)) {
b[count] = '-';
++count; //be nice to the client compiler.
v.getChars(0, v.length(), b, count);
count += v.length();
}
return String.valueOf(b, 0, count);
}
/**
* Creates a new filter from the given class name.
*
* @param name the fully qualified class name.
* @return a new filter.
* @throws ClassCastException if class name does not match the type.
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws InstantiationException if the given class name is abstract.
* @throws InvocationTargetException if the constructor throws an exception.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws NoSuchMethodException if the class name does not have a no
* argument constructor.
* @since JavaMail 1.4.5
*/
static Filter newFilter(String name) throws Exception {
return newObjectFrom(name, Filter.class);
}
/**
* Creates a new formatter from the given class name.
*
* @param name the fully qualified class name.
* @return a new formatter.
* @throws ClassCastException if class name does not match the type.
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws InstantiationException if the given class name is abstract.
* @throws InvocationTargetException if the constructor throws an exception.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws NoSuchMethodException if the class name does not have a no
* argument constructor.
* @since JavaMail 1.4.5
*/
static Formatter newFormatter(String name) throws Exception {
return newObjectFrom(name, Formatter.class);
}
/**
* Creates a new log record comparator from the given class name.
*
* @param name the fully qualified class name.
* @return a new comparator.
* @throws ClassCastException if class name does not match the type.
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws InstantiationException if the given class name is abstract.
* @throws InvocationTargetException if the constructor throws an exception.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws NoSuchMethodException if the class name does not have a no
* argument constructor.
* @see java.util.logging.LogRecord
* @since JavaMail 1.4.5
*/
@SuppressWarnings("unchecked")
static Comparator super LogRecord> newComparator(String name) throws Exception {
return newObjectFrom(name, Comparator.class);
}
/**
* Returns a comparator that imposes the reverse ordering of the specified
* {@link Comparator}. If the given comparator declares a public
* reverseOrder method that method is called first and the return value is
* used. If that method is not declared or the caller does not have access
* then a comparator wrapping the given comparator is returned.
*
* @param the element type to be compared
* @param c a comparator whose ordering is to be reversed by the returned
* comparator
* @return A comparator that imposes the reverse ordering of the specified
* comparator.
* @throws NullPointerException if the given comparator is null.
* @since JavaMail 1.5.0
*/
@SuppressWarnings({"unchecked", "ThrowableResultIgnored"})
static Comparator reverseOrder(final Comparator c) {
Objects.requireNonNull(c);
Comparator reverse = null;
//Comparator in JDK8 has 'reversed' as a default method.
//This code calls that method first to allow custom
//code to define what reverse order means in versions older than JDK8.
try {
//assert Modifier.isPublic(c.getClass().getModifiers()) :
// Modifier.toString(c.getClass().getModifiers());
final Method m = c.getClass().getMethod("reversed");
if (!Modifier.isStatic(m.getModifiers())
&& Comparator.class.isAssignableFrom(m.getReturnType())) {
try {
reverse = (Comparator) m.invoke(c);
} catch (final ExceptionInInitializerError eiie) {
throw wrapOrThrow(eiie);
}
}
} catch (final NoSuchMethodException | RuntimeException | IllegalAccessException ignore) {
} catch (final InvocationTargetException ite) {
paramOrError(ite); //Ignore invocation bugs (returned values).
}
if (reverse == null) {
reverse = Collections.reverseOrder(c);
}
return reverse;
}
/**
* Creates a new error manager from the given class name.
*
* @param name the fully qualified class name.
* @return a new error manager.
* @throws ClassCastException if class name does not match the type.
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws InstantiationException if the given class name is abstract.
* @throws InvocationTargetException if the constructor throws an exception.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws NoSuchMethodException if the class name does not have a no
* argument constructor.
* @since JavaMail 1.4.5
*/
static ErrorManager newErrorManager(String name) throws Exception {
return newObjectFrom(name, ErrorManager.class);
}
/**
* Determines if the given class name identifies a utility class.
*
* @param name the fully qualified class name.
* @return true if the given class name
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws SecurityException if unable to inspect properties of class.
* @since JavaMail 1.5.2
*/
static boolean isStaticUtilityClass(String name) throws Exception {
final Class> c = findClass(name);
final Class> obj = Object.class;
Method[] methods;
boolean util;
if (c != obj && (methods = c.getMethods()).length != 0) {
util = true;
for (Method m : methods) {
if (m.getDeclaringClass() != obj
&& !Modifier.isStatic(m.getModifiers())) {
util = false;
break;
}
}
} else {
util = false;
}
return util;
}
/**
* Determines if the given class name is a reflection class name responsible
* for invoking methods and or constructors.
*
* @param name the fully qualified class name.
* @return true if the given class name
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws SecurityException if unable to inspect properties of class.
* @since JavaMail 1.5.2
*/
static boolean isReflectionClass(String name) throws Exception {
String[] names = REFLECT_NAMES;
if (names == null) { //Benign data race.
REFLECT_NAMES = names = reflectionClassNames();
}
for (String rf : names) { //The set of names is small.
if (name.equals(rf)) {
return true;
}
}
findClass(name); //Fail late instead of normal return.
return false;
}
/**
* Determines all of the reflection class names used to invoke methods.
*
* This method performs indirect and direct calls on a throwable to capture
* the standard class names and the implementation class names.
*
* @return a string array containing the fully qualified class names.
* @throws Exception if there is a problem.
*/
private static String[] reflectionClassNames() throws Exception {
final Class> thisClass = LogManagerProperties.class;
assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
try {
//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
final HashSet traces = new HashSet<>();
Throwable t = Throwable.class.getConstructor().newInstance();
for (StackTraceElement ste : t.getStackTrace()) {
if (!thisClass.getName().equals(ste.getClassName())) {
traces.add(ste.getClassName());
} else {
break;
}
}
//This code must use reflection to capture extra frames.
//The invoke API doesn't produce the frames needed.
Throwable.class.getMethod("fillInStackTrace").invoke(t);
for (StackTraceElement ste : t.getStackTrace()) {
if (!thisClass.getName().equals(ste.getClassName())) {
traces.add(ste.getClassName());
} else {
break;
}
}
return traces.toArray(new String[0]);
} catch (final InvocationTargetException ITE) {
throw paramOrError(ITE);
}
}
/**
* Creates a new object from the given class name.
*
* @param The generic class type.
* @param name the fully qualified class name.
* @param type the assignable type for the given name.
* @return a new object assignable to the given type.
* @throws ClassCastException if class name does not match the type.
* @throws ClassNotFoundException if the class name was not found.
* @throws IllegalAccessException if the constructor is inaccessible.
* @throws InstantiationException if the given class name is abstract.
* @throws InvocationTargetException if the constructor throws an exception.
* @throws LinkageError if the linkage fails.
* @throws ExceptionInInitializerError if the static initializer fails.
* @throws Exception to match the error method of the ErrorManager.
* @throws NoSuchMethodException if the class name does not have a no
* argument constructor.
* @since JavaMail 1.4.5
*/
static T newObjectFrom(String name, Class type) throws Exception {
try {
final Class> clazz = LogManagerProperties.findClass(name);
//This check avoids additional side effects when the name parameter
//is a literal name and not a class name.
if (type.isAssignableFrom(clazz)) {
try {
return type.cast(clazz.getConstructor().newInstance());
} catch (final InvocationTargetException ITE) {
throw paramOrError(ITE);
}
} else {
throw new ClassCastException(clazz.getName()
+ " cannot be cast to " + type.getName());
}
} catch (final NoClassDefFoundError NCDFE) {
//No class def found can occur on filesystems that are
//case insensitive (BUG ID 6196068). In some cases, we allow class
//names or literal names, this code guards against the case where a
//literal name happens to match a class name in a different case.
//This is also a nice way to adapt this error for the error manager.
throw new ClassNotFoundException(NCDFE.toString(), NCDFE);
} catch (final ExceptionInInitializerError EIIE) {
throw wrapOrThrow(EIIE);
}
}
/**
* Returns the given exception or throws the escaping cause.
*
* @param ite any invocation target.
* @return the exception.
* @throws VirtualMachineError if present as cause.
* @since JavaMail 1.4.5
*/
private static Exception paramOrError(InvocationTargetException ite) {
final Throwable cause = ite.getCause();
if (cause instanceof VirtualMachineError) {
throw (Error) cause;
}
return ite;
}
/**
* Throws the given error if the cause is an error otherwise the given error
* is wrapped.
*
* @param eiie the error.
* @return an InvocationTargetException.
* @since JavaMail 1.5.0
*/
private static InvocationTargetException wrapOrThrow(
ExceptionInInitializerError eiie) {
//This linkage error will escape the constructor new instance call.
//If the cause is an error, rethrow to skip any error manager.
if (eiie.getCause() instanceof Error) {
throw eiie;
} else {
//Considered a bug in the code, wrap the error so it can be
//reported to the error manager.
return new InvocationTargetException(eiie);
}
}
/**
* This code is modified from the LogManager, which explicitly states
* searching the system class loader first, then the context class loader.
* There is resistance (compatibility) to change this behavior to simply
* searching the context class loader. See JDK-6878454.
*
* @param name full class name
* @return the class.
* @throws LinkageError if the linkage fails.
* @throws ClassNotFoundException if the class name was not found.
* @throws ExceptionInInitializerError if static initializer fails.
*/
private static Class> findClass(String name) throws ClassNotFoundException {
ClassLoader[] loaders = getClassLoaders();
assert loaders.length == 2 : loaders.length;
Class> clazz;
if (loaders[0] != null) {
try {
clazz = Class.forName(name, false, loaders[0]);
} catch (ClassNotFoundException tryContext) {
clazz = tryLoad(name, loaders[1]);
}
} else {
clazz = tryLoad(name, loaders[1]);
}
return clazz;
}
/**
* Loads a class using the given loader or the class loader of this class.
*
* @param name the class name.
* @param l any class loader or null.
* @return the raw class.
* @throws ClassNotFoundException if not found.
*/
private static Class> tryLoad(String name, ClassLoader l) throws ClassNotFoundException {
if (l != null) {
return Class.forName(name, false, l);
} else {
return Class.forName(name);
}
}
/**
* Gets the class loaders using elevated privileges.
*
* @return any array of class loaders. Indexes may be null.
*/
private static ClassLoader[] getClassLoaders() {
return runOrDoPrivileged(new PrivilegedAction() {
@SuppressWarnings("override") //JDK-6954234
public ClassLoader[] run() {
final ClassLoader[] loaders = new ClassLoader[2];
try {
loaders[0] = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignore) {
loaders[0] = null;
}
try {
loaders[1] = Thread.currentThread().getContextClassLoader();
} catch (SecurityException ignore) {
loaders[1] = null;
}
return loaders;
}
});
}
/**
* Executes a PrivilegedAction without permissions then falling back to
* running with elevated permissions.
*
* Any unchecked exceptions from the action are passed through this API.
*
* @param the action return type.
* @param a the PrivilegedAction object.
* @return the result.
* @throws NullPointerException if the given action is null.
* @throws UndeclaredThrowableException if a checked exception is thrown.
* @since Angus Mail 2.0.3
*/
static T runOrDoPrivileged(final PrivilegedAction a) {
Objects.requireNonNull(a);
try {
return a.run();
} catch (SecurityException sandbox) {
return invokeAccessController(a);
}
}
/**
* Reflective call to access controller for sandbox environments.
* Any unchecked exceptions from the action are passed through this API.
*
* @param the return type of the action.
* @param a a non-null action.
* @return the result.
* @throws UnsupportedOperationException if not allowed.
* @throws UndeclaredThrowableException if a checked exception is thrown.
* @since Angus Mail 2.0.3
*/
@SuppressWarnings("unchecked")
private static T invokeAccessController(final PrivilegedAction a) {
assert a != null;
try {
Class> c = Class.forName("java.security.AccessController");
return (T) c.getMethod("doPrivileged", PrivilegedAction.class)
.invoke((Object) null, a);
} catch (ReflectiveOperationException roe) {
Throwable cause = roe.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
throw new UndeclaredThrowableException(roe);
}
}
}
/**
* The namespace prefix to search LogManager and defaults.
*/
private final String prefix;
/**
* Creates a log manager properties object.
*
* @param parent the parent properties.
* @param prefix the namespace prefix.
* @throws NullPointerException if prefix
or
* parent
is null
.
*/
LogManagerProperties(final Properties parent, final String prefix) {
super(parent);
if (parent == null || prefix == null) {
throw new NullPointerException();
}
this.prefix = prefix;
}
/**
* Returns a properties object that contains a snapshot of the current
* state. This method violates the clone contract so that no instances of
* LogManagerProperties is exported for public use.
*
* @return the snapshot.
* @since JavaMail 1.4.4
*/
@Override
@SuppressWarnings("CloneDoesntCallSuperClone")
public synchronized Object clone() {
return exportCopy(defaults);
}
/**
* Searches defaults, then searches the log manager if available or the
* system properties by the prefix property, and then by the key itself.
*
* @param key a non null key.
* @return the value for that key.
*/
@Override
public synchronized String getProperty(final String key) {
String value = defaults.getProperty(key);
if (value == null) {
if (key.length() > 0) {
value = fromLogManager(prefix + '.' + key);
}
if (value == null) {
value = fromLogManager(key);
}
/**
* Copy the log manager properties as we read them. If a value is no
* longer present in the LogManager read it from here. The reason
* this works is because LogManager.reset() closes all attached
* handlers therefore, stale values only exist in closed handlers.
*/
if (value != null) {
super.put(key, value);
} else {
Object v = super.get(key); //defaults are not used.
value = v instanceof String ? (String) v : null;
}
}
return value;
}
/**
* Calls getProperty directly. If getProperty returns null the default value
* is returned.
*
* @param key a key to search for.
* @param def the default value to use if not found.
* @return the value for the key.
* @since JavaMail 1.4.4
*/
@Override
public String getProperty(final String key, final String def) {
final String value = this.getProperty(key);
return value == null ? def : value;
}
/**
* Required to work with PropUtil. Calls getProperty directly if the given
* key is a string. Otherwise, performs a get operation on the defaults
* followed by the normal hash table get.
*
* @param key any key.
* @return the value for the key or null.
* @since JavaMail 1.4.5
*/
@Override
public synchronized Object get(final Object key) {
Object value;
if (key instanceof String) {
value = getProperty((String) key);
} else {
value = null;
}
//Search for non-string value.
if (value == null) {
value = defaults.get(key);
if (value == null && !defaults.containsKey(key)) {
value = super.get(key);
}
}
return value;
}
/**
* Required to work with PropUtil. An updated copy of the key is fetched
* from the log manager if the key doesn't exist in this properties.
*
* @param key any key.
* @return the value for the key or the default value for the key.
* @since JavaMail 1.4.5
*/
@Override
public synchronized Object put(final Object key, final Object value) {
if (key instanceof String && value instanceof String) {
final Object def = preWrite(key);
final Object man = super.put(key, value);
return man == null ? def : man;
} else {
return super.put(key, value);
}
}
/**
* Calls the put method directly.
*
* @param key any key.
* @return the value for the key or the default value for the key.
* @since JavaMail 1.4.5
*/
@Override
public Object setProperty(String key, String value) {
return this.put(key, value);
}
/**
* Required to work with PropUtil. An updated copy of the key is fetched
* from the log manager prior to returning.
*
* @param key any key.
* @return the value for the key or null.
* @since JavaMail 1.4.5
*/
@Override
public synchronized boolean containsKey(final Object key) {
boolean found = key instanceof String
&& getProperty((String) key) != null;
if (!found) {
found = defaults.containsKey(key) || super.containsKey(key);
}
return found;
}
/**
* Required to work with PropUtil. An updated copy of the key is fetched
* from the log manager if the key doesn't exist in this properties.
*
* @param key any key.
* @return the value for the key or the default value for the key.
* @since JavaMail 1.4.5
*/
@Override
public synchronized Object remove(final Object key) {
final Object def = preWrite(key);
final Object man = super.remove(key);
return man == null ? def : man;
}
/**
* It is assumed that this method will never be called. No way to get the
* property names from LogManager.
*
* @return the property names
*/
@Override
public Enumeration> propertyNames() {
assert false;
return super.propertyNames();
}
/**
* It is assumed that this method will never be called. The prefix value is
* not used for the equals method.
*
* @param o any object or null.
* @return true if equal, otherwise false.
*/
@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (o instanceof Properties == false) {
return false;
}
assert false : prefix;
return super.equals(o);
}
/**
* It is assumed that this method will never be called. See equals.
*
* @return the hash code.
*/
@Override
public int hashCode() {
assert false : prefix.hashCode();
return super.hashCode();
}
/**
* Called before a write operation of a key. Caches a key read from the log
* manager in this properties object. The key is only cached if it is an
* instance of a String and this properties doesn't contain a copy of the
* key.
*
* @param key the key to search.
* @return the default value for the key.
*/
private Object preWrite(final Object key) {
assert Thread.holdsLock(this);
return get(key);
}
/**
* Creates a public snapshot of this properties object using the given
* parent properties.
*
* @param parent the defaults to use with the snapshot.
* @return the safe snapshot.
*/
private Properties exportCopy(final Properties parent) {
Thread.holdsLock(this);
final Properties child = new Properties(parent);
child.putAll(this);
return child;
}
/**
* It is assumed that this method will never be called. We return a safe
* copy for export to avoid locking this properties object or the defaults
* during write.
*
* @return the parent properties.
* @throws ObjectStreamException if there is a problem.
*/
private synchronized Object writeReplace() throws ObjectStreamException {
assert false;
return exportCopy((Properties) defaults.clone());
}
}