Skip to content

Commit

Permalink
NIFI-12379 Refactored nifi-uuid5 using standard classes
Browse files Browse the repository at this point in the history
- Replaced commons-codec Hex with Java HexFormat
- Replaced commons-codec DigestUtils with Java MessageDigest

Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com>

This closes #8035.
  • Loading branch information
exceptionfactory authored and pvillard31 committed Nov 16, 2023
1 parent e1942a8 commit 026222d
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 43 deletions.
4 changes: 4 additions & 0 deletions nifi-commons/nifi-record-path/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
Expand Down
12 changes: 0 additions & 12 deletions nifi-commons/nifi-uuid5/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,4 @@
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-uuid5</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,73 @@
package org.apache.nifi.uuid5;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HexFormat;
import java.util.Objects;
import java.util.UUID;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;

// This is based on an unreleased implementation in Apache Commons Id.
// See http://svn.apache.org/repos/asf/commons/sandbox/id/trunk/src/java/org/apache/commons/id/uuid/UUID.java
/**
* UUID Version 5 generator based on RFC 4122 Section 4.3 with SHA-1 hash algorithm for names
*/
public class Uuid5Util {
public static String fromString(String value, String namespace) {
final UUID nsUUID = namespace == null ? new UUID(0, 0) : UUID.fromString(namespace);
final byte[] nsBytes =
ByteBuffer.wrap(new byte[16])
.putLong(nsUUID.getMostSignificantBits())
.putLong(nsUUID.getLeastSignificantBits())
.array();
private static final String NAME_DIGEST_ALGORITHM = "SHA-1";

private static final int NAMESPACE_LENGTH = 16;

private static final int ENCODED_LENGTH = 32;

private static final char SEPARATOR = '-';

final byte[] subjectBytes = value.getBytes();
final byte[] nameBytes = ArrayUtils.addAll(nsBytes, subjectBytes);
final byte[] nameHash = DigestUtils.sha1(nameBytes);
final byte[] uuidBytes = Arrays.copyOf(nameHash, 16);
/**
* Generate UUID Version 5 based on SHA-1 hash of name with optional UUID namespace
*
* @param name Name for deterministic UUID generation with SHA-1 hash
* @param namespace Optional namespace defaults to 0 when not provided
* @return UUID string consisting of 36 lowercase characters
*/
public static String fromString(final String name, final String namespace) {
Objects.requireNonNull(name, "Name required");
final UUID namespaceId = namespace == null ? new UUID(0, 0) : UUID.fromString(namespace);

uuidBytes[6] &= 0x0F;
uuidBytes[6] |= 0x50;
uuidBytes[8] &= 0x3f;
uuidBytes[8] |= 0x80;
final byte[] subject = name.getBytes();
final int nameLength = NAMESPACE_LENGTH + subject.length;
final ByteBuffer nameBuffer = ByteBuffer.allocate(nameLength);
nameBuffer.putLong(namespaceId.getMostSignificantBits());
nameBuffer.putLong(namespaceId.getLeastSignificantBits());
nameBuffer.put(subject);

final String encoded = Hex.encodeHexString(Objects.requireNonNull(uuidBytes));
final StringBuffer sb = new StringBuffer(encoded);
final byte[] nameHash = getNameHash(nameBuffer.array());
final byte[] nameUuid = Arrays.copyOf(nameHash, NAMESPACE_LENGTH);

while (sb.length() != 32) {
sb.insert(0, "0");
nameUuid[6] &= 0x0F;
nameUuid[6] |= 0x50;
nameUuid[8] &= 0x3f;
nameUuid[8] |= (byte) 0x80;

final String encoded = HexFormat.of().formatHex(nameUuid);
final StringBuilder builder = new StringBuilder(encoded);

while (builder.length() != ENCODED_LENGTH) {
builder.insert(0, "0");
}

sb.ensureCapacity(32);
sb.insert(8, '-');
sb.insert(13, '-');
sb.insert(18, '-');
sb.insert(23, '-');
builder.ensureCapacity(ENCODED_LENGTH);
builder.insert(8, SEPARATOR);
builder.insert(13, SEPARATOR);
builder.insert(18, SEPARATOR);
builder.insert(23, SEPARATOR);

return sb.toString();
return builder.toString();
}

private static byte[] getNameHash(final byte[] name) {
try {
final MessageDigest messageDigest = MessageDigest.getInstance(NAME_DIGEST_ALGORITHM);
return messageDigest.digest(name);
} catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException("UUID Name Digest Algorithm not found", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 org.apache.nifi.uuid5;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class Uuid5UtilTest {
private static final String NAMESPACE = "11111111-1111-1111-1111-111111111111";

private static final String NAME = "identifier";

private static final String NAME_UUID_NULL_NAMESPACE = "485f3c34-2c6c-50a3-9904-63233467b96a";

private static final String NAME_UUID_NAMESPACE = "c9b54096-3890-53cc-b375-af7e7ec5e59f";

@Test
void testFromStringNullNamespace() {
final String uuid = Uuid5Util.fromString(NAME, null);

assertEquals(NAME_UUID_NULL_NAMESPACE, uuid);
}

@Test
void testFromStringNamespace() {
final String uuid = Uuid5Util.fromString(NAME, NAMESPACE);

assertEquals(NAME_UUID_NAMESPACE, uuid);
}
}

0 comments on commit 026222d

Please sign in to comment.