Skip to content

Commit

Permalink
Support for UUID encoded in Base64
Browse files Browse the repository at this point in the history
  • Loading branch information
grzegorz-aniol authored and JMHReif committed Apr 21, 2022
1 parent a963e6c commit d08182d
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 6 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/apoc/ApocConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class ApocConfig extends LifecycleAdapter {
public static final String APOC_TRIGGER_ENABLED = "apoc.trigger.enabled";
public static final String APOC_UUID_ENABLED = "apoc.uuid.enabled";
public static final String APOC_UUID_ENABLED_DB = "apoc.uuid.enabled.%s";
public static final String APOC_UUID_FORMAT = "apoc.uuid.format";
public enum UuidFormatType { hex, base64 }
public static final String APOC_JSON_ZIP_URL = "apoc.json.zip.url"; // TODO: check if really needed
public static final String APOC_JSON_SIMPLE_JSON_URL = "apoc.json.simpleJson.url"; // TODO: check if really needed
public static final String APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM = "apoc.import.file.allow_read_from_filesystem";
Expand Down Expand Up @@ -350,6 +352,16 @@ public boolean getBoolean(String key, boolean defaultValue) {
return getConfig().getBoolean(key, defaultValue);
}

public <T extends Enum<T>> T getEnumProperty(String key, Class<T> cls, T defaultValue) {
var value = getConfig().getString(key, defaultValue.toString()).trim();
try {
return T.valueOf(cls, value);
} catch (IllegalArgumentException e) {
log.error("Wrong value '{}' for parameter '{}' is provided. Default value is used: '{}'", value, key, defaultValue);
return defaultValue;
}
}

public boolean isImportFolderConfigured() {
// in case we're test database import path is TestDatabaseManagementServiceBuilder.EPHEMERAL_PATH

Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/apoc/create/Create.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import apoc.get.Get;
import apoc.result.*;
import apoc.util.Util;
import apoc.uuid.UuidUtil;
import org.neo4j.graphdb.*;
import org.neo4j.internal.helpers.collection.Iterables;
import org.neo4j.procedure.*;
Expand Down Expand Up @@ -264,6 +265,24 @@ public String uuid() {
return UUID.randomUUID().toString();
}

@UserFunction
@Description("apoc.create.uuidBase64() - create an UUID encoded with Base64")
public String uuidBase64() {
return UuidUtil.generateBase64Uuid(UUID.randomUUID());
}

@UserFunction
@Description("apoc.create.uuidBase64ToHex() - convert between an UUID encoded with Base64 to HEX format")
public String uuidBase64ToHex(@Name("base64Uuid") String base64Uuid) {
return UuidUtil.fromBase64ToHex(base64Uuid);
}

@UserFunction
@Description("apoc.create.uuidHexToBase64() - convert an UUID in HEX format to encoded with Base64")
public String uuidHexToBase64(@Name("uuidHex") String uuidHex) {
return UuidUtil.fromHexToBase64(uuidHex);
}

private Object toPropertyValue(Object value) {
if (value instanceof Iterable) {
Iterable it = (Iterable) value;
Expand Down
61 changes: 61 additions & 0 deletions core/src/main/java/apoc/uuid/UuidUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package apoc.uuid;

import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.UUID;

public class UuidUtil {

public static String fromHexToBase64(String hexUuid) {
var uuid = UUID.fromString(hexUuid);
return generateBase64Uuid(uuid);
}

public static String fromBase64ToHex(String base64Uuid) {
if (base64Uuid == null) {
throw new NullPointerException();
}
if (base64Uuid.isBlank()) {
throw new IllegalStateException("Expected not empty UUID value");
}

String valueForConversion;
// check if Base64 text ends with '==' already (Base64 alignment)
if (base64Uuid.endsWith("==")) {
if (base64Uuid.length() != 24) {
throw new IllegalStateException("Invalid UUID length. Expected 24 characters");
}
valueForConversion = base64Uuid;
} else {
if (base64Uuid.length() != 22) {
throw new IllegalStateException("Invalid UUID length. Expected 22 characters");
}
valueForConversion = base64Uuid + "==";
}

var buffer = Base64.getDecoder().decode(valueForConversion);

// Generate UUID from 16 byte buffer
long msb = 0L;
for (int i=0; i < 8; ++i) {
msb <<= 8;
msb |= (buffer[i] & 0xFF);
}
var lsb = 0L;
for (int i=8; i < 16; ++i) {
lsb <<= 8;
lsb |= (buffer[i] & 0xFF);
}

var uuid = new UUID(msb, lsb);
return uuid.toString();
}

public static String generateBase64Uuid(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
var encoded = Base64.getEncoder().encodeToString(bb.array());
return encoded.substring(0, encoded.length() - 2); // skip '==' alignment
}
}
74 changes: 74 additions & 0 deletions core/src/test/java/apoc/uuid/UuidUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package apoc.uuid;

import org.junit.Test;

import java.util.UUID;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;

public class UuidUtilTest {

@Test
public void fromHexToBase64() {
var input = "290d6cba-ce94-455e-b59f-029cf1e395c5";
var output = UuidUtil.fromHexToBase64(input);
assertThat(output).isEqualTo("KQ1sus6URV61nwKc8eOVxQ");
}

@Test
public void fromBase64ToHex() {
var input = "KQ1sus6URV61nwKc8eOVxQ";
var output = UuidUtil.fromBase64ToHex(input);
assertThat(output).isEqualTo("290d6cba-ce94-455e-b59f-029cf1e395c5");
}

@Test
public void fromBase64WithAlignmentToHex() {
var input = "KQ1sus6URV61nwKc8eOVxQ==";
var output = UuidUtil.fromBase64ToHex(input);
assertThat(output).isEqualTo("290d6cba-ce94-455e-b59f-029cf1e395c5");
}

@Test
public void shouldFailIfHexFormatIsWrong() {
var input = "290d6cba-455e-b59f-029cf1e395c5";
assertThatCode(() -> UuidUtil.fromHexToBase64(input)).hasMessageStartingWith("Invalid UUID string");
}

@Test
public void shouldFailIfHexFormatIsEmpty() {
var input = "";
assertThatCode(() -> UuidUtil.fromHexToBase64(input)).hasMessageStartingWith("Invalid UUID string");
}

@Test
public void shouldFailIfHexFormatIsNull() {
assertThatCode(() -> UuidUtil.fromHexToBase64(null)).isInstanceOf(NullPointerException.class);
}

@Test
public void shouldFailIfBase64LengthIsWrong() {
var input1 = "KQ1sus6URV61nwKc8eO=="; // wrong length
assertThatCode(() -> UuidUtil.fromBase64ToHex(input1)).hasMessageStartingWith("Invalid UUID length. Expected 24 characters");
var input2 = "Q1sus6URV61nwKc8eOVxQ"; // wrong length
assertThatCode(() -> UuidUtil.fromBase64ToHex(input2)).hasMessageStartingWith("Invalid UUID length. Expected 22 characters");
}

@Test
public void shouldFailIfBase64IsEmpty() {
assertThatCode(() -> UuidUtil.fromBase64ToHex("")).hasMessageStartingWith("Expected not empty UUID value");
}

@Test
public void shouldFailIfBase64IsNull() {
assertThatCode(() -> UuidUtil.fromBase64ToHex(null)).isInstanceOf(NullPointerException.class);
}

@Test
public void generateBase64ForSpecificUUIDs() {
var uuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
var uuidBase64 = UuidUtil.generateBase64Uuid(uuid);
assertThat(uuidBase64).isEqualTo("AAAAAAAAAAAAAAAAAAAAAA");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.create/apoc.create.uuidBase64.adoc[apoc.create.uuidBase64 icon:book[]] +

- create an UUID encoded with Base64
¦label:function[]
¦label:apoc-core[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.create/apoc.create.uuidBase64ToHex.adoc[apoc.create.uuidBase64ToHex icon:book[]] +

- convert between an UUID encoded with Base64 to HEX format
¦label:function[]
¦label:apoc-core[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.create/apoc.create.uuidHexToBase64.adoc[apoc.create.uuidHexToBase64 icon:book[]] +

- convert an UUID in HEX format to encoded with Base64
¦label:function[]
¦label:apoc-core[]
16 changes: 13 additions & 3 deletions docs/asciidoc/modules/ROOT/pages/graph-updates/uuid.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ The library supports manual and automation generation of UUIDs, which can be sto

UUIDs are generated using the https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html#randomUUID()[java randomUUID utility method], which generates a https://www.ietf.org/rfc/rfc4122.txt[v4UUID].

UUID may be encoded into String with well-known hexadecimal presentation (32 characters, e.g. `1051af4f-b81d-4a76-8605-ecfb8ef703d5`) or Base64 (22 characters, e.g. `vX8dM5XoSe2ldoc/QzMEyw`)

[[manual-uuids]]
== Manual UUIDs

[separator=¦,opts=header,cols="5,1m,1m"]
|===
¦Qualified Name¦Type¦Release
include::example$generated-documentation/apoc.create.uuid.adoc[]
include::example$generated-documentation/apoc.create.uuidBase64.adoc[]
include::example$generated-documentation/apoc.create.uuidBase64ToHex.adoc[]
include::example$generated-documentation/apoc.create.uuidHexToBase64.adoc[]
|===



.The following generates a UUID
=== Usage Examples
The following generates a UUID
[source,cypher]
----
RETURN apoc.create.uuid() AS uuid;
Expand All @@ -32,7 +37,7 @@ RETURN apoc.create.uuid() AS uuid;
| "1051af4f-b81d-4a76-8605-ecfb8ef703d5"
|===

.The following creates a `Person` node, using a UUID as the merging key:
The following creates a `Person` node, using a UUID as the merging key:

[source, cypher]
----
Expand All @@ -48,6 +53,9 @@ RETURN p
| {"firstName":"Michael","surname":"Hunger","id":"5530953d-b85e-4939-b37f-a79d54b770a3"}
|===

include::partial$usage/apoc.create.uuidBase64.adoc[]
include::partial$usage/apoc.create.uuidHexToBase64.adoc[]
include::partial$usage/apoc.create.uuidBase64ToHex.adoc[]

[[automatic-uuids]]
== Automatic UUIDs
Expand All @@ -58,6 +66,8 @@ Please check the following documentation to an in-depth description.

Enable `apoc.uuid.enabled=true` or `apoc.uuid.enabled.[DATABASE_NAME]=true` in `$NEO4J_HOME/config/apoc.conf` first.

Configuration value `apoc.uuid.format` let you choose between different UUID encoding methods: `hex` (default option) or `base64`.

[separator=¦,opts=header,cols="5,1m,1m"]
|===
¦Qualified Name¦Type¦Release
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
////
This file is generated by DocsTest, so don't change it!
////

= apoc.create.uuidBase64
:description: This section contains reference documentation for the apoc.create.uuidBase64 function.

label:function[] label:apoc-core[]

[.emphasis]
apoc.create.uuidBase64() - create an UUID encoded with Base64

== Signature

[source]
----
apoc.create.uuidBase64() :: (STRING?)
----

[[usage-apoc.create.uuidBase64]]
== Usage Examples
include::partial$usage/apoc.create.uuidBase64.adoc[]

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
////
This file is generated by DocsTest, so don't change it!
////

= apoc.create.uuidBase64ToHex
:description: This section contains reference documentation for the apoc.create.uuidBase64ToHex function.

label:function[] label:apoc-core[]

[.emphasis]
apoc.create.uuidBase64ToHex() - convert between an UUID encoded with Base64 to HEX format

== Signature

[source]
----
apoc.create.uuidBase64ToHex(base64Uuid :: STRING?) :: (STRING?)
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|base64Uuid|STRING?|null
|===

[[usage-apoc.create.uuidBase64ToHex]]
== Usage Examples
include::partial$usage/apoc.create.uuidBase64ToHex.adoc[]

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
////
This file is generated by DocsTest, so don't change it!
////

= apoc.create.uuidHexToBase64
:description: This section contains reference documentation for the apoc.create.uuidHexToBase64 function.

label:function[] label:apoc-core[]

[.emphasis]
apoc.create.uuidHexToBase64() - convert an UUID in HEX format to encoded with Base64

== Signature

[source]
----
apoc.create.uuidHexToBase64(uuidHex :: STRING?) :: (STRING?)
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|uuidHex|STRING?|null
|===

[[usage-apoc.create.uuidHexToBase64]]
== Usage Examples
include::partial$usage/apoc.create.uuidHexToBase64.adoc[]

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
The following generates a new UUID encoded with Base64:

[source,cypher]
----
RETURN apoc.create.uuidBase64() as output;
----

.Results
[opts="header",cols="1"]
|===
| Output
| "vX8dM5XoSe2ldoc/QzMEyw"
|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
The following converts an UUID encoded with Base64 to HEX representation:

[source,cypher]
----
RETURN apoc.create.uuidBase64ToHex("vX8dM5XoSe2ldoc/QzMEyw") as output;
----

.Results
[opts="header",cols="1"]
|===
| Output
| "bd7f1d33-95e8-49ed-a576-873f433304cb"
|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
The following converts an UUID encoded with Base64 to HEX representation:

[source,cypher]
----
RETURN apoc.create.uuidHexToBase64("bd7f1d33-95e8-49ed-a576-873f433304cb") as output;
----

.Results
[opts="header",cols="1"]
|===
| Output
| "vX8dM5XoSe2ldoc/QzMEyw"
|===
Loading

0 comments on commit d08182d

Please sign in to comment.