Skip to content

Commit

Permalink
Merge pull request #431 from adobe/staging
Browse files Browse the repository at this point in the history
  • Loading branch information
praveek authored Apr 14, 2023
2 parents 773e7ef + c42c689 commit c8743a6
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ package com.adobe.marketing.mobile.internal

internal object CoreConstants {
const val LOG_TAG = "MobileCore"
const val VERSION = "2.1.1"
const val VERSION = "2.1.2"

object EventDataKeys {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ internal fun Map<String, Any?>.fnv1a32(masks: Array<String>? = null): Long {
if (innerMasks?.isEmpty() == true) innerMasks = null
innerMasks?.let {
it.sortedArray().forEach { mask ->
if (mask.isNotEmpty() && flattenedMap.containsKey(mask)) {
if (mask.isNotEmpty() && !flattenedMap[mask].isNullOrEmptyString()) {
kvPairs.append(mask).append(":").append(flattenedMap[mask].toString())
}
}
} ?: run {
flattenedMap.toSortedMap().forEach { entry ->
kvPairs.append(entry.key).append(":").append(entry.value.toString())
if (!entry.value.isNullOrEmptyString()) {
kvPairs.append(entry.key).append(":").append(entry.value.toString())
}
}
}
return StringEncoder.convertStringToDecimalHash(kvPairs.toString())
Expand Down Expand Up @@ -147,3 +149,10 @@ private fun join(elements: Iterable<*>, delimiter: String?): String {
}
return sBuilder.toString()
}

/**
* Returns a [Boolean] containing true if the value is null or an empty [String], false otherwise.
*
* @return [Boolean] true if the value is null or an empty [String], false otherwise
*/
private fun Any?.isNullOrEmptyString(): Boolean = this == null || (this is String && this.isEmpty())
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

private const val LOG_TAG = "historicalEventsQuerying"
private const val SEARCH_TYPE_ANY = "any"
private const val SEARCH_TYPE_ORDERED = "ordered"
private const val ASYNC_TIMEOUT = 1000L

@JvmSynthetic
Expand All @@ -32,7 +32,7 @@ internal fun historicalEventsQuerying(
var eventCounts = 0
extensionApi.getHistoricalEvents(
requests.toTypedArray(),
searchType == SEARCH_TYPE_ANY
searchType == SEARCH_TYPE_ORDERED
) {
latch.countDown()
eventCounts = it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp
*/
private fun getValueFromEvent(key: String): Any? {
if (event.eventData == null) {
Log.debug(
LaunchRulesEngineConstants.LOG_TAG,
LOG_TAG,
"Triggering event ${event.uniqueIdentifier} - Event data is null, unable to replace the token $key"
)
return EMPTY_STRING
}
val eventDataMap = event.eventData.flattening()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

package com.adobe.marketing.mobile.rulesengine;

import java.util.ArrayList;
import java.util.List;

public class LogicalExpression implements Evaluable {
Expand All @@ -26,52 +25,50 @@ public LogicalExpression(final List<Evaluable> operands, final String operationN

@Override
public RulesResult evaluate(final Context context) {
ArrayList<RulesResult> resolvedOperands = new ArrayList<>();

if (operationName == null || operationName.isEmpty()) {
return new RulesResult(
RulesResult.FailureType.MISSING_OPERATOR,
"Null or empty operator for logical expression");
}

if (operands != null) {
for (Evaluable evaluable : operands) {
if (evaluable != null) {
resolvedOperands.add(evaluable.evaluate(context));
}
}
}

switch (operationName) {
case "and":
return performAndOperation(resolvedOperands);
return performAndOperation(context, operands);
case "or":
return performOrOperation(resolvedOperands);
return performOrOperation(context, operands);
default:
return new RulesResult(
RulesResult.FailureType.MISSING_OPERATOR,
String.format("Unknown conjunction operator - %s.", operationName));
}
}

private RulesResult performAndOperation(final List<RulesResult> resolvedOperands) {
for (RulesResult rulesResult : resolvedOperands) {
if (!rulesResult.isSuccess()) {
return new RulesResult(
RulesResult.FailureType.CONDITION_FAILED, "AND operation returned false.");
private RulesResult performAndOperation(
final Context context, final List<Evaluable> resolvedOperands) {
for (Evaluable evaluable : resolvedOperands) {
if (evaluable != null) {
RulesResult rulesResult = evaluable.evaluate(context);
if (!rulesResult.isSuccess()) {
return new RulesResult(
RulesResult.FailureType.CONDITION_FAILED,
"AND operation returned false.");
}
}
}

return RulesResult.SUCCESS;
}

private RulesResult performOrOperation(final List<RulesResult> resolvedOperands) {
for (RulesResult rulesResult : resolvedOperands) {
if (rulesResult.isSuccess()) {
return RulesResult.SUCCESS;
private RulesResult performOrOperation(
final Context context, final List<Evaluable> resolvedOperands) {
for (Evaluable evaluable : resolvedOperands) {
if (evaluable != null) {
RulesResult rulesResult = evaluable.evaluate(context);
if (rulesResult.isSuccess()) {
return RulesResult.SUCCESS;
}
}
}

return new RulesResult(
RulesResult.FailureType.CONDITION_FAILED, "OR operation returned false.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,146 @@ public void testGetFnv1aHash_WithMaskMatchingNoKeys() {
assertEquals(expectedHash, hash);
}

@Test
public void testGetFnv1aHash_NullAndEmptyMapValuesPresent_WithAllKeysInMask() {
// setup
final Map<String, Object> map =
new HashMap<String, Object>() {
{
put("a", "1");
put("b", "2");
put("c", "");
put("d", null);
}
};
// test
final long hash = MapUtilsKt.convertMapToFnv1aHash(map, new String[] {"a", "b", "c", "d"});
// verify flattened map string "a:1b:2"
final long expectedHash = 3371500665L;
assertEquals(expectedHash, hash);
}

@Test
public void
testGetFnv1aHash_NullAndEmptyMapValuesPresent_WithKeysForNullAndEmptyValuesInMask() {
// setup
final Map<String, Object> map =
new HashMap<String, Object>() {
{
put("a", "1");
put("b", "2");
put("c", "");
put("d", null);
}
};
// test
final long hash = MapUtilsKt.convertMapToFnv1aHash(map, new String[] {"c", "d"});
// verify 0 / no hash generated due to mask keys being present for null and empty values
final long expectedHash = 0;
assertEquals(expectedHash, hash);
}

@Test
public void testGetFnv1aHash_NullAndEmptyMapValuesPresentInInnerMap_WithAllKeysInMask() {
// setup
final Map<String, Object> inner =
new HashMap<String, Object>() {
{
put("a", "1");
put("b", "2");
put("c", "");
put("d", null);
}
};
final Map<String, Object> map =
new HashMap<String, Object>() {
{
put("inner", inner);
}
};
// test
final long hash =
MapUtilsKt.convertMapToFnv1aHash(map, new String[] {"inner.a", "inner.b"});
// verify flattened map string "inner.a:1inner.b:2"
final long expectedHash = 3328417429L;
assertEquals(expectedHash, hash);
}

@Test
public void testGetFnv1aHash_NullAndEmptyMapValuesPresentInMultipleNestedInnerMaps() {
// setup
final Map<String, Object> nestedNestedInner =
new HashMap<String, Object>() {
{
put("k", "5");
put("l", "6");
put("m", "");
put("n", null);
}
};
final Map<String, Object> nestedInner =
new HashMap<String, Object>() {
{
put("f", "3");
put("g", "4");
put("h", "");
put("i", null);
put("nestedNestedInner", nestedNestedInner);
}
};
final Map<String, Object> inner =
new HashMap<String, Object>() {
{
put("a", "1");
put("b", "2");
put("c", "");
put("d", null);
put("nestedInner", nestedInner);
}
};
final Map<String, Object> map =
new HashMap<String, Object>() {
{
put("inner", inner);
}
};
// test
final long hash =
MapUtilsKt.convertMapToFnv1aHash(
map,
new String[] {
"inner.a",
"inner.b",
"inner.nestedInner.g",
"inner.nestedInner.nestedNestedInner.l"
});
// verify flattened map string
// "inner.a:1inner.b:2inner.nestedInner.g:4inner.nestedInner.nestedNestedInner.l:6"
final long expectedHash = 4160127196L;
assertEquals(expectedHash, hash);
}

@Test
public void testGetFnv1aHash_NullAndEmptyMapValuesPresent_WithNoMask() {
// setup
final Map<String, Object> map =
new HashMap<String, Object>() {
{
put("a", "1");
put("b", "2");
put("c", "");
put("d", null);
put("e", 3);
put("f", "4");
}
};
// test
final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null);
// verify flattened map string "a:1b:2e:3f:4"
final long expectedHash = 3916945161L;
assertEquals(expectedHash, hash);
}

@Test
public void testGetFnv1aHash_NoMask_VerifyEventDataMapSortedWithCaseSensitivity() {
// setup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,32 @@ import com.adobe.marketing.mobile.ExtensionApi
import com.adobe.marketing.mobile.rulesengine.ComparisonExpression
import com.adobe.marketing.mobile.rulesengine.Evaluable
import com.adobe.marketing.mobile.rulesengine.LogicalExpression
import com.adobe.marketing.mobile.rulesengine.OperandFunction
import com.adobe.marketing.mobile.test.util.buildJSONObject
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@RunWith(MockitoJUnitRunner.Silent::class)
class JSONConditionTests {
fun ComparisonExpression<*, *>.getLhs(): Any? {
return javaClass.getDeclaredField("lhs").let {
it.isAccessible = true
return@let it.get(this)
}
}

fun OperandFunction<Object>.getFunctionParameters(): Any {
return javaClass.getDeclaredField("functionParameters").let {
it.isAccessible = true
return@let it.get(this)
}
}

private lateinit var extensionApi: ExtensionApi

@Before
Expand Down Expand Up @@ -171,4 +187,69 @@ class JSONConditionTests {
assertTrue(evaluable is Evaluable)
assertTrue(evaluable is LogicalExpression)
}

@Test
fun testMatcherWithHistoricalTypeAndNoSearchTypeDefaultsToSearchTypeAny() {
val jsonConditionString = """
{
"type": "historical",
"definition" : {
"events" : [
{
"key1": "value1",
"key2": "value2",
"key3": true,
}
],
"from": 123456789,
"to": 234567890,
"matcher" : "ge",
"value" : 1
}
}
""".trimIndent()
val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString), extensionApi)
assertTrue(jsonCondition is HistoricalCondition)
val evaluable = jsonCondition.toEvaluable()
assertTrue(evaluable is Evaluable)
assertTrue(evaluable is ComparisonExpression<*, *>)
val lhs = evaluable.getLhs() as OperandFunction<Object>
val parameters = lhs.getFunctionParameters() as Array<Any>
assertEquals("any", parameters.get(1))
}

@Test
fun testMatcherWithHistoricalTypeAndSearchTypeIsOrdered() {
val jsonConditionString = """
{
"type": "historical",
"definition" : {
"events" : [
{
"key1": "value1",
"key2": "value2",
"key3": true,
},
{
"key4": "value3",
"key5": "value4"
}
],
"searchType": "ordered",
"from": 123456789,
"to": 234567890,
"matcher" : "ge",
"value" : 1
}
}
""".trimIndent()
val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString), extensionApi)
assertTrue(jsonCondition is HistoricalCondition)
val evaluable = jsonCondition.toEvaluable()
assertTrue(evaluable is Evaluable)
assertTrue(evaluable is ComparisonExpression<*, *>)
val lhs = evaluable.getLhs() as OperandFunction<Object>
val parameters = lhs.getFunctionParameters() as Array<Any>
assertEquals("ordered", parameters.get(1))
}
}
Loading

0 comments on commit c8743a6

Please sign in to comment.