Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7aa9528
Reevaluatoin Added to Launch Rules Engine
sagar-sharma-adobe Dec 22, 2025
f3b0112
Updated Reevaluation logic and added test cases for backward compatib…
sagar-sharma-adobe Jan 2, 2026
5149dd9
Adding Reevaluable key parsing logic to JSONRule and JSONRuleRoot
sagar-sharma-adobe Jan 2, 2026
d30ff81
Reevaluation based on consequence type added
sagar-sharma-adobe Jan 7, 2026
4956617
Launch Rules Engine Unit Tes cases fix
sagar-sharma-adobe Jan 14, 2026
d4866bf
Using processed event later for reevaluation processing as well
sagar-sharma-adobe Jan 21, 2026
1eb3ec9
Unit test cases for Reevaluation flow
sagar-sharma-adobe Jan 21, 2026
6882148
Updating parsing and unit test cases
sagar-sharma-adobe Jan 22, 2026
278eae0
Adding a default value for reEvaluable flag in Meta Object
sagar-sharma-adobe Jan 28, 2026
df0a37e
Updating reevaluation key in meta object from "reEvaluable" to "reEva…
sagar-sharma-adobe Jan 29, 2026
8c0df49
Updating Core version to 3.5.1
github-actions[bot] Feb 2, 2026
372642c
Merge pull request #795 from adobe/core-update-version-3.5.1
sagar-sharma-adobe Feb 4, 2026
6263b92
Updating Core version to 3.6.0
github-actions[bot] Feb 4, 2026
8555e18
Merge pull request #796 from adobe/core-update-version-3.6.0
sagar-sharma-adobe Feb 4, 2026
a28d476
Spotless Apply
sagar-sharma-adobe Feb 4, 2026
a703a49
Merge remote-tracking branch 'origin/dev-v3.6.0' into main_reevaluati…
sagar-sharma-adobe Feb 4, 2026
c52ba74
Fixing Unit Test case assertion
sagar-sharma-adobe Feb 4, 2026
5bff154
Adding API changes declaration to core.api
sagar-sharma-adobe Feb 4, 2026
1d2880c
Addressing review Comments
sagar-sharma-adobe Feb 9, 2026
855cb00
Spotless Apply
sagar-sharma-adobe Feb 9, 2026
2a6cc19
Updating callback to AdobeCallback
sagar-sharma-adobe Feb 10, 2026
116f07e
Updating callback to AdobeCallback
sagar-sharma-adobe Feb 10, 2026
9bd4ce9
SpotlessApply
sagar-sharma-adobe Feb 10, 2026
781bd01
checkStyle fixes
sagar-sharma-adobe Feb 10, 2026
90e280f
Updating public API
sagar-sharma-adobe Feb 10, 2026
69592e0
Updating Core version to 3.5.1
github-actions[bot] Feb 10, 2026
7c094a2
Merge pull request #798 from adobe/core-update-version-3.5.1
sagar-sharma-adobe Feb 10, 2026
6aa73d4
Merge remote-tracking branch 'origin/staging' into dev-v3.6.0
sagar-sharma-adobe Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions code/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,18 @@ public final class com/adobe/marketing/mobile/WrapperType : java/lang/Enum {
public final class com/adobe/marketing/mobile/launch/rulesengine/LaunchRule : com/adobe/marketing/mobile/rulesengine/Rule {
public static final field $stable I
public fun <init> (Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;)V
public fun <init> (Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;)V
public synthetic fun <init> (Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcom/adobe/marketing/mobile/rulesengine/Evaluable;
public final fun component2 ()Ljava/util/List;
public final fun copy (Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;)Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;
public static synthetic fun copy$default (Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;ILjava/lang/Object;)Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;
public final fun component3 ()Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;
public final fun copy (Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;)Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;
public static synthetic fun copy$default (Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;Lcom/adobe/marketing/mobile/rulesengine/Evaluable;Ljava/util/List;Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;ILjava/lang/Object;)Lcom/adobe/marketing/mobile/launch/rulesengine/LaunchRule;
public fun equals (Ljava/lang/Object;)Z
public final fun getCondition ()Lcom/adobe/marketing/mobile/rulesengine/Evaluable;
public final fun getConsequenceList ()Ljava/util/List;
public fun getEvaluable ()Lcom/adobe/marketing/mobile/rulesengine/Evaluable;
public final fun getMeta ()Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
Expand All @@ -341,6 +345,7 @@ public class com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine {
public fun evaluateEvent (Lcom/adobe/marketing/mobile/Event;)Ljava/util/List;
public fun processEvent (Lcom/adobe/marketing/mobile/Event;)Lcom/adobe/marketing/mobile/Event;
public fun replaceRules (Ljava/util/List;)V
public fun setRuleReevaluationInterceptor (Lcom/adobe/marketing/mobile/launch/rulesengine/RuleReevaluationInterceptor;)V
}

public final class com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence {
Expand All @@ -359,6 +364,24 @@ public final class com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence
public fun toString ()Ljava/lang/String;
}

public final class com/adobe/marketing/mobile/launch/rulesengine/RuleMeta {
public static final field $stable I
public fun <init> ()V
public fun <init> (Z)V
public synthetic fun <init> (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun copy (Z)Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;
public static synthetic fun copy$default (Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;ZILjava/lang/Object;)Lcom/adobe/marketing/mobile/launch/rulesengine/RuleMeta;
public fun equals (Ljava/lang/Object;)Z
public final fun getReEvaluate ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/adobe/marketing/mobile/launch/rulesengine/RuleReevaluationInterceptor {
public abstract fun onReevaluationTriggered (Lcom/adobe/marketing/mobile/Event;Ljava/util/List;Lcom/adobe/marketing/mobile/AdobeCallback;)V
}

public class com/adobe/marketing/mobile/launch/rulesengine/download/RulesLoadResult {
public fun <init> (Ljava/lang/String;Lcom/adobe/marketing/mobile/launch/rulesengine/download/RulesLoadResult$Reason;)V
public fun getData ()Ljava/lang/String;
Expand Down
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 = "3.5.0"
const val VERSION = "3.5.1"

object EventDataKeys {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ import com.adobe.marketing.mobile.rulesengine.Rule
*
* @property condition an object of [Evaluable]
* @property consequenceList a list of [RuleConsequence] objects
* @constructor Constructs a new [LaunchRule]
* @property meta an object containing relevant meta data regarding the rule
Comment thread
sagar-sharma-adobe marked this conversation as resolved.
* @constructor Constructs a new [LaunchRule] (Optional)
*/
data class LaunchRule(val condition: Evaluable, val consequenceList: List<RuleConsequence>) : Rule {
data class LaunchRule @JvmOverloads constructor(
val condition: Evaluable,
val consequenceList: List<RuleConsequence>,
val meta: RuleMeta = RuleMeta()
) : Rule {
override fun getEvaluable(): Evaluable {
return condition
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ internal class LaunchRulesConsequence(
private const val CONSEQUENCE_EVENT_HISTORY_OPERATION_INSERT_IF_NOT_EXISTS =
"insertIfNotExists"
private const val ASYNC_TIMEOUT = 1000L
private val REEVALUABLE_CONSEQUENCE_TYPES = setOf<String>(CONSEQUENCE_TYPE_SCHEMA)
}

/**
Expand Down Expand Up @@ -170,6 +171,36 @@ internal class LaunchRulesConsequence(
return processedConsequences
}

fun getReevaluableRules(rules: MutableList<LaunchRule>): MutableList<LaunchRule?> {
val revaluableRules: MutableList<LaunchRule?> = java.util.ArrayList<LaunchRule?>()
for (rule in rules) {
if (rule.meta.reEvaluate && rule.hasReevaluableSupportedConsequence) {
Comment thread
sagar-sharma-adobe marked this conversation as resolved.
revaluableRules.add(rule)
}
}
return revaluableRules
}

fun getRulesToHoldForReevaluation(rules: MutableList<LaunchRule>): MutableList<LaunchRule?> {
val rulesToHold: MutableList<LaunchRule?> = ArrayList()
for (rule in rules) {
if (rule.hasReevaluableSupportedConsequence) {
rulesToHold.add(rule)
}
}
return rulesToHold
}

private val LaunchRule.hasReevaluableSupportedConsequence: Boolean
get() {
for (consequence in this.consequenceList) {
if (REEVALUABLE_CONSEQUENCE_TYPES.contains(consequence.type)) {
return true
}
}
return false
}

/**
* Replace tokens inside the provided [RuleConsequence] with the right value
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

public class LaunchRulesEngine {

private RuleReevaluationInterceptor reevaluationInterceptor;

@VisibleForTesting static final String RULES_ENGINE_NAME = "name";
private final String name;
private final RulesEngine<LaunchRule> ruleRulesEngine;
Expand Down Expand Up @@ -61,6 +63,15 @@ public LaunchRulesEngine(@NonNull final String name, @NonNull final ExtensionApi
this.ruleRulesEngine = ruleEngine;
}

/**
* Sets the {@link RuleReevaluationInterceptor} for this {@link LaunchRulesEngine}.
*
* @param interceptor the interceptor to be set.
*/
public void setRuleReevaluationInterceptor(final RuleReevaluationInterceptor interceptor) {
Comment thread
sagar-sharma-adobe marked this conversation as resolved.
this.reevaluationInterceptor = interceptor;
}

/**
* Set a new set of rules, the new rules replace the current rules.
*
Expand Down Expand Up @@ -102,20 +113,19 @@ public Event processEvent(@NonNull final Event event) {
throw new IllegalArgumentException("Cannot evaluate null event.");
}

final List<LaunchRule> matchedRules =
ruleRulesEngine.evaluate(new LaunchTokenFinder(event, extensionApi));

// if initial rule set has not been received, cache the event to be processed
// when rules are set
if (!initialRulesReceived) {
handleCaching(event);
}
return launchRulesConsequence.process(event, matchedRules);

return processAndIntercept(event);
}

/**
* Evaluates the supplied event against the all current rules and returns the {@link
* RuleConsequence}'s from the rules that matched the supplied event.
* RuleConsequence}'s from the rules that matched the supplied event. This method is synchronous
* and does not trigger any re-evaluation interceptors.
*
* @param event the event to be evaluated
* @return a {@code List<RuleConsequence>} that match the supplied event.
Expand All @@ -125,13 +135,50 @@ public List<RuleConsequence> evaluateEvent(@NonNull final Event event) {
throw new IllegalArgumentException("Cannot evaluate null event.");
}

final List<LaunchRule> matchedRules =
ruleRulesEngine.evaluate(new LaunchTokenFinder(event, extensionApi));
final LaunchTokenFinder tokenFinder = new LaunchTokenFinder(event, extensionApi);
final List<LaunchRule> matchedRules = ruleRulesEngine.evaluate(tokenFinder);

// get token replaced consequences
return launchRulesConsequence.evaluate(event, matchedRules);
}

private Event processAndIntercept(final Event event) {
final LaunchTokenFinder tokenFinder = new LaunchTokenFinder(event, extensionApi);
final List<LaunchRule> matchedRules = ruleRulesEngine.evaluate(tokenFinder);

// If no interceptor is set, process consequences immediately.
if (reevaluationInterceptor == null) {
return launchRulesConsequence.process(event, matchedRules);
}

final List<LaunchRule> revaluableRules =
launchRulesConsequence.getReevaluableRules(matchedRules);
if (revaluableRules.isEmpty()) {
return launchRulesConsequence.process(event, matchedRules);
}

final List<LaunchRule> rulesToHold =
launchRulesConsequence.getRulesToHoldForReevaluation(matchedRules);
final ArrayList<LaunchRule> rulesToProcess = new ArrayList<>(matchedRules);
rulesToProcess.removeAll(rulesToHold);
Event processedEvent = launchRulesConsequence.process(event, rulesToProcess);
reevaluationInterceptor.onReevaluationTriggered(
processedEvent,
revaluableRules,
(success) -> {
// After the interceptor has updated the rules, re-evaluate and process
// consequences. If update is not success intercepted rules are not
// processed
if (success) {
final ArrayList<LaunchRule> newlyMatchedRules =
new ArrayList<>(ruleRulesEngine.evaluate(tokenFinder));
newlyMatchedRules.removeAll(rulesToProcess);
launchRulesConsequence.process(processedEvent, newlyMatchedRules);
}
});
return processedEvent;
}

List<LaunchRule> getRules() {
return ruleRulesEngine.getRules();
}
Expand All @@ -142,14 +189,14 @@ int getCachedEventCount() {
}

private void reprocessCachedEvents() {
for (Event cachedEvent : cachedEvents) {
final List<LaunchRule> matchedRules =
ruleRulesEngine.evaluate(new LaunchTokenFinder(cachedEvent, extensionApi));
launchRulesConsequence.process(cachedEvent, matchedRules);
}
// clear cached events and set the flag to indicate that rules were set at least once
// To avoid ConcurrentModificationException, we process a copy of the cached events.
final List<Event> eventsToReprocess = new ArrayList<>(cachedEvents);
cachedEvents.clear();
initialRulesReceived = true;
initialRulesReceived = true; // Set before processing to prevent re-caching

for (final Event cachedEvent : eventsToReprocess) {
processEvent(cachedEvent);
}
}

private void handleCaching(final Event event) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright 2022 Adobe. All rights reserved.
This file is licensed 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.launch.rulesengine

/**
* The data class representing a rule's consequence object
*
* @property reEvaluate the flag reEvaluate for sensitive rules
* @constructor Constructs a new [RuleMeta]
*/

data class RuleMeta(
val reEvaluate: Boolean = false,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2026 Adobe. All rights reserved.
This file is licensed 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.launch.rulesengine

import com.adobe.marketing.mobile.AdobeCallback
import com.adobe.marketing.mobile.Event

/**
* An interface for an interceptor that is triggered when a [LaunchRule] with the
* re-evaluation flag is triggered. The interceptor is responsible for updating the rules and
* invoking the [AdobeCallback] when complete. success is passed as a boolean value.
*/
interface RuleReevaluationInterceptor {
fun onReevaluationTriggered(
event: Event?,
revaluableRules: List<LaunchRule?>?,
callback: AdobeCallback<Boolean>?
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2022 Adobe. All rights reserved.
This file is licensed 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.launch.rulesengine.json

import com.adobe.marketing.mobile.launch.rulesengine.RuleMeta
import org.json.JSONObject

/**
* Generic utility to parse a meta object from JSON for rules engine.
*
* This class is responsible for extracting meta information from a JSON object.
* Currently, it only parses the `reEvaluate` flag, but it is designed to be extended
* in the future to support additional meta keys as needed.
*
* Example of a meta JSON object:
* ```json
* {
* "reEvaluate": true
* }
* ```
*
* Future meta keys can be added to this class as requirements evolve.
*/
internal class JSONMeta private constructor(
Comment thread
sagar-sharma-adobe marked this conversation as resolved.
private val reEvaluate: Boolean,
) {
companion object {
private const val KEY_REEVALUATE = "reEvaluate"
operator fun invoke(jsonObject: JSONObject?): JSONMeta {
return JSONMeta(
jsonObject?.optBoolean(KEY_REEVALUATE, false) ?: false
)
}
}

/**
* Converts this object into a validated [RuleMeta].
*
* @return a valid [RuleMeta] or `null` if any validation check fails
*/
@JvmSynthetic
internal fun toMeta(): RuleMeta {
return RuleMeta(reEvaluate)
}
}
Loading
Loading