/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.util; import alluxio.security.group.CachedGroupMapping; import alluxio.security.group.GroupMappingService; import alluxio.util.ShellUtils.ExitCodeException; import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.io.Closer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Random; import java.util.StringTokenizer; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.ThreadSafe; /** * Common utilities shared by all components in Alluxio. */ @ThreadSafe public final class CommonUtils { private static final Logger LOG = LoggerFactory.getLogger(CommonUtils.class); private static final String ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final Random RANDOM = new Random(); /** * @return current time in milliseconds */ public static long getCurrentMs() { return System.currentTimeMillis(); } /** * Converts a list of objects to a string. * * @param list list of objects * @param <T> type of the objects * @return space-separated concatenation of the string representation returned by Object#toString * of the individual objects */ public static <T> String listToString(List<T> list) { StringBuilder sb = new StringBuilder(); for (T s : list) { if (sb.length() != 0) { sb.append(" "); } sb.append(s); } return sb.toString(); } /** * Converts varargs of objects to a string. * * @param separator separator string * @param args variable arguments * @param <T> type of the objects * @return concatenation of the string representation returned by Object#toString * of the individual objects */ public static <T> String argsToString(String separator, T... args) { StringBuilder sb = new StringBuilder(); for (T s : args) { if (sb.length() != 0) { sb.append(separator); } sb.append(s); } return sb.toString(); } /** * Parses {@code ArrayList<String>} into {@code String[]}. * * @param src is the ArrayList of strings * @return an array of strings */ public static String[] toStringArray(ArrayList<String> src) { String[] ret = new String[src.size()]; return src.toArray(ret); } /** * Generates a random alphanumeric string of the given length. * * @param length the length * @return a random string */ public static String randomAlphaNumString(int length) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { sb.append(ALPHANUM.charAt(RANDOM.nextInt(ALPHANUM.length()))); } return sb.toString(); } /** * Generates a random byte array of the given length. * * @param length the length * @return a random byte array */ public static byte[] randomBytes(int length) { byte[] result = new byte[length]; RANDOM.nextBytes(result); return result; } /** * Sleeps for the given number of milliseconds. * * @param timeMs sleep duration in milliseconds */ public static void sleepMs(long timeMs) { sleepMs(null, timeMs); } /** * Sleeps for the given number of milliseconds, reporting interruptions using the given logger. * * Unlike Thread.sleep(), this method responds to interrupts by setting the thread interrupt * status. This means that callers must check the interrupt status if they need to handle * interrupts. * * @param logger logger for reporting interruptions; no reporting is done if the logger is null * @param timeMs sleep duration in milliseconds */ public static void sleepMs(Logger logger, long timeMs) { try { Thread.sleep(timeMs); } catch (InterruptedException e) { if (logger != null) { logger.warn(e.getMessage(), e); } Thread.currentThread().interrupt(); } } /** * Common empty loop utility that serves the purpose of warming up the JVM before performance * microbenchmarks. */ public static void warmUpLoop() { for (int k = 0; k < 10000000; k++) {} } /** * Creates new instance of a class by calling a constructor that receives ctorClassArgs arguments. * * @param <T> type of the object * @param cls the class to create * @param ctorClassArgs parameters type list of the constructor to initiate, if null default * constructor will be called * @param ctorArgs the arguments to pass the constructor * @return new class object or null if not successful * @throws InstantiationException if the instantiation fails * @throws IllegalAccessException if the constructor cannot be accessed * @throws NoSuchMethodException if the constructor does not exist * @throws SecurityException if security violation has occurred * @throws InvocationTargetException if the constructor invocation results in an exception */ public static <T> T createNewClassInstance(Class<T> cls, Class<?>[] ctorClassArgs, Object[] ctorArgs) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, InvocationTargetException { if (ctorClassArgs == null) { return cls.newInstance(); } Constructor<T> ctor = cls.getConstructor(ctorClassArgs); return ctor.newInstance(ctorArgs); } /** * Gets the current user's group list from Unix by running the command 'groups' NOTE. For * non-existing user it will return EMPTY list. This method may return duplicate groups. * * @param user user name * @return the groups list that the {@code user} belongs to. The primary group is returned first */ public static List<String> getUnixGroups(String user) throws IOException { String result; List<String> groups = new ArrayList<>(); try { result = ShellUtils.execCommand(ShellUtils.getGroupsForUserCommand(user)); } catch (ExitCodeException e) { // if we didn't get the group - just return empty list LOG.warn("got exception trying to get groups for user " + user + ": " + e.getMessage()); return groups; } StringTokenizer tokenizer = new StringTokenizer(result, ShellUtils.TOKEN_SEPARATOR_REGEX); while (tokenizer.hasMoreTokens()) { groups.add(tokenizer.nextToken()); } return groups; } /** * Waits for a condition to be satisfied. * * @param description a description of what causes condition to evaluation to true * @param condition the condition to wait on */ public static void waitFor(String description, Function<Void, Boolean> condition) { waitFor(description, condition, WaitForOptions.defaults()); } /** * Waits for a condition to be satisfied. * * @param description a description of what causes condition to evaluation to true * @param condition the condition to wait on * @param options the options to use */ public static void waitFor(String description, Function<Void, Boolean> condition, WaitForOptions options) { long start = System.currentTimeMillis(); int interval = options.getInterval(); int timeout = options.getTimeout(); while (!condition.apply(null)) { if (timeout != WaitForOptions.NEVER && System.currentTimeMillis() - start > timeout) { throw new RuntimeException("Timed out waiting for " + description); } CommonUtils.sleepMs(interval); } } /** * Waits for an operation to return a non-null value with a specified timeout. * * @param description the description of this operation * @param operation the operation * @param options the options to use * @param <T> the type of the return value * @return the return value, null if it times out */ public static <T> T waitForResult(String description, Function<Void, T> operation, WaitForOptions options) { T t; long start = System.currentTimeMillis(); int interval = options.getInterval(); int timeout = options.getTimeout(); while ((t = operation.apply(null)) == null) { if (timeout != WaitForOptions.NEVER && System.currentTimeMillis() - start > timeout) { throw new RuntimeException("Timed out waiting for " + description); } CommonUtils.sleepMs(interval); } return t; } /** * Gets the primary group name of a user. * * @param userName Alluxio user name * @return primary group name */ public static String getPrimaryGroupName(String userName) throws IOException { List<String> groups = getGroups(userName); return (groups != null && groups.size() > 0) ? groups.get(0) : ""; } /** * Using {@link CachedGroupMapping} to get the group list of a user. * * @param userName Alluxio user name * @return the group list of the user */ public static List<String> getGroups(String userName) throws IOException { GroupMappingService groupMappingService = GroupMappingService.Factory.get(); return groupMappingService.getGroups(userName); } /** * Strips the suffix if it exists. This method will leave keys without a suffix unaltered. * * @param key the key to strip the suffix from * @param suffix suffix to remove * @return the key with the suffix removed, or the key unaltered if the suffix is not present */ public static String stripSuffixIfPresent(final String key, final String suffix) { if (key.endsWith(suffix)) { return key.substring(0, key.length() - suffix.length()); } return key; } /** * Strips the prefix from the key if it is present. For example, for input key * ufs://my-bucket-name/my-key/file and prefix ufs://my-bucket-name/, the output would be * my-key/file. This method will leave keys without a prefix unaltered, ie. my-key/file * returns my-key/file. * * @param key the key to strip * @param prefix prefix to remove * @return the key without the prefix */ public static String stripPrefixIfPresent(final String key, final String prefix) { if (key.startsWith(prefix)) { return key.substring(prefix.length()); } return key; } /** * Strips the leading and trailing quotes from the given string. * E.g. return 'alluxio' for input '"alluxio"'. * * @param str The string to strip * @return The string without the leading and trailing quotes */ public static String stripLeadingAndTrailingQuotes(String str) { int length = str.length(); if (length > 1 && str.startsWith("\"") && str.endsWith("\"")) { str = str.substring(1, length - 1); } return str; } /** * Gets the value with a given key from a static key/value mapping in string format. E.g. with * mapping "id1=user1;id2=user2", it returns "user1" with key "id1". It returns null if the given * key does not exist in the mapping. * * @param mapping the "key=value" mapping in string format separated by ";" * @param key the key to query * @return the mapped value if the key exists, otherwise returns "" */ public static String getValueFromStaticMapping(String mapping, String key) { Map<String, String> m = Splitter.on(";") .omitEmptyStrings() .trimResults() .withKeyValueSeparator("=") .split(mapping); return m.get(key); } /** * Gets the root cause of an exception. * * @param e the exception * @return the root cause */ public static Throwable getRootCause(Throwable e) { while (e.getCause() != null) { e = e.getCause(); } return e; } /** * Casts a {@link Throwable} to an {@link IOException}. * * @param e the throwable * @return the IO exception */ public static IOException castToIOException(Throwable e) { if (e instanceof IOException) { return (IOException) e; } else { return new IOException(e); } } /** * Returns an iterator that iterates on a single element. * * @param element the element * @param <T> the type of the element * @return the iterator */ public static <T> Iterator<T> singleElementIterator(final T element) { return new Iterator<T>() { private boolean mHasNext = true; @Override public boolean hasNext() { return mHasNext; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } mHasNext = false; return element; } @Override public void remove() { throw new UnsupportedOperationException("remove is not supported."); } }; } /** * Executes the given callables, waiting for them to complete (or time out). * @param callables the callables to execute * @param <T> the return type of the callables */ public static <T> void invokeAll(List<Callable<T>> callables) { ExecutorService service = Executors.newCachedThreadPool(); try { service.invokeAll(callables, 10, TimeUnit.SECONDS); service.shutdown(); if (!service.awaitTermination(10, TimeUnit.SECONDS)) { throw new RuntimeException("Timed out trying to shutdown service."); } } catch (InterruptedException e) { service.shutdownNow(); throw new RuntimeException(e); } } /** * Closes the Closer and re-throws the Throwable. Any exceptions thrown while closing the Closer * will be added as suppressed exceptions to the Throwable. This method always throws the given * Throwable, wrapping it in a RuntimeException if it's a non-IOException checked exception. * * Note: This method will wrap non-IOExceptions in RuntimeException. Do not use this method in * methods which throw non-IOExceptions. * * <pre> * Closer closer = new Closer(); * try { * Closeable c = closer.register(new Closeable()); * } catch (Throwable t) { * throw closeAndRethrow(closer, t); * } * </pre> * * @param closer the Closer to close * @param t the Throwable to re-throw * @return this method never returns */ public static RuntimeException closeAndRethrow(Closer closer, Throwable t) throws IOException { try { throw closer.rethrow(t); } finally { closer.close(); } } private CommonUtils() {} // prevent instantiation }