Skip to content

Commit cc42033

Browse files
committed
SDKS-3917 Journey Module Design and Prototype
1 parent e5c2981 commit cc42033

File tree

93 files changed

+5069
-371
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+5069
-371
lines changed

external-idp/src/main/kotlin/com/pingidentity/idp/journey/IdpCallback.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class IdpCallback : AbstractCallback(), JourneyAware, RequestInterceptor {
4040

4141
override lateinit var journey: Journey
4242

43-
override fun onAttribute(name: String, value: JsonElement) {
43+
override fun init(name: String, value: JsonElement) {
4444
when (name) {
4545
"provider" -> this.provider = value.jsonPrimitive.content
4646
"clientId" -> this.clientId = value.jsonPrimitive.content
@@ -53,7 +53,7 @@ class IdpCallback : AbstractCallback(), JourneyAware, RequestInterceptor {
5353
}
5454
}
5555

56-
override fun asJson() = input(result.token, tokenType)
56+
override fun payload() = input(result.token, tokenType)
5757

5858
/**
5959
* Overrides the request with the resume request if initialized, else return the input request.

external-idp/src/main/kotlin/com/pingidentity/idp/journey/SelectIdpCallback.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SelectIdpCallback : AbstractCallback() {
2525

2626
var value: String= ""
2727

28-
override fun onAttribute(name: String, value: JsonElement) {
28+
override fun init(name: String, value: JsonElement) {
2929
when (name) {
3030
"providers" -> {
3131
providers = value.jsonArray.map {
@@ -35,7 +35,7 @@ class SelectIdpCallback : AbstractCallback() {
3535
}
3636
}
3737

38-
override fun asJson(): JsonObject {
38+
override fun payload(): JsonObject {
3939
return input(value)
4040
}
4141

foundation/journey-plugin/README.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<p align="center">
2+
<a href="https://github.com/ForgeRock/ping-android-sdk">
3+
<img src="https://www.pingidentity.com/content/dam/picr/nav/Ping-Logo-2.svg" alt="Ping Identity Logo" width="200">
4+
</a>
5+
<hr/>
6+
</p>
7+
8+
# Journey Plugin
9+
10+
# Journey-Plugin Module
11+
12+
The `Journey-Plugin` module provides a mechanism to extend the functionality of the Journey Module by allowing you to
13+
implement custom callbacks and register them for use within the Journey workflow. This enables you to inject specific
14+
logic and data handling at various stages of the user journey.
15+
16+
## Key Features
17+
18+
1. **Callback Implementation:** You can implement your own callback logic by creating classes that adhere to
19+
the `Callback` interface. This allows for highly customized behavior tailored to your application's needs.
20+
21+
2. **Callback Registration:** The module provides a centralized registry (`CallbackRegistry`) where you can register
22+
your implemented callbacks. This registration associates a Callback type (a string) with your callback
23+
implementation, making it discoverable and usable by the Journey Module.
24+
25+
3. **Loose Coupling:** By using the plugin mechanism, your custom logic remains separate from the core Journey Module,
26+
promoting modularity and maintainability.
27+
28+
## Getting Started
29+
30+
### 1. Add Dependency
31+
32+
To use the `Journey-Plugin` module in your project, you need to add it as a dependency, add the following line to your
33+
module's `build.gradle` file (usually the `app/build.gradle` or a feature module's `build.gradle`):
34+
35+
```gradle
36+
implementation(project(":foundation:android"))
37+
implementation(project(":foundation:journey-plugin"))
38+
```
39+
40+
**Note:** Ensure that the path `:foundation:android` correctly points to the location of your `foundation` Android
41+
module.
42+
43+
### 2. Implement the `Callback` Interface
44+
45+
Create a class that implements the `Callback` interface. This interface defines the contract for your custom callbacks.
46+
47+
```kotlin
48+
import com.google.gson.JsonObject
49+
import com.example.foundation.android.journey.Action // Assuming Action interface is in this package
50+
51+
interface Callback : Action {
52+
/**
53+
* Initializes the callback with the provided JSON object.
54+
* @param jsonObject The JSON object containing the callback configuration.
55+
* @return The initialized Callback instance.
56+
*/
57+
fun init(jsonObject: JsonObject): Callback
58+
59+
/**
60+
* Returns the payload of the callback.
61+
*/
62+
fun payload(): JsonObject
63+
}
64+
```
65+
66+
**Explanation of the `Callback` Interface:**
67+
68+
* **`Callback : Action`**: Your callback interface extends an `Action` interface (the definition of `Action` is not
69+
provided in the original text, but it likely represents a base interface for actions within the Journey Module).
70+
* **`fun init(jsonObject: JsonObject): Callback`**: This function is responsible for initializing your callback instance
71+
using a JSON object provided by the Journey Module. This JSON object will likely contain configuration parameters
72+
specific to this callback instance. The function should return the initialized `Callback` object.
73+
* **`fun payload(): JsonObject`**: This function should return a `JsonObject` representing the output or data payload of
74+
your callback after it has been executed. This payload will likely be used by subsequent steps in the Journey
75+
workflow.
76+
* **`AbstractCallback : Callback`**: Provides a base implementation of the `Callback` interface. This class can
77+
contain common logic or properties that all callbacks might share, reducing code duplication.
78+
*
79+
80+
**Example Implementation:**
81+
82+
Let's say you want to create a callback that collect the username.
83+
84+
```kotlin
85+
86+
class NameCallback : AbstractCallback() {
87+
var prompt: String = ""
88+
private set
89+
90+
//Input
91+
var name: String = ""
92+
93+
override fun init(name: String, value: JsonElement) {
94+
when (name) {
95+
"prompt" -> this.prompt = value.jsonPrimitive.content
96+
}
97+
}
98+
99+
override fun payload() = input(name)
100+
101+
}
102+
```
103+
104+
### 3. Register Your Callback
105+
106+
To make your custom callback available to the Journey Module, you need to register it with the `CallbackRegistry`. This
107+
is typically done during your module's startup process. The Journey Module likely provides a mechanism to initialize
108+
modules.
109+
110+
**Example using `ModuleInitializer`:**
111+
112+
```kotlin
113+
import com.example.foundation.android.journey.CallbackRegistry
114+
import com.example.foundation.android.journey.ModuleInitializer
115+
116+
class CollectorRegistry : ModuleInitializer() {
117+
118+
/**
119+
* Initializes the module by registering custom callbacks.
120+
*/
121+
override fun initialize() {
122+
CallbackRegistry.register("ChoiceCallback", ::ChoiceCallback)
123+
CallbackRegistry.register("NameCallback", ::NameCallback) // Example from the original text
124+
// Register other custom callbacks here
125+
}
126+
}
127+
```
128+
129+
**Explanation:**
130+
131+
* **`CollectorRegistry : ModuleInitializer()`**: This class implements the `ModuleInitializer` interface (assuming this
132+
interface exists in the Journey Module).
133+
* **`override fun initialize()`**: This function is called during the module's initialization.
134+
* **`CallbackRegistry.register("CHOICE_CALLBACK", ::ChoiceCallback)`**: This line registers your `ChoiceCallback`
135+
implementation.
136+
* `"ChoiceCallback"`: This is a unique identifier (a string) that the Journey Module will use to invoke this
137+
specific callback.
138+
* `::ChoiceCallback`: This is a class reference to your `ChoiceCallback` implementation. The `CallbackRegistry`
139+
likely uses this reference to create instances of your callback when needed.
140+
141+
**Important Considerations:**
142+
143+
* **Callback Name:** Ensure that the identifier you use when registering your callback matches the one Callback name
144+
from server.
145+
* **Module Initialization:** Ensure you register the ModuleInitializer in the AndroidManifest.xml file.
146+
```xml
147+
<provider
148+
android:name=".journey.CollectorRegistry"
149+
android:authorities="${applicationId}.collectorRegistry"
150+
android:enabled="true"
151+
android:exported="false"/>
152+
```
153+
* **Testing:** Thoroughly test your custom callbacks to ensure they function as expected within the Journey workflow.
154+
155+
By following these steps, you can effectively use the `Journey-Plugin` module to extend the Journey Module with your own
156+
custom callback logic, making your application more flexible and adaptable to specific user journey requirements.

foundation/journey-plugin/src/main/kotlin/com/pingidentity/journey/plugin/AbstractCallback.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package com.pingidentity.journey.plugin
99

1010
import kotlinx.serialization.json.JsonArray
1111
import kotlinx.serialization.json.JsonElement
12+
import kotlinx.serialization.json.JsonNull
1213
import kotlinx.serialization.json.JsonObject
1314
import kotlinx.serialization.json.buildJsonArray
1415
import kotlinx.serialization.json.buildJsonObject
@@ -22,18 +23,21 @@ abstract class AbstractCallback : Callback {
2223
lateinit var json: JsonObject
2324
protected set
2425

25-
protected abstract fun onAttribute(name: String, value: JsonElement)
26+
protected abstract fun init(name: String, value: JsonElement)
2627

27-
override fun init(jsonObject: JsonObject) {
28+
override fun init(jsonObject: JsonObject) : Callback {
2829
this.json = jsonObject
2930
jsonObject["output"]?.jsonArray?.forEach { outputItem ->
3031
val outputObject = outputItem.jsonObject
3132
outputObject["name"]?.jsonPrimitive?.content?.let { name ->
3233
outputObject["value"]?.let { value ->
33-
onAttribute(name, value)
34+
if (value !is JsonNull) {
35+
init(name, value)
36+
}
3437
}
3538
}
3639
}
40+
return this
3741
}
3842

3943
fun input(vararg value: Any): JsonObject {
@@ -112,5 +116,5 @@ abstract class AbstractCallback : Callback {
112116
return json
113117
}
114118

115-
override fun asJson(): JsonObject = json
119+
override fun payload(): JsonObject = json
116120
}

foundation/journey-plugin/src/main/kotlin/com/pingidentity/journey/plugin/Callback.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@ import com.pingidentity.orchestrate.Action
1111
import com.pingidentity.orchestrate.ContinueNode
1212
import kotlinx.serialization.json.JsonObject
1313

14+
/**
15+
* Callback interface for handling actions in the journey plugin.
16+
*/
1417
interface Callback : Action {
15-
fun init(jsonObject: JsonObject)
18+
/**
19+
* Initializes the callback with the provided JSON object.
20+
* @param jsonObject The JSON object containing the callback configuration.
21+
* * @return The initialized Callback instance.
22+
*/
23+
fun init(jsonObject: JsonObject): Callback
1624

17-
//Callback is more self-contained, it created its own json without depending on other Callback
18-
fun asJson(): JsonObject
25+
/**
26+
* Returns the payload of the callback.
27+
*/
28+
fun payload(): JsonObject
1929
}
2030

2131
/**

foundation/journey-plugin/src/main/kotlin/com/pingidentity/journey/plugin/CallbackRegistry.kt

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,52 @@ package com.pingidentity.journey.plugin
99

1010
import com.pingidentity.orchestrate.ContinueNode
1111
import kotlinx.serialization.json.JsonArray
12-
import kotlinx.serialization.json.JsonObject
1312
import kotlinx.serialization.json.jsonObject
1413
import kotlinx.serialization.json.jsonPrimitive
1514

15+
/**
16+
* CallbackRegistry is responsible for managing the registration and retrieval of callbacks.
17+
* It allows for the registration of callbacks by type and provides a method to retrieve
18+
* callbacks based on a JSON array.
19+
*/
1620
object CallbackRegistry {
1721
private val callbacks: MutableMap<String, () -> Callback> = HashMap()
18-
val derivedCallbacks: MutableList<(JsonObject) -> String?> = mutableListOf()
1922

23+
/**
24+
* Registers a callback with the specified type.
25+
* @param type The type of the callback.
26+
* @param block A lambda function that returns an instance of the callback.
27+
*/
2028
fun register(type: String, block: () -> Callback) {
2129
callbacks[type] = block
2230
}
2331

24-
fun registerDerived(block: (JsonObject) -> String?) {
25-
derivedCallbacks.add(block)
26-
}
27-
2832
/**
29-
* Injects the DaVinci and ContinueNode instances into the collectors.
30-
* @param davinci The DaVinci instance to be injected.
31-
* @param continueNode The ContinueNode instance to be injected.
33+
* Injects the Journey instances into the callbacks.
34+
* @param journey The Journey instance to be injected.
35+
* @param continueNode The ContinueNode instance.
3236
*/
33-
fun inject(journey: Journey, continueNode: ContinueNode) {
37+
fun inject(journey: Journey, continueNode: ContinueNode) {
3438
continueNode.callbacks.forEach { callback ->
3539
if (callback is JourneyAware) {
3640
callback.journey = journey
3741
}
3842
}
3943
}
4044

41-
45+
/**
46+
* Retrieves a list of callbacks based on the provided JSON array.
47+
* @param array The JSON array containing the callback types.
48+
* * @return A list of initialized Callback instances.
49+
*/
4250
fun callback(array: JsonArray): List<Callback> {
4351
val list = mutableListOf<Callback>()
4452
array.forEach { item ->
4553
val jsonObject = item.jsonObject
4654
val type = jsonObject["type"]?.jsonPrimitive?.content
47-
callbacks[type]?.let { list.add(it().apply { init(jsonObject) }) }
55+
callbacks[type]?.let {
56+
list.add(it().init(jsonObject))
57+
}
4858
}
4959
return list
5060
}

foundation/journey-plugin/src/test/kotlin/com/pingidentity/journey/plugin/CallbackRegistryTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,20 @@ class CallbackRegistryTest {
5858
}
5959

6060
class DummyCallback : Callback {
61-
override fun init(jsonObject: JsonObject) {
62-
// Do nothing
61+
override fun init(jsonObject: JsonObject): Callback {
62+
return this
6363
}
6464

65-
override fun asJson(): JsonObject {
65+
override fun payload(): JsonObject {
6666
return buildJsonObject { }
6767
}
6868
}
6969
class Dummy2Callback : Callback {
70-
override fun init(jsonObject: JsonObject) {
71-
// Do nothing
70+
override fun init(jsonObject: JsonObject): Callback {
71+
return this
7272
}
7373

74-
override fun asJson(): JsonObject {
74+
override fun payload(): JsonObject {
7575
return buildJsonObject { }
7676
}
7777
}

foundation/logger/src/main/kotlin/com/pingidentity/logger/Standard.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ open class Standard : Logger {
2020
* @param message The message to log.
2121
*/
2222
override fun d(message: String) {
23-
Log.d(TAG, message)
23+
if (message.length > 4000) {
24+
var i = 0
25+
while (i < message.length) {
26+
val end = (i + 4000).coerceAtMost(message.length)
27+
Log.d(TAG, message.substring(i, end))
28+
i = end
29+
}
30+
} else {
31+
Log.d(TAG, message)
32+
}
2433
}
2534

2635
/**

foundation/orchestrate/src/main/kotlin/com/pingidentity/orchestrate/SharedContext.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ package com.pingidentity.orchestrate
1212
* @property map The map that holds the shared context.
1313
*/
1414
class SharedContext(val map: MutableMap<String, Any>) : MutableMap<String, Any> by map {
15+
16+
infix fun String.to(value: Any) {
17+
this@SharedContext[this] = value
18+
}
1519
/**
1620
* Returns the value of a specific key from the shared context.
1721
* @param key The key for which the value is to be returned.

0 commit comments

Comments
 (0)