/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.util; import org.candlepin.model.CuratorException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.collections.Closure; import org.apache.commons.collections.ClosureUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.UUID; /** * Genuinely random utilities. */ public class Util { /** * */ public static final String UTC_STR = "UTC"; private static Logger log = LoggerFactory.getLogger(Util.class); private static ObjectMapper mapper = new ObjectMapper(); /** * Invokes the close() method of any given object, if present. */ private static Closure closeInvoker = ClosureUtils.invokerClosure("close"); private Util() { // default ctor } /** * Generates a random UUID. * * @return a random UUID. */ public static String generateUUID() { return UUID.randomUUID().toString(); } /** * Generates a 32-character UUID to use with object creation/migration. * <p></p> * The UUID is generated by creating a "standard" UUID and removing the hyphens. The UUID may be * standardized by reinserting the hyphens later, if necessary. * * @return * a 32-character UUID */ public static String generateDbUUID() { return generateUUID().replace("-", ""); } public static <T> List<T> subList(List<T> parentList, int start, int end) { List<T> l = new ArrayList<T>(); for (int i = start; i < end; i++) { l.add(parentList.get(i)); } return l; } public static <T> List<T> subList(List<T> parentList, int size) { return subList(parentList, 0, size - 1); } public static <E> List<E> newList() { return new ArrayList<E>(); } public static <K, V> Map<K, V> newMap() { return new HashMap<K, V>(); } public static <T> Set<T> newSet() { return new HashSet<T>(); } public static <T> Set<T> asSet(T... values) { Set<T> output = new HashSet<T>(values.length); for (T value : values) { output.add(value); } return output; } public static Date getFutureDate(int years) { Calendar future = Calendar.getInstance(); future.setTime(new Date()); future.set(Calendar.YEAR, future.get(Calendar.YEAR) + years); return future.getTime(); } public static Date tomorrow() { return addDaysToDt(1); } public static Date yesterday() { return addDaysToDt(-1); } public static Date addDaysToDt(int dayField) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, dayField); return calendar.getTime(); } public static Date addMinutesToDt(int minuteField) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, minuteField); return calendar.getTime(); } public static Date hoursAgo(int hours) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.HOUR, hours * -1); return calendar.getTime(); } public static Date addToFields(int day, int month, int yr) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, day); calendar.add(Calendar.MONTH, month); calendar.add(Calendar.YEAR, yr); return calendar.getTime(); } public static Date roundToMidnight(Date dt) { Calendar cal = Calendar.getInstance(); cal.setTime(dt); cal.set(Calendar.MINUTE, 59); cal.set(Calendar.SECOND, 59); cal.set(Calendar.HOUR_OF_DAY, 23); return cal.getTime(); } public static BigInteger toBigInt(long l) { return new BigInteger(String.valueOf(l)); } public static Date toDate(String dt) { SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yyyy"); try { return fmt.parse(dt); } catch (ParseException e) { throw new RuntimeException(e); } } public static <T> T assertNotNull(T value, String message) { if (value == null) { throw new IllegalArgumentException(message); } return value; } public static String defaultIfEmpty(String str, String def) { if (str == null || str.trim().length() == 0) { return def; } return str; } public static boolean equals(String str, String str1) { if (str == str1) { return true; } if ((str == null) ^ (str1 == null)) { return false; } return str.equals(str1); } /** * Invokes the close() method on the given closable object, and logs that * it is closing "msg". If closable is null, the function simply returns. * * For example, if msg = AMQPSession, the logs will show something like * this: INFO Going to close: AMQPSession * * @param closable Object with a close() method. * @param msg indicates what the closable is and used to log informational * messages. */ public static void closeSafely(Object closable, String msg) { if (closable == null) { return; } try { log.info("Going to close: " + msg); closeInvoker.execute(closable); } catch (Exception e) { log.warn(msg + ".close() was not successful!", e); } } public static String capitalize(String str) { char [] chars = str.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } public static long generateUniqueLong() { /* This deserves explanation. A random positive Long has 63 bits of hash space. We want to have a given amount of certainty about the probability of collisions within this space. This is an instance of the Birthday Problem[1]. We can get the probability that any two random numbers collide with the approximation: 1-e**((-(N**2))/(2H)) Where e is Euler's number, N is the number of random numbers generated, and H is the number of possible random outcomes. Suppose then that we generated one billion serials, with each serial being a 63-bit positive Long. Then our probability of having a collision would be: irb(main):001:0> 1-Math.exp((-(1000000000.0**2))/(2.0*(2**63))) => 0.052766936243662 So, if we generated a *billion* such serials, there is only a 5% chance that any two of them would be the same. In other words, there is 95% chance that we would not have a single collision in one billion entries. The chances obviously get even less likely with smaller numbers. With one million, the probability of a collision is: irb(main):002:0> 1-Math.exp((-(1000000.0**2))/(2.0*(2**63))) => 5.42101071809853e-08 Or, 1 in 18,446,744. [1] http://en.wikipedia.org/wiki/Birthday_problem */ return Math.abs(new SecureRandom().nextLong()); } public static String toBase64(byte [] data) { try { // to be thread-safe, we should create it from the static method // If we don't specify the line separator, it will use CRLF return new String(new Base64(64, "\n".getBytes()).encode(data), "ASCII"); } catch (UnsupportedEncodingException e) { log.warn("Unable to convert binary data to string", e); return new String(data); } } public static SimpleDateFormat getUTCDateFormat() { SimpleDateFormat iso8601DateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'"); iso8601DateFormat.setTimeZone(TimeZone.getTimeZone(UTC_STR)); return iso8601DateFormat; } public static String readFile(InputStream is) { InputStreamReader isr = new InputStreamReader(is); BufferedReader reader = new BufferedReader(isr); StringBuilder builder = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { builder.append(line + "\n"); } } catch (IOException e) { throw new CuratorException(e); } finally { try { reader.close(); } catch (IOException e) { log.warn("problem closing BufferedReader", e); } } return builder.toString(); } public static String hash(String password) { //This is secure because even if the salt is known, a cracker //would still need to generate their own rainbow table, which //is the same as brute-forcing the password in the first place. String salt = "b669e3274a43f20769d3dedf03e9ac180e160f92"; String combined = salt + password; MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException nsae) { throw new RuntimeException(nsae); } try { md.update(combined.getBytes("UTF-8"), 0, combined.length()); } catch (UnsupportedEncodingException uee) { throw new RuntimeException(uee); } byte[] sha1hash = md.digest(); return new String(Hex.encodeHex(sha1hash)); } public static String toJson(Object anObject) { String output = ""; try { output = mapper.writeValueAsString(anObject); } catch (Exception e) { log.error("Could no serialize the object to json " + anObject, e); } return output; } @SuppressWarnings({ "rawtypes", "unchecked" }) public static Object fromJson(String json, Class clazz) { Object output = null; try { output = mapper.readValue(json, clazz); } catch (Exception e) { log.error("Could no de-serialize the following json " + json, e); } return output; } @SuppressWarnings("rawtypes") public static String getClassName(Class c) { return getClassName(c.getName()); } public static String getClassName(String fullClassName) { int firstChar = fullClassName.lastIndexOf('.') + 1; if (firstChar > 0) { fullClassName = fullClassName.substring(firstChar); } return fullClassName; } public static String reverseEndian(String in) { in = (in.length() % 2 != 0) ? "0" + in : in; StringBuilder sb = new StringBuilder(); for (int i = in.length() - 2; i >= 0; i += (i % 2 == 0) ? 1 : -3) { sb.append(in.charAt(i)); } return sb.toString(); } public static String transformUuid(String uuid) { String[] partitions = uuid.split("-"); List<String> newPartitions = new LinkedList<String>(); // We only want to revese the first three partitions for (int i = 0; i < partitions.length; i++) { newPartitions.add(i < 3 ? reverseEndian(partitions[i]) : partitions[i]); } return StringUtils.join(newPartitions, '-'); } /* * Gets possible guest uuids regardless of endianness. When given a non-uuid, * this should return a list of length 1, with the given value. All values * returned should be lower case */ public static List<String> getPossibleUuids(String... ids) { List<String> results = new LinkedList<String>(); for (String id : ids) { if (id != null) { // We want to use lower case everywhere we can in order // to do less work at query time. id = id.toLowerCase(); } results.add(id); if (isUuid(id)) { results.add(transformUuid(id)); } } return results; } private static final String UUID_REGEX = "[a-fA-F0-9]{8}-" + "[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"; public static boolean isUuid(String uuid) { return uuid != null && uuid.matches(UUID_REGEX); } public static String collectionToString(Collection c) { StringBuffer buf = new StringBuffer(); for (Object o : c) { buf.append(o.toString()); buf.append(" "); } return buf.toString(); } /** * Compares two collections for equality without using the collection's equals method. This is * primarily only useful when working with collections that may actually be Hibernate bags, as * bags and proxies do not properly implement the equals method, which tends to lead to * incorrect results and unnecessary work. * <p></p> * WARNING: This method will not work with collections which use iterators that return its * elements in an inconsistent order. The order does not need to be known, but it must be * consistent and repeatable for a given collection state. * * @param c1 * A collection to compare to c2 * * @param c2 * A collection to compare to c1 * * @return * true if both collections are the same instance, are both null or contain the same elements; * false otherwise */ public static <T> boolean collectionsAreEqual(Collection<T> c1, Collection<T> c2) { if (c1 == c2) { return true; } if (c1 == null || c2 == null || c1.size() != c2.size()) { return false; } Set<Integer> indexes = new HashSet<Integer>(); for (T lhs : c1) { boolean found = false; int offset = -1; for (T rhs : c2) { if (indexes.contains(++offset)) { continue; } if (lhs == rhs || (lhs != null && lhs.equals(rhs))) { indexes.add(offset); found = true; break; } } if (!found) { return false; } } return true; } /** * Compares two collections for equality without using the collection's equals method. This is * primarily only useful when working with collections that may actually be Hibernate bags, as * bags and proxies do not properly implement the equals method, which tends to lead to * incorrect results and unnecessary work. * <p></p> * WARNING: This method will not work with collections which use iterators that return its * elements in an inconsistent order. The order does not need to be known, but it must be * consistent and repeatable for a given collection state. * * @param c1 * A collection to compare to c2 * * @param c2 * A collection to compare to c1 * * @param comp * A comparator to use to compare elements of c1 and c2 for equality * * @throws IllegalArgumentException * if the provided compator is null * * @return * true if both collections are the same instance, are both null or contain the same elements; * false otherwise */ public static <T> boolean collectionsAreEqual(Collection<T> c1, Collection<T> c2, Comparator<T> comp) { if (comp == null) { throw new IllegalArgumentException("comp is null"); } if (c1 == c2) { return true; } if (c1 == null || c2 == null || c1.size() != c2.size()) { return false; } Set<Integer> indexes = new HashSet<Integer>(); for (T lhs : c1) { boolean found = false; int offset = -1; for (T rhs : c2) { if (indexes.contains(++offset)) { continue; } if (lhs == rhs || (lhs != null && comp.compare(lhs, rhs) == 0)) { indexes.add(offset); found = true; break; } } if (!found) { return false; } } return true; } }