/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gobblin.util.guid; import lombok.EqualsAndHashCode; import java.io.IOException; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.ArrayUtils; import com.google.common.base.Charsets; /** * Class wrapping a byte array representing a guid. A {@link Guid} is intended to uniquely identify objects in a * replicable way. */ @EqualsAndHashCode public class Guid { public static final int GUID_LENGTH = 20; final byte[] sha; /** * Creates a {@link Guid} by computing the sha of the input bytes. */ public Guid(byte[] bytes) { this(bytes, false); } /** * @param bytes byte array. * @param isSha true if bytes are already a sha. */ private Guid(byte[] bytes, boolean isSha) { if (isSha) { this.sha = bytes; } else { this.sha = computeGuid(bytes); } } /** * Generate a {@link Guid} for an array of {@link HasGuid}. * @param objs array of {@link HasGuid}. * @return a single {@link Guid} for the array. * @throws IOException */ public static Guid fromHasGuid(HasGuid... objs) throws IOException { byte[][] byteArrays = new byte[objs.length][]; for (int i = 0; i < objs.length; i++) { byteArrays[i] = objs[i].guid().sha; } return fromByteArrays(byteArrays); } /** * Generate a {@link Guid} for an array of Strings. * @param strings array of Strings. * @return a single {@link Guid} for the array. * @throws IOException */ public static Guid fromStrings(String... strings) throws IOException { if (strings == null || strings.length == 0) { throw new IOException("Attempting to compute guid for an empty array."); } return new Guid(StringUtils.join(strings).getBytes(Charsets.UTF_8)); } /** * Generate a {@link Guid} for an array of byte arrays. * @param byteArrays array of byte arrays. * @return a single {@link Guid} for the array. * @throws IOException */ public static Guid fromByteArrays(byte[]... byteArrays) throws IOException { if (byteArrays == null || byteArrays.length == 0) { throw new IOException("Attempting to compute guid for an empty array."); } if (byteArrays.length == 1) { return new Guid(byteArrays[0]); } byte[] tmp = new byte[0]; for (byte[] arr : byteArrays) { tmp = ArrayUtils.addAll(tmp, arr); } return new Guid(tmp); } /** * Reverse of {@link #toString}. Deserializes a {@link Guid} from a previously serialized one. * @param str Serialized {@link Guid}. * @return deserialized {@link Guid}. * @throws IOException */ public static Guid deserialize(String str) throws IOException { if (str.length() != 2 * GUID_LENGTH) { throw new IOException("String is not an encoded guid."); } try { return new Guid(Hex.decodeHex(str.toCharArray()), true); } catch (DecoderException de) { throw new IOException(de); } } /** * Combine multiple {@link Guid}s into a single {@link Guid}. * @throws IOException */ public static Guid combine(Guid... guids) throws IOException { byte[][] byteArrays = new byte[guids.length][]; for (int i = 0; i < guids.length; i++) { byteArrays[i] = guids[i].sha; } return fromByteArrays(byteArrays); } /** * Creates a new {@link Guid} which is a unique, replicable representation of the pair (this, byteArrays). * @param byteArrays an array of byte arrays. * @return a new {@link Guid}. * @throws IOException */ public Guid append(byte[]... byteArrays) throws IOException { if (byteArrays == null || byteArrays.length == 0) { return this; } return fromByteArrays(ArrayUtils.add(byteArrays, this.sha)); } /** * Creates a new {@link Guid} which is a unique, replicable representation of the pair (this, guids). Equivalent to * combine(this, guid1, guid2, ...) * @param guids an array of {@link Guid}. * @return a new {@link Guid}. * @throws IOException */ public Guid append(Guid... guids) throws IOException { if (guids == null || guids.length == 0) { return this; } return combine(ArrayUtils.add(guids, this)); } /** * Creates a new {@link Guid} which is a unique, replicable representation of the pair (this, objs). * @param objs an array of {@link HasGuid}. * @return a new {@link Guid}. * @throws IOException */ public Guid append(HasGuid... objs) throws IOException { if (objs == null || objs.length == 0) { return this; } return fromHasGuid(ArrayUtils.add(objs, new SimpleHasGuid(this))); } /** * Serializes the guid into a hex string. The original {@link Guid} can be recovered using {@link #deserialize}. */ @Override public String toString() { return Hex.encodeHexString(this.sha); } // DigestUtils.sha is deprecated for sha1, but sha1 is not available in old versions of commons codec @SuppressWarnings("deprecation") private static byte[] computeGuid(byte[] bytes) { return DigestUtils.sha(bytes); } static class SimpleHasGuid implements HasGuid { private final Guid guid; public SimpleHasGuid(Guid guid) { this.guid = guid; } @Override public Guid guid() throws IOException { return this.guid; } } }