Skip to content

Commit

Permalink
fix trigger id calculation for complex args: heterogeneous lists, lis…
Browse files Browse the repository at this point in the history
…t of maps (#882)

* fix trigger id calculation for complex args: heterogeneous lists, list of maps
  • Loading branch information
brig authored Mar 9, 2024
1 parent 008ae5a commit e56be84
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,78 +21,65 @@
*/

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public final class TriggerInternalIdCalculator {

@SuppressWarnings("UnstableApiUsage")
public static String getId(String name, List<String> activeProfiles, Map<String, Object> arguments, Map<String, Object> conditions, Map<String, Object> cfg) {
HashFunction hf = Hashing.sha256();
return hf.newHasher()
.putString(name, StandardCharsets.UTF_8)
.putUnencodedChars(name)
.putObject(ensureList(activeProfiles), (from, into) -> from.stream().sorted().forEach(p -> into.putString(p, StandardCharsets.UTF_8)))
.putBytes(serialize(toSortedMap(arguments)))
.putBytes(serialize(toSortedMap(conditions)))
.putBytes(serialize(toSortedMap(cfg)))
.putUnencodedChars(objectToString(arguments))
.putUnencodedChars(objectToString(conditions))
.putUnencodedChars(objectToString(cfg))
.hash()
.toString();
}

@SuppressWarnings("unchecked")
private static Object toSorted(Object o) {
private static String objectToString(Object o) {
if (o == null) {
return "";
}

if (o instanceof Collection) {
return toSortedCollection((Collection<Object>) o);
return hashCollection((Collection<Object>) o);
} else if (o instanceof Map) {
return toSortedMap((Map<String, Object>) o);
return hashMap((Map<String, Object>) o);
}
return o;
}

private static Collection<Object> toSortedCollection(Collection<Object> collection) {
if (collection == null) {
return Collections.emptyList();
}
return o.toString();
}

List<Object> result = new ArrayList<>();
collection.stream().sorted().forEach(c -> result.add(toSorted(c)));
return result;
@SuppressWarnings("UnstableApiUsage")
private static String hashCollection(Collection<Object> collection) {
Hasher hasher = Hashing.sha256().newHasher();
collection.stream()
.map(TriggerInternalIdCalculator::objectToString)
.sorted()
.forEach(hasher::putUnencodedChars);
return hasher.hash().toString();
}

// use LinkedHashMap instead of Map, because LinkedHashMap is serializable
private static LinkedHashMap<String, Object> toSortedMap(Map<String, Object> map) {
if (map == null) {
return new LinkedHashMap<>();
}
@SuppressWarnings("UnstableApiUsage")
private static String hashMap(Map<String, Object> map) {
Hasher hasher = Hashing.sha256().newHasher();

LinkedHashMap<String, Object> result = new LinkedHashMap<>();
map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> result.put(e.getKey(), toSorted(e.getValue())));
.forEach(e -> hasher.putUnencodedChars(e.getKey())
.putUnencodedChars(objectToString(e.getValue())));

return result;
}

private static byte[] serialize(LinkedHashMap<String, Object> map) {
if (map == null) {
return new byte[0];
}

try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos)) {

out.writeObject(map);
out.flush();

return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
return hasher.hash().toString();
}

private static <E> List<E> ensureList(List<E> list) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@
import java.util.*;

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

public class TriggerInternalIdCalculatorTest {

@Test
public void testEmpty() {
String name = "trigger";
List<String> activeProfiles = new ArrayList<>();
Map<String, Object> arguments = new HashMap<>();
Map<String, Object> conditions = new HashMap<>();
Map<String, Object> cfg = new HashMap<>();
List<String> activeProfiles = List.of();
Map<String, Object> arguments = Map.of();
Map<String, Object> conditions = Map.of();
Map<String, Object> cfg = Map.of();

String id1 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, cfg);
String id2 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, cfg);
Expand All @@ -44,15 +45,16 @@ public void testEmpty() {
@Test
public void test1() {
String name = "trigger";
List<String> activeProfiles = new ArrayList<>();
Map<String, Object> arguments = new HashMap<>();
Map<String, Object> conditions = new HashMap<>();

Map<String, Object> cfg1 = new HashMap<>();
cfg1.put("a", "a-value");
cfg1.put("b", Collections.singletonMap("k", "v"));
cfg1.put("c", "c-value");
Map<String, Object> cfg2 = new HashMap<>();
List<String> activeProfiles = List.of();
Map<String, Object> arguments = Map.of();
Map<String, Object> conditions = Map.of();

Map<String, Object> cfg1 = Map.of(
"a", "a-value",
"b", Collections.singletonMap("k", "v"),
"c", "c-value");

Map<String, Object> cfg2 = new LinkedHashMap<>();
cfg2.put("c", "c-value");
cfg2.put("a", "a-value");
cfg2.put("b", Collections.singletonMap("k", "v"));
Expand All @@ -61,4 +63,46 @@ public void test1() {
String id2 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, cfg2);
assertEquals(id1, id2);
}

@Test
public void testArrayOfObjects() {
String name = "trigger";
List<String> activeProfiles = List.of();

Map<String, Object> arguments = Map.of(
"listOfMaps", List.of(Map.of("name", "one"), Map.of("name", "two")),
"anotherProblem", Arrays.asList("a", 2, false));

Map<String, Object> conditions = Map.of();

Map<String, Object> configuration = Map.of(
"name", "MyTrigger",
"entryPoint", "default");

String id1 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, configuration);
String id2 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, configuration);
assertEquals(id1, id2);
}

@Test
public void testNotEquals() {
String name = "trigger";
List<String> activeProfiles = List.of();
Map<String, Object> arguments = Map.of();
Map<String, Object> conditions = Map.of();

Map<String, Object> cfg1 = Map.of(
"a", "a-value",
"b", Map.of("k", "v"),
"c", "c-value");

Map<String, Object> cfg2 = Map.of(
"c", "c-value",
"a", "a-value",
"boom", Map.of("k", "v"));

String id1 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, cfg1);
String id2 = TriggerInternalIdCalculator.getId(name, activeProfiles, arguments, conditions, cfg2);
assertNotEquals(id1, id2);
}
}

0 comments on commit e56be84

Please sign in to comment.