/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.util.java;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.io.IOUtils;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.util.logging.PwmLogger;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
public class JavaHelper {
private static final PwmLogger LOGGER = PwmLogger.forClass(JavaHelper.class);
private JavaHelper() {
}
/**
* Convert a byte[] array to readable string format. This makes the "hex" readable
*
* @param in byte[] buffer to convert to string format
* @return result String buffer in String format
*/
public static String byteArrayToHexString(final byte[] in) {
final String[] pseudo = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
if (in == null || in.length <= 0) {
return "";
}
final StringBuilder out = new StringBuilder(in.length * 2);
for (final byte b : in) {
byte ch = (byte) (b & 0xF0); // strip off high nibble
ch = (byte) (ch >>> 4); // shift the bits down
ch = (byte) (ch & 0x0F); // must do this is high order bit is on!
out.append(pseudo[(int) ch]); // convert the nibble to a String Character
ch = (byte) (b & 0x0F); // strip off low nibble
out.append(pseudo[(int) ch]); // convert the nibble to a String Character
}
return out.toString();
}
/**
* Pause the calling thread the specified amount of time.
*
* @param sleepTimeMS - a time duration in milliseconds
* @return time actually spent sleeping
*/
public static long pause(final long sleepTimeMS) {
final long startTime = System.currentTimeMillis();
do {
try {
final long sleepTime = sleepTimeMS - (System.currentTimeMillis() - startTime);
Thread.sleep(sleepTime > 0 ? sleepTime : 5);
} catch (InterruptedException e) {
//who cares
}
} while ((System.currentTimeMillis() - startTime) < sleepTimeMS);
return System.currentTimeMillis() - startTime;
}
public static long pause(
final long sleepTimeMS,
final long predicateCheckIntervalMS,
final Predicate predicate
) {
final long startTime = System.currentTimeMillis();
final long pauseTime = Math.max(sleepTimeMS, predicateCheckIntervalMS);
while ((System.currentTimeMillis() - startTime) < sleepTimeMS) {
JavaHelper.pause(pauseTime);
if (predicate.test(null)) {
break;
}
}
return System.currentTimeMillis() - startTime;
}
public static String binaryArrayToHex(final byte[] buf) {
final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
final char[] chars = new char[2 * buf.length];
for (int i = 0; i < buf.length; ++i) {
chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
}
return new String(chars);
}
public static Instant nextZuluZeroTime() {
final Calendar nextZuluMidnight = GregorianCalendar.getInstance(TimeZone.getTimeZone("Zulu"));
nextZuluMidnight.set(Calendar.HOUR_OF_DAY,0);
nextZuluMidnight.set(Calendar.MINUTE,0);
nextZuluMidnight.set(Calendar.SECOND, 0);
nextZuluMidnight.add(Calendar.HOUR, 24);
return nextZuluMidnight.getTime().toInstant();
}
public static <E extends Enum<E>> List<E> readEnumListFromStringCollection(final Class<E> enumClass, final Collection<String> inputs ) {
final List<E> returnList = new ArrayList<E>();
for (final String input : inputs) {
final E item = readEnumFromString(enumClass, null, input);
if (item != null) {
returnList.add(item);
}
}
return Collections.unmodifiableList(returnList);
}
public static <E extends Enum<E>> E readEnumFromString(final Class<E> enumClass, final E defaultValue, final String input) {
if (StringUtil.isEmpty(input)) {
return defaultValue;
}
if (enumClass == null || !enumClass.isEnum()) {
return defaultValue;
}
try {
return Enum.valueOf(enumClass, input);
} catch (IllegalArgumentException e) {
/* noop */
//LOGGER.trace("input=" + input + " does not exist in enumClass=" + enumClass.getSimpleName());
} catch (Throwable e) {
LOGGER.warn("unexpected error translating input=" + input + " to enumClass=" + enumClass.getSimpleName() + ", error: " + e.getMessage());
}
return defaultValue;
}
public static String throwableToString(final Throwable throwable) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
pw.flush();
return sw.toString();
}
/**
* Converts an exception to a string message. Handles cases where the message in the exception is null
* and/or there are multiple nested cause exceptions.
* @param e The exception to convert to a string
* @return A string containing any meaningful extractable cause information, suitable for debugging.
*/
public static String readHostileExceptionMessage(final Throwable e) {
String errorMsg = e.getClass().getName();
if (e.getMessage() != null) {
errorMsg += ": " + e.getMessage();
}
Throwable cause = e.getCause();
int safetyCounter = 0;
while (cause != null && safetyCounter < 10) {
safetyCounter++;
errorMsg += ", cause:" + cause.getClass().getName();
if (cause.getMessage() != null) {
errorMsg += ": " + cause.getMessage();
}
cause = cause.getCause();
}
return errorMsg;
}
public static <E extends Enum<E>> boolean enumArrayContainsValue(final E[] enumArray, final E enumValue) {
return !(enumArray == null || enumArray.length == 0) && Arrays.asList(enumArray).contains(enumValue);
}
public static void unhandledSwitchStatement(final Object switchParameter) {
final String className = switchParameter == null
? "unknown - see stack trace"
: switchParameter.getClass().getName();
final String paramValue = switchParameter == null
? "unknown"
: switchParameter.toString();
final String errorMsg = "unhandled switch statement on parameter class=" + className + ", value=" + paramValue;
final UnsupportedOperationException exception = new UnsupportedOperationException(errorMsg);
LOGGER.warn(errorMsg, exception);
throw exception;
}
public static long copyWhilePredicate(final InputStream input, final OutputStream output, final Predicate predicate)
throws IOException
{
final int bufferSize = 4 * 1024;
final byte[] buffer = new byte[bufferSize];
long bytesCopied;
long totalCopied = 0;
do {
bytesCopied = IOUtils.copyLarge(input, output, 0 , bufferSize, buffer);
if (bytesCopied > 0) {
totalCopied += bytesCopied;
}
if (!predicate.test(null)) {
return totalCopied;
}
} while (bytesCopied > 0);
return totalCopied;
}
public static String toIsoDate(final Instant instant) {
return instant == null ? "" : instant.toString();
}
public static String toIsoDate(final Date date) {
if (date == null) {
return "";
}
final DateFormat dateFormat = new SimpleDateFormat(
PwmConstants.DEFAULT_DATETIME_FORMAT_STR,
PwmConstants.DEFAULT_LOCALE
);
dateFormat.setTimeZone(PwmConstants.DEFAULT_TIMEZONE);
return dateFormat.format(date);
}
public static Instant parseIsoToInstant(final String input) {
return Instant.parse(input);
}
public static boolean closeAndWaitExecutor(final ExecutorService executor, final TimeDuration timeDuration)
{
if (executor == null) {
return true;
}
executor.shutdown();
try {
return executor.awaitTermination(timeDuration.getTotalMilliseconds(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOGGER.warn("unexpected error shutting down executor service " + executor.getClass().toString() + " error: " + e.getMessage());
}
return false;
}
public static String makeThreadName(final PwmApplication pwmApplication, final Class theClass) {
String instanceName = "-";
if (pwmApplication != null && pwmApplication.getInstanceID() != null) {
instanceName = pwmApplication.getInstanceID();
}
return PwmConstants.PWM_APP_NAME + "-" + instanceName + "-" + theClass.getSimpleName();
}
public static Properties newSortedProperties() {
return new Properties() {
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
};
}
public static ThreadFactory makePwmThreadFactory(final String namePrefix, final boolean daemon) {
return new ThreadFactory() {
private final ThreadFactory realThreadFactory = Executors.defaultThreadFactory();
@Override
public Thread newThread(final Runnable r) {
final Thread t = realThreadFactory.newThread(r);
t.setDaemon(daemon);
if (namePrefix != null) {
final String newName = namePrefix + t.getName();
t.setName(newName);
}
return t;
}
};
}
public static Collection<Method> getAllMethodsForClass(final Class clazz) {
final LinkedHashSet<Method> methods = new LinkedHashSet<>();
// add local methods;
methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
final Class superClass = clazz.getSuperclass();
if (superClass != null) {
methods.addAll(getAllMethodsForClass(superClass));
}
return Collections.unmodifiableSet(methods);
}
public static CSVPrinter makeCsvPrinter(final OutputStream outputStream)
throws IOException
{
return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
}
public static ExecutorService makeSingleThreadExecutorService(
final PwmApplication pwmApplication,
final Class clazz
)
{
return Executors.newSingleThreadScheduledExecutor(
makePwmThreadFactory(
JavaHelper.makeThreadName(pwmApplication,clazz) + "-",
true
));
}
/**
* Copy of {@link ThreadInfo#toString()} but with the MAX_FRAMES changed from 8 to 256.
*/
public static String threadInfoToString(final ThreadInfo threadInfo) {
final int MAX_FRAMES = 256;
final StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" +
" Id=" + threadInfo.getThreadId() + " " +
threadInfo.getThreadState());
if (threadInfo.getLockName() != null) {
sb.append(" on " + threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by \"" + threadInfo.getLockOwnerName() +
"\" Id=" + threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
int counter = 0;
for (; counter < threadInfo.getStackTrace().length && counter < MAX_FRAMES; counter++) {
final StackTraceElement ste = threadInfo.getStackTrace()[counter];
sb.append("\tat ").append(ste.toString());
sb.append('\n');
if (counter == 0 && threadInfo.getLockInfo() != null) {
final Thread.State ts = threadInfo.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on " + threadInfo.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on " + threadInfo.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on " + threadInfo.getLockInfo());
sb.append('\n');
break;
default:
}
}
for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
if (mi.getLockedStackDepth() == counter) {
sb.append("\t- locked " + mi);
sb.append('\n');
}
}
}
if (counter < threadInfo.getStackTrace().length) {
sb.append("\t...");
sb.append('\n');
}
final LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = " + locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- " + li);
sb.append('\n');
}
}
sb.append('\n');
return sb.toString();
}
}