diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..ca33bb1
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,53 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ minSdk 24
+ targetSdk 32
+ versionCode 1
+ versionName "1.0.5"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+ buildTypes {
+ debug {
+ buildConfigField "String", 'SDK_VERSION', '"1.0.5"'
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+
+ release {
+ buildConfigField "String", 'SDK_VERSION', '"1.0.5"'
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
+// implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3'
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.5'
+
+ testImplementation 'junit:junit:4.13.2'
+ testImplementation "org.robolectric:robolectric:4.+"
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ testImplementation 'com.squareup.okhttp3:mockwebserver:4.6.0'
+}
\ No newline at end of file
diff --git a/consumer-rules.pro b/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/proguard-rules.pro b/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/src/androidTest/java/com/bloomreach/discovery/pixel/ExampleInstrumentedTest.kt b/src/androidTest/java/com/bloomreach/discovery/pixel/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..c47ac74
--- /dev/null
+++ b/src/androidTest/java/com/bloomreach/discovery/pixel/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.bloomreach.discovery.pixel
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.bloomreach.discovery.pixel.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2662982
--- /dev/null
+++ b/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/ApiConstants.kt b/src/main/java/com/bloomreach/discovery/api/ApiConstants.kt
new file mode 100644
index 0000000..615c181
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/ApiConstants.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api
+
+/**
+ * API module constants including request parameters
+ */
+internal object ApiConstants {
+
+ const val REQUEST_TYPE = "request_type"
+ const val SEARCH_TYPE = "search_type"
+
+ const val REQUEST_TYPE_SEARCH = "search"
+ const val REQUEST_TYPE_SUGGEST = "suggest"
+
+ const val SEARCH_TYPE_KEYWORD = "keyword"
+ const val SEARCH_TYPE_CATEGORY = "category"
+ const val SEARCH_TYPE_BESTSELLER = "bestseller"
+
+ const val ROWS = "rows"
+ const val DEFAULT_ROWS = 10
+
+ const val START = "start"
+ const val DEFAULT_START = 0
+
+ const val SEARCH_TERM = "q"
+ const val FL = "fl"
+ const val FQ = "fq"
+ const val SORT = "sort"
+ const val STATS_FIELD = "stats.field"
+ const val EFQ = "efq"
+ const val LAT_LONG = "ll"
+ const val FACET_RANGE = "facet.range"
+
+ const val USER_ID = "user_id"
+ const val VIEW_ID = "view_id"
+ const val WIDGET_ID = "widget_id"
+
+ const val ITEM_IDS = "item_ids"
+ const val CAT_ID = "cat_id"
+ const val QUERY = "query"
+ const val CATALOG_NAME = "catalog_name"
+ const val TITLE = "title"
+ const val URL = "url"
+ const val CATALOG_VIEWS = "catalog_views"
+ const val USER_AGENT = "user_agent"
+ const val CONTEXT_ID = "context_id"
+ const val FIELDS = "fields"
+ const val FILTER_FACET = "filter_facet"
+ const val FACET = "facet"
+ const val FILTER = "filter"
+
+ const val DEFAULT_FACET_FLAG = false
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/BrApi.kt b/src/main/java/com/bloomreach/discovery/api/BrApi.kt
new file mode 100644
index 0000000..ee47504
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/BrApi.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api
+
+import com.bloomreach.discovery.api.listener.BrApiCompletionListener
+import com.bloomreach.discovery.api.network.ApiProcessor
+import com.bloomreach.discovery.api.request.*
+
+/**
+ * BrApi Singleton class holds method to initiate BrApiRequest object and API calls methods
+ */
+object BrApi {
+ private val TAG: String = BrApi.javaClass.simpleName
+ private val apiProcessor = ApiProcessor()
+ lateinit var brApiRequest: BrApiRequest
+
+ /**
+ * Initialise BrApi class with BrApiRequest object
+ * @param brApiRequest BrApiRequest object defined for initialisation
+ */
+ public fun init(brApiRequest: BrApiRequest) {
+ BrApi.brApiRequest = brApiRequest
+ }
+
+ /**
+ * Method for calling Product Search API request
+ * @param productSearchRequest Request Object required for Product Search API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun productSearchApi(productSearchRequest: ProductSearchRequest, brApiCompletionListener: BrApiCompletionListener) {
+ apiProcessor.processCoreApi(productSearchRequest.getMap(), brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Category Search API request
+ * @param categorySearchRequest Request Object required for Category Search API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun categorySearchApi(categorySearchRequest: CategorySearchRequest, brApiCompletionListener: BrApiCompletionListener) {
+ apiProcessor.processCoreApi(categorySearchRequest.getMap(), brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Content API request
+ * @param contentSearchRequest Request Object required for Content Search API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun contentSearchApi(contentSearchRequest: ContentSearchRequest, brApiCompletionListener: BrApiCompletionListener) {
+ apiProcessor.processCoreApi(contentSearchRequest.getMap(), brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling BestSeller API request
+ * @param bestSellerRequest Request Object required for Content Search API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun bestSellerApi(bestSellerRequest: BestSellerRequest, brApiCompletionListener: BrApiCompletionListener) {
+ apiProcessor.processCoreApi(bestSellerRequest.getMap(), brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Suggest API request
+ * @param autosuggestRequest Request Object required for Content Search API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun autoSuggestApi(autosuggestRequest: AutosuggestRequest, brApiCompletionListener: BrApiCompletionListener) {
+ apiProcessor.processSuggestApi(autosuggestRequest.getMap(), brApiCompletionListener)
+ }
+
+ /* ========= WIDGET API=== */
+
+ /**
+ * Method for calling Recommendation Widget API where apiType can be specified
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param apiType the type of Recommendation Widget API. This is the widgetType path parameter
+ * @param widgetRequest request Object required for Global Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+ fun recAndPathwaysWidgetApi(widgetId: String, apiType:WidgetApiType, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+
+ recAndPathwaysWidgetApi(widgetId, apiType.value, widgetRequest, brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Recommendation Widget API where apiType can be specified
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param apiType the type of Recommendation Widget API. This is the widgetType path parameter
+ * @param widgetRequest request Object required for Global Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+ fun recAndPathwaysWidgetApi(widgetId: String,
+ apiType:String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ apiProcessor.processRecsAndPathwaysApi(widgetId, apiType, widgetRequest.getMap(), brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Item-based Recommendation Widget API
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetRequest request Object required for Item-based Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+ fun itemBasedRecommendationWidgetApi(widgetId: String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ recAndPathwaysWidgetApi(widgetId, WidgetApiType.ITEM.value, widgetRequest, brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Category-based Recommendation Widget API
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetRequest request Object required for Category-based Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+ fun categoryBasedWidgetApi(widgetId: String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ recAndPathwaysWidgetApi(widgetId, WidgetApiType.CATEGORY.value, widgetRequest, brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Keyword-based Widget API
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetRequest request Object required for Keyword-based Recommendation Widget API
+ * @param brApiCompletionListener Callback listener
+ */
+ fun keywordBasedWidgetApi(widgetId: String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ recAndPathwaysWidgetApi(widgetId, WidgetApiType.KEYWORD.value, widgetRequest, brApiCompletionListener)
+ }
+
+
+ /**
+ * Method for calling Personalization-based Widget API
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetRequest request Object required for Personalization-based Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+
+ fun personalizationBasedWidgetApi(widgetId: String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ recAndPathwaysWidgetApi(widgetId, WidgetApiType.PERSONALIZED.value, widgetRequest, brApiCompletionListener)
+ }
+
+ /**
+ * Method for calling Global Recommendation Widget API
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetRequest request Object required for Global Recommendation Widget API
+ *
+ * @param brApiCompletionListener Callback listener
+ */
+ fun globalRecommendationWidgetApi(widgetId: String, widgetRequest: WidgetRequest, brApiCompletionListener: BrApiCompletionListener) {
+ if(widgetId.isEmpty()) {
+ throw IllegalArgumentException("Widget Id is empty")
+ }
+ recAndPathwaysWidgetApi(widgetId, WidgetApiType.GLOBAL.value, widgetRequest, brApiCompletionListener)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/BrApiRequest.kt b/src/main/java/com/bloomreach/discovery/api/BrApiRequest.kt
new file mode 100644
index 0000000..c3d49c1
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/BrApiRequest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api
+
+import com.bloomreach.discovery.api.model.Env
+import com.bloomreach.discovery.pixel.model.VisitorType
+
+/**
+ * Class containing initialising parameters for the API SDK.
+ *
+ * @property accountId Account Id provided by Bloomreach
+ * @property uuid Android Advertising ID
+ * @property visitorType ENUM type for New User or returning user
+ * @property domainKey The Bloomreach-provided ID of the domain receiving the request.
+ * @property authKey This parameter is only required if you track users via a universal customer ID.
+ * @property userId This parameter is only required if you track users via a universal customer ID.
+ * @property environment ENUM for api to be pointed to which version., STAGE or PROD
+ */
+data class BrApiRequest(
+ val accountId: String,
+ val uuid: String,
+ val visitorType: VisitorType,
+ val domainKey: String,
+ var authKey: String? = null,
+ var userId: String? = null,
+ var environment: Env = Env.STAGE
+)
diff --git a/src/main/java/com/bloomreach/discovery/api/listener/BrApiCompletionListener.kt b/src/main/java/com/bloomreach/discovery/api/listener/BrApiCompletionListener.kt
new file mode 100644
index 0000000..b375f63
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/listener/BrApiCompletionListener.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.listener
+
+import com.bloomreach.discovery.api.model.BrApiError
+
+/**
+ * Interface to provide callback with response when API is success and Error when API fails
+ */
+interface BrApiCompletionListener {
+ fun onBrApiSuccess(response: Any)
+ fun onBrApiFailure(error: BrApiError)
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/BaseResponse.kt b/src/main/java/com/bloomreach/discovery/api/model/BaseResponse.kt
new file mode 100644
index 0000000..67a03d4
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/BaseResponse.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter
+import com.fasterxml.jackson.annotation.JsonAnySetter
+
+open class BaseResponse {
+
+ @JsonAnySetter
+ @get:JsonAnyGetter
+ val otherFields: Map = hashMapOf()
+
+ fun getOtherField(key: String): Any? {
+ return if (otherFields.contains(key)) {
+ otherFields[key]
+ } else
+ null
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/BrApiError.kt b/src/main/java/com/bloomreach/discovery/api/model/BrApiError.kt
new file mode 100644
index 0000000..438f0da
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/BrApiError.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model
+
+import com.fasterxml.jackson.annotation.JsonProperty
+
+/**
+ * Generic Error class for handling errors from API calls
+ *
+ * @property errorMessage Formatted error message
+ * @property errorCode Error code for additional handling
+ */
+data class BrApiError(
+ @JsonProperty("message")
+ val errorMessage: String,
+
+ @JsonProperty("status_code")
+ val errorCode: Int
+)
diff --git a/src/main/java/com/bloomreach/discovery/api/model/Env.kt b/src/main/java/com/bloomreach/discovery/api/model/Env.kt
new file mode 100644
index 0000000..931cb30
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/Env.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model
+
+/**
+ * ENUM to specify APIs to be pointed to which environment
+ */
+enum class Env {
+ STAGE,
+ PROD
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Campaign.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Campaign.kt
new file mode 100644
index 0000000..124aacf
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Campaign.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+
+data class Campaign(
+ val id: String? = null,
+ val htmlText: String? = null,
+ val bannerType: String? = null,
+ val keyword: String? = null,
+ val name: String? = null,
+ val dateEnd: String? = null,
+ val dateStart: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/CoreResponse.kt b/src/main/java/com/bloomreach/discovery/api/model/core/CoreResponse.kt
new file mode 100644
index 0000000..95443b2
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/CoreResponse.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class CoreResponse(
+ @JsonProperty("category_map")
+ val categoryMap: LinkedHashMap? = null,
+
+ @JsonProperty("did_you_mean")
+ val didYouMean: List? = null,
+
+ @JsonProperty("facet_counts")
+ val facetCounts: FacetCounts? = null,
+
+ @JsonProperty("response")
+ val response: Response? = null,
+
+ @JsonProperty("campaign")
+ val campaign: Campaign? = null,
+
+ @JsonProperty("stats")
+ val stats: Stats? = null,
+
+ @JsonProperty("keywordRedirect")
+ val keywordRedirect: KeywordRedirect? = null,
+
+ @JsonProperty("Metadata")
+ val metadata: Metadata? = null,
+
+ @JsonProperty("autoCorrectQuery")
+ val autoCorrectQuery: String? = null
+) : BaseResponse() {
+ fun getCategory(key: String): String? {
+ return if (categoryMap?.contains(key) == true) {
+ categoryMap[key]
+ } else
+ null
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Doc.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Doc.kt
new file mode 100644
index 0000000..7909ce8
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Doc.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Doc(
+ @JsonProperty("brand")
+ val brand: String? = null,
+
+ @JsonProperty("description")
+ val description: String? = null,
+
+ @JsonProperty("pid")
+ val pid: String? = null,
+
+ @JsonProperty("price")
+ val price: Double? = null,
+
+ @JsonProperty("sale_price")
+ val salePrice: Double? = null,
+
+ @JsonProperty("thumb_image")
+ val thumbImage: String? = null,
+
+ @JsonProperty("title")
+ val title: String? = null,
+
+ @JsonProperty("url")
+ val url: String? = null,
+
+ @JsonProperty("variants")
+ val variants: List? = null,
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/FacetCounts.kt b/src/main/java/com/bloomreach/discovery/api/model/core/FacetCounts.kt
new file mode 100644
index 0000000..b175090
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/FacetCounts.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+
+data class FacetCounts(
+
+ @JsonProperty("facet_fields")
+ val facetFields: LinkedHashMap>? = null,
+
+ @JsonProperty("facet_queries")
+ val facetQueries: LinkedHashMap? = null,
+// val facetQueries: LinkedHashMap>? = null,
+
+ @JsonProperty("facet_ranges")
+ val facetRanges: LinkedHashMap>? = null,
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/FacetFields.kt b/src/main/java/com/bloomreach/discovery/api/model/core/FacetFields.kt
new file mode 100644
index 0000000..a15718b
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/FacetFields.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+class FacetFields : BaseResponse() {
+ val name: String? = null
+
+ val count: Int = 0
+
+ @JsonProperty("cat_id")
+ val catId: String? = null
+
+ @JsonProperty("cat_name")
+ val catName: String? = null
+
+ @JsonProperty("crumb")
+ val crumb: String? = null
+
+ @JsonProperty("tree_path")
+ val treePath: String? = null
+
+ @JsonProperty("parent")
+ val parent: String? = null
+}
+
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/FacetRange.kt b/src/main/java/com/bloomreach/discovery/api/model/core/FacetRange.kt
new file mode 100644
index 0000000..8d0b33e
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/FacetRange.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+
+data class FacetRange(
+ val start: Any? = null,
+ val end: Any? = null,
+ val count: Int? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/KeywordRedirect.kt b/src/main/java/com/bloomreach/discovery/api/model/core/KeywordRedirect.kt
new file mode 100644
index 0000000..34de90c
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/KeywordRedirect.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+
+data class KeywordRedirect(
+ val originalQuery: String? = null,
+ val redirectedQuery: String? = null,
+ val redirectedUrl: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Metadata.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Metadata.kt
new file mode 100644
index 0000000..68d670c
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Metadata.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Metadata(
+ @JsonProperty("query")
+ val query: Query? = null
+) : BaseResponse()
+
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Modification.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Modification.kt
new file mode 100644
index 0000000..968876d
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Modification.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Modification(
+ @JsonProperty("mode")
+ val mode: String? = null,
+
+ @JsonProperty("value")
+ val value: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Query.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Query.kt
new file mode 100644
index 0000000..351ca0b
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Query.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Query(
+ @JsonProperty("modification")
+ val modification: Modification? = null,
+
+ @JsonProperty("didYouMean")
+ val didYouMean: List? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Response.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Response.kt
new file mode 100644
index 0000000..e4e681f
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Response.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Response(
+ @JsonProperty("docs")
+ val docs: List? = null,
+
+ @JsonProperty("numFound")
+ val numFound: Int? = null,
+
+ @JsonProperty("start")
+ val start: Int? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Stats.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Stats.kt
new file mode 100644
index 0000000..6f0f9e2
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Stats.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Stats(
+ @JsonProperty("stats_fields")
+ val statsFields: LinkedHashMap? = null
+) : BaseResponse() {
+ fun getStatsField(key: String): StatsField {
+ return statsFields?.get(key)!!
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/StatsField.kt b/src/main/java/com/bloomreach/discovery/api/model/core/StatsField.kt
new file mode 100644
index 0000000..908ad44
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/StatsField.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+
+data class StatsField(
+ val min: Double = 0.0,
+ val max: Double = 0.0
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/core/Variant.kt b/src/main/java/com/bloomreach/discovery/api/model/core/Variant.kt
new file mode 100644
index 0000000..c628624
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/core/Variant.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.core
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Variant(
+ @JsonProperty("skuid")
+ val skuId: List? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Category.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Category.kt
new file mode 100644
index 0000000..f04ff09
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Category.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Category(
+ @JsonProperty("cat_id")
+ val catId: String? = null,
+
+ @JsonProperty("cat_name")
+ val catName: String? = null,
+
+ @JsonProperty("count")
+ val count: Int = 0,
+
+ @JsonProperty("children")
+ val children: List? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Doc.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Doc.kt
new file mode 100644
index 0000000..a3e1de1
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Doc.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.bloomreach.discovery.api.model.core.Variant
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Doc(
+ @JsonProperty("brand")
+ val brand: String? = null,
+
+ @JsonProperty("description")
+ val description: String? = null,
+
+ @JsonProperty("pid")
+ val pid: String? = null,
+
+ @JsonProperty("price")
+ val price: Double? = null,
+
+ @JsonProperty("sale_price")
+ val salePrice: Double? = null,
+
+ @JsonProperty("thumb_image")
+ val thumbImage: String? = null,
+
+ @JsonProperty("title")
+ val title: String? = null,
+
+ @JsonProperty("url")
+ val url: String? = null,
+
+ @JsonProperty("variants")
+ val variants: List? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Facet.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Facet.kt
new file mode 100644
index 0000000..710de81
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Facet.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Facet(
+ @JsonProperty("category")
+ val category: List? = null,
+
+ @JsonProperty("fields")
+ val fields: List? = null,
+
+ @JsonProperty("ranges")
+ val ranges: List? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Field.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Field.kt
new file mode 100644
index 0000000..cf23ed7
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Field.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Field(
+ @JsonProperty("key")
+ val key: String? = null,
+
+ @JsonProperty("value")
+ val value: List? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Metadata.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Metadata.kt
new file mode 100644
index 0000000..9335591
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Metadata.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Metadata(
+ @JsonProperty("response")
+ val response: MetadataResponse? = null,
+
+ @JsonProperty("widget")
+ val widget: Widget? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/MetadataResponse.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/MetadataResponse.kt
new file mode 100644
index 0000000..aeb73e0
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/MetadataResponse.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class MetadataResponse(
+ @JsonProperty("fallback")
+ val fallback: String? = null,
+
+ @JsonProperty("personalized_results")
+ val personalizedResults: Boolean? = null,
+
+ @JsonProperty("recall")
+ val recall: String? = null
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Range.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Range.kt
new file mode 100644
index 0000000..a7e8cc0
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Range.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Range(
+ @JsonProperty("key")
+ val key: String? = null,
+
+ @JsonProperty("value")
+ val value: List? = null,
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/RecsAndPathwaysResponse.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/RecsAndPathwaysResponse.kt
new file mode 100644
index 0000000..ab344ce
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/RecsAndPathwaysResponse.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class RecsAndPathwaysResponse(
+ @JsonProperty("metadata")
+ val metadata: Metadata? = null,
+
+ @JsonProperty("response")
+ val response: Response? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Response.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Response.kt
new file mode 100644
index 0000000..241ba27
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Response.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.bloomreach.discovery.api.model.core.Doc
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Response(
+ @JsonProperty("docs")
+ val docs: List? = null,
+
+ @JsonProperty("numFound")
+ val numFound: Int? = null,
+
+ @JsonProperty("start")
+ val start: Int? = null
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/RpError.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/RpError.kt
new file mode 100644
index 0000000..e8942ec
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/RpError.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class RpError(
+ @JsonProperty("detail")
+ val detail: String? = null,
+)
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Value.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Value.kt
new file mode 100644
index 0000000..b8996cc
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Value.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Value(
+ @JsonProperty("name")
+ val name: String? = null,
+
+ @JsonProperty("count")
+ val count: Int = 0,
+
+ @JsonProperty("start")
+ val start: Any? = null,
+
+ @JsonProperty("end")
+ val end: Any? = null,
+
+) : BaseResponse()
diff --git a/src/main/java/com/bloomreach/discovery/api/model/rp/Widget.kt b/src/main/java/com/bloomreach/discovery/api/model/rp/Widget.kt
new file mode 100644
index 0000000..51c8c9c
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/rp/Widget.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.rp
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class Widget(
+ @JsonProperty("description")
+ val description: String? = null,
+
+ @JsonProperty("id")
+ val id: String? = null,
+
+ @JsonProperty("name")
+ val name: String? = null,
+
+ @JsonProperty("rid")
+ val rid: String? = null,
+
+ @JsonProperty("type")
+ val type: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/AttributeSuggestion.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/AttributeSuggestion.kt
new file mode 100644
index 0000000..209355d
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/AttributeSuggestion.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class AttributeSuggestion(
+ @JsonProperty("attributeType")
+ val attributeType: String? = null,
+
+ @JsonProperty("name")
+ val name: String? = null,
+
+ @JsonProperty("value")
+ val value: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/QueryContext.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/QueryContext.kt
new file mode 100644
index 0000000..e0d2583
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/QueryContext.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class QueryContext(
+ @JsonProperty("originalQuery")
+ val originalQuery: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/QuerySuggestion.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/QuerySuggestion.kt
new file mode 100644
index 0000000..0c7ab07
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/QuerySuggestion.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class QuerySuggestion(
+ @JsonProperty("displayText")
+ val displayText: String? = null,
+
+ @JsonProperty("query")
+ val query: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/SearchSuggestion.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/SearchSuggestion.kt
new file mode 100644
index 0000000..a49242f
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/SearchSuggestion.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.bloomreach.discovery.api.model.core.Variant
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class SearchSuggestion(
+ @JsonProperty("pid")
+ val pid: String? = null,
+
+ @JsonProperty("sale_price")
+ val salePrice: Double? = null,
+
+ @JsonProperty("thumb_image")
+ val thumbImage: String? = null,
+
+ @JsonProperty("title")
+ val title: String? = null,
+
+ @JsonProperty("url")
+ val url: String? = null,
+
+ @JsonProperty("variants")
+ val variants: List? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestResponse.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestResponse.kt
new file mode 100644
index 0000000..bf2f34d
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestResponse.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class SuggestResponse(
+ @JsonProperty("queryContext")
+ val queryContext: QueryContext? = null,
+
+ @JsonProperty("suggestionGroups")
+ val suggestionGroups: List? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestionGroup.kt b/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestionGroup.kt
new file mode 100644
index 0000000..b3fc98d
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/model/suggest/SuggestionGroup.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.model.suggest
+
+import com.bloomreach.discovery.api.model.BaseResponse
+import com.fasterxml.jackson.annotation.JsonProperty
+
+data class SuggestionGroup(
+ @JsonProperty("attributeSuggestions")
+ val attributeSuggestions: List? = null,
+
+ @JsonProperty("catalogName")
+ val catalogName: String? = null,
+
+ @JsonProperty("querySuggestions")
+ val querySuggestions: List? = null,
+
+ @JsonProperty("searchSuggestions")
+ val searchSuggestions: List? = null,
+
+ @JsonProperty("view")
+ val view: String? = null
+) : BaseResponse()
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/network/ApiProcessor.kt b/src/main/java/com/bloomreach/discovery/api/network/ApiProcessor.kt
new file mode 100644
index 0000000..10751ab
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/network/ApiProcessor.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.network
+
+import android.net.Uri
+import com.bloomreach.discovery.api.BrApi
+import com.bloomreach.discovery.api.listener.BrApiCompletionListener
+import com.bloomreach.discovery.api.model.BrApiError
+import com.bloomreach.discovery.api.model.Env
+import com.bloomreach.discovery.api.model.core.CoreResponse
+import com.bloomreach.discovery.api.model.rp.RecsAndPathwaysResponse
+import com.bloomreach.discovery.api.model.suggest.SuggestResponse
+import com.bloomreach.discovery.pixel.processpixel.FormatterUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.net.URL
+
+/**
+ * Class for adding global parameters to the request, processing all types of API call and providing callback once API returns
+ */
+internal class ApiProcessor {
+
+ private val CORE_API_PATH = "api/v1/core/"
+ private val SUGGEST_API_PATH = "api/v2/suggest"
+ private val WIDGET_API_PATH = "api/v2/widgets/"
+ private val SCEHEME = "https"
+
+ /**
+ * Method to format Core API parameters, execute the API and invoke the callback with appropriate result
+ * @param params Map of request parameters to be sent with the request
+ * @param brApiCompletionListener Interface object to provide success or failure callback
+ */
+ fun processCoreApi(
+ params: MutableMap,
+ brApiCompletionListener: BrApiCompletionListener
+ ) {
+ var uriBuilder = FormatterUtils.mapToUriBuilderForApi(params)
+ //add global request parameters
+ uriBuilder = addGlobalQuery(uriBuilder)
+ // append base endpoint for API call
+ addBaseUrlForCoreApi(uriBuilder)
+ //perform API
+ CoroutineScope(Dispatchers.IO).launch {
+ val client = RestClientApi()
+
+ val result = client.doApiCall(URL(uriBuilder.build().toString()), ApiType.CORE)
+ if (result is CoreResponse)
+ //invoke success callback
+ brApiCompletionListener.onBrApiSuccess(result)
+ else if (result is BrApiError)
+ //invoke failure callback
+ brApiCompletionListener.onBrApiFailure(result)
+ }
+ }
+
+ /**
+ * Method to format Suggest API parameters, execute the API and invoke the callback with appropriate result
+ * @param params Map of request parameters to be sent with the request
+ * @param brApiCompletionListener Interface object to provide success or failure callback
+ */
+ fun processSuggestApi(
+ params: MutableMap,
+ brApiCompletionListener: BrApiCompletionListener
+ ) {
+ val uriBuilder = FormatterUtils.mapToUriBuilderForApi(params)
+ //add global request parameters
+ addGlobalQuery(uriBuilder)
+ // append base endpoint for API call
+ addBaseUrlForSuggestApi(uriBuilder)
+ //perform API
+ CoroutineScope(Dispatchers.IO).launch {
+ val client = RestClientApi()
+ val result = client.doApiCall(URL(uriBuilder.build().toString()), ApiType.SUGGEST)
+ if (result is SuggestResponse)
+ //invoke success callback
+ brApiCompletionListener.onBrApiSuccess(result)
+ else if (result is BrApiError)
+ //invoke failure callback
+ brApiCompletionListener.onBrApiFailure(result)
+ }
+ }
+
+ /**
+ * Method to format Pathways APIs parameters, execute the API and invoke the callback with appropriate result
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ * @param widgetType Type of widget
+ * @param params Map of request parameters to be sent with the request
+ * @param brApiCompletionListener Interface object to provide success or failure callback
+ */
+ fun processRecsAndPathwaysApi(
+ widgetId: String,
+ widgetType: String,
+ params: MutableMap,
+ brApiCompletionListener: BrApiCompletionListener
+ ) {
+ val uriBuilder = FormatterUtils.mapToUriBuilderForApi(params)
+ //add global request parameters
+ addGlobalQuery(uriBuilder)
+ // append base endpoint for Pixel call
+ addBaseUrlForPathwaysApi(uriBuilder, widgetType, widgetId)
+ //perform API
+ CoroutineScope(Dispatchers.IO).launch {
+ val client = RestClientApi()
+ val result = client.doApiCall(URL(uriBuilder.build().toString()), ApiType.PATHWAYS)
+ if (result is RecsAndPathwaysResponse)
+ //invoke success callback
+ brApiCompletionListener.onBrApiSuccess(result)
+ else if (result is BrApiError)
+ //invoke failure callback
+ brApiCompletionListener.onBrApiFailure(result)
+ }
+ }
+
+ /**
+ * Method to add global request parameters to Uri Builder
+ * @param uriBuilder The Uri.Builder where the global request parameters will be added in required format
+ */
+ fun addGlobalQuery(uriBuilder: Uri.Builder): Uri.Builder {
+ uriBuilder.appendQueryParameter("account_id", BrApi.brApiRequest.accountId)
+ uriBuilder.appendQueryParameter("auth_key", BrApi.brApiRequest.authKey)
+ uriBuilder.appendQueryParameter("domain_key", BrApi.brApiRequest.domainKey)
+ uriBuilder.appendQueryParameter("request_id", FormatterUtils.generateRand())
+ uriBuilder.appendQueryParameter(
+ "_br_uid_2",
+ FormatterUtils.formatCookieValue(
+ BrApi.brApiRequest.uuid,
+ BrApi.brApiRequest.visitorType
+ )
+ )
+ uriBuilder.appendQueryParameter("ref_url", "")
+ if (!BrApi.brApiRequest.userId.isNullOrEmpty()) {
+ uriBuilder.appendQueryParameter("user_id", BrApi.brApiRequest.userId)
+ }
+ return uriBuilder
+ }
+
+ /**
+ * Method to generate Base EndPoint Url for Stage Env
+ *
+ * @param uriBuilder The Uri.Builder the Base Url Endpoint will be set
+ *
+ */
+ fun addBaseUrlForCoreApi(uriBuilder: Uri.Builder) {
+ uriBuilder.scheme(SCEHEME)
+ //check for env if stage or prod
+ when (BrApi.brApiRequest.environment) {
+ Env.STAGE -> uriBuilder.authority("staging-core.dxpapi.com")
+ Env.PROD -> uriBuilder.authority("core.dxpapi.com")
+ }
+ uriBuilder.appendEncodedPath(CORE_API_PATH)
+ }
+
+ /**
+ * Method to generate Base EndPoint Url for Stage Env
+ *
+ * @param uriBuilder The Uri.Builder the Base Url Endpoint will be set
+ *
+ */
+ fun addBaseUrlForSuggestApi(uriBuilder: Uri.Builder) {
+ uriBuilder.scheme(SCEHEME)
+ //check for env if stage or prod
+ when (BrApi.brApiRequest.environment) {
+ Env.STAGE -> uriBuilder.authority("staging-suggest.dxpapi.com")
+ Env.PROD -> uriBuilder.authority("suggest.dxpapi.com")
+ }
+ uriBuilder.appendEncodedPath(SUGGEST_API_PATH)
+ }
+
+ /**
+ * Method to generate Base EndPoint Url for Stage Env
+ *
+ * @param uriBuilder The Uri.Builder the Base Url Endpoint will be set
+ * @param widgetType Type of widget
+ * @param widgetId The ID of the widget, which can be found in the Widget Configurator in the Dashboard.
+ *
+ */
+ fun addBaseUrlForPathwaysApi(uriBuilder: Uri.Builder, widgetType: String, widgetId: String) {
+ uriBuilder.scheme(SCEHEME)
+ //check for env if stage or prod
+ when (BrApi.brApiRequest.environment) {
+ Env.STAGE -> uriBuilder.authority("pathways-staging.dxpapi.com")
+ Env.PROD -> uriBuilder.authority("pathways.dxpapi.com")
+ }
+ uriBuilder.appendEncodedPath("$WIDGET_API_PATH$widgetType/$widgetId")
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/network/ApiType.kt b/src/main/java/com/bloomreach/discovery/api/network/ApiType.kt
new file mode 100644
index 0000000..790a699
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/network/ApiType.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.network
+
+internal enum class ApiType {
+ CORE,
+ SUGGEST,
+ PATHWAYS
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/network/RestClientApi.kt b/src/main/java/com/bloomreach/discovery/api/network/RestClientApi.kt
new file mode 100644
index 0000000..b724d41
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/network/RestClientApi.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.network
+
+import android.util.Log
+import com.bloomreach.discovery.BuildConfig
+import com.bloomreach.discovery.api.BrApi
+import com.bloomreach.discovery.api.model.BrApiError
+import com.bloomreach.discovery.api.model.core.CoreResponse
+import com.bloomreach.discovery.api.model.rp.RecsAndPathwaysResponse
+import com.bloomreach.discovery.api.model.rp.RpError
+import com.bloomreach.discovery.api.model.suggest.SuggestResponse
+import com.fasterxml.jackson.databind.ObjectMapper
+import java.io.BufferedReader
+import java.io.InputStream
+import java.net.HttpURLConnection
+import java.net.URL
+
+/**
+ * Class to perform API call for API module
+ */
+internal class RestClientApi {
+
+ /**
+ * Method to HTTP call for all API
+ * @param url URL object containing request parameters
+ * @param type The type to identify of API call such as core, suggest.
+ * @return Any response object CoreResponse if API call success else return BrApiError object
+ */
+ fun doApiCall(url: URL, type: ApiType): Any? {
+ val inputStream: InputStream
+ try {
+ Log.i("$type API CALL:", url.toString())
+
+ // Create HttpURLConnection
+ val conn: HttpURLConnection = url.openConnection() as HttpURLConnection
+ // API request set to GET
+ conn.requestMethod = "GET";
+ // set none cache
+ conn.setRequestProperty("Cache-Control", "no-cache")
+ conn.setRequestProperty(
+ "User-Agent",
+ "Bloomreach/${BuildConfig.SDK_VERSION} " + System.getProperty("http.agent")
+ )
+
+ if (type == ApiType.PATHWAYS) {
+ //v2 API requires passing the auth-key as a request
+ conn.setRequestProperty("auth_key", BrApi.brApiRequest.authKey)
+ }
+
+ conn.defaultUseCaches = false;
+ conn.useCaches = false;
+ // Launch GET request
+ conn.connect()
+
+ val responseCode = conn.responseCode
+ Log.i("$type API CALL:", "responseCode: $responseCode")
+ if (responseCode in 200..299) { // success
+ // Receive response as inputStream
+ inputStream = conn.inputStream
+
+ if (inputStream != null) {
+ // Convert input stream to string
+ var result = inputStream.bufferedReader().use(BufferedReader::readText)
+ inputStream.close()
+ val responseMapper = ObjectMapper()
+ return when (type) {
+ ApiType.CORE -> responseMapper.readValue(result, CoreResponse::class.java)
+ ApiType.SUGGEST -> responseMapper.readValue(
+ result,
+ SuggestResponse::class.java
+ )
+ else -> responseMapper.readValue(result, RecsAndPathwaysResponse::class.java)
+ }
+ } else {
+ inputStream.close()
+ return BrApiError("Something went wrong", responseCode)
+ }
+ } else {
+ if (conn.errorStream != null) {
+ val result = conn.errorStream.bufferedReader().use(BufferedReader::readText)
+ conn.errorStream.close()
+ //covert error result to BrApiError object
+ print("error: $result")
+ return if (type == ApiType.PATHWAYS) {
+ val responseMapper = ObjectMapper()
+ val rpError = responseMapper.readValue(result, RpError::class.java)
+ BrApiError(rpError.detail ?: "Something went wrong", responseCode)
+ } else {
+ val responseMapper = ObjectMapper()
+ responseMapper.readValue(result, BrApiError::class.java)
+ }
+ }
+ return BrApiError("Something went wrong", responseCode)
+ }
+ } catch (err: Exception) {
+ Log.e("$type API CALL:", "Error: ${err.localizedMessage}")
+ return BrApiError("Something went wrong", 0)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/AutosuggestRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/AutosuggestRequest.kt
new file mode 100644
index 0000000..748dba4
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/AutosuggestRequest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+import java.util.stream.Collectors
+
+/**
+ * AutoSuggest Request Object class. Create the object of this class in order to
+ * send it with AutoSuggest API
+ */
+class AutosuggestRequest : RequestMap() {
+ // add hardcoded default parameters required for product search API
+ init {
+ setRequestType()
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for Auto Suggest API
+ * @return A reference request object
+ */
+ private fun setRequestType(): AutosuggestRequest {
+ return set(ApiConstants.REQUEST_TYPE, ApiConstants.REQUEST_TYPE_SUGGEST)
+ }
+
+ /**
+ * Method to set catalog views that you want to see in your suggestions.
+ *
+ * @param value catalogs views formatted in required format
+ *
+ * @return A reference request object
+ */
+ fun catalogViews(value: String): AutosuggestRequest {
+ return set(ApiConstants.CATALOG_VIEWS, value)
+ }
+
+ /**
+ * Method to set catalog views that you want to see in your suggestions.
+ * This method helps to format the catalogs views in required format
+ *
+ * @param values Map of catalog views attributes and its values
+ *
+ * @return A reference request object
+ */
+ fun catalogViews(values: Map): AutosuggestRequest {
+ //converts to my_product_catalog:store1|recipe:daily
+ val catalogViewsStr = values.entries
+ .stream()
+ .map { e -> e.key + ":" + e.value }
+ .collect(Collectors.joining("|"))
+ return catalogViews(catalogViewsStr)
+ }
+
+ /**
+ * Method to set search term for Search APIs
+ *
+ * @param q Partial search query that Autosuggest should operate on.
+ *
+ * @return A reference to the current Request object
+ */
+ fun searchTerm(q: String): AutosuggestRequest {
+ return set(ApiConstants.SEARCH_TERM, q)
+ }
+
+ /**
+ * The user agent of the device that's making the request.
+ *
+ * @param value user agent value
+ *
+ * @return A reference request object
+ */
+ fun userAgent(value: String): AutosuggestRequest {
+ return set(ApiConstants.USER_AGENT, value)
+ }
+
+ /**
+ * Method to set url
+ *
+ * @param value The title or name of the product.
+ *
+ * @return A reference to the current Request object
+ */
+ fun url(value: String): AutosuggestRequest {
+ return set(ApiConstants.URL, value)
+ }
+
+ /**
+ * Method to set user id of the customer
+ *
+ * @param value The universal customer ID of the user.
+ *
+ * @return A reference to the current Request object
+ */
+ fun userId(value: String?): AutosuggestRequest {
+ return set(ApiConstants.USER_ID, value)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/BestSellerRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/BestSellerRequest.kt
new file mode 100644
index 0000000..628e1cc
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/BestSellerRequest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+
+/**
+ * BestSeller Request Object class. Create the object of this class in order to
+ * send it with BestSeller API
+ */
+class BestSellerRequest : SearchRequest() {
+
+ // add hardcoded default parameters required for BestSeller API
+ init {
+ setRequestType()
+ setSearchType()
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for BestSeller API
+ * @return A reference request object
+ */
+ private fun setRequestType(): BestSellerRequest {
+ return set(ApiConstants.REQUEST_TYPE, ApiConstants.REQUEST_TYPE_SEARCH)
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for BestSeller API
+ * @return A reference request object
+ */
+ private fun setSearchType(): BestSellerRequest {
+ return set(ApiConstants.SEARCH_TYPE, ApiConstants.SEARCH_TYPE_BESTSELLER)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/CategorySearchRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/CategorySearchRequest.kt
new file mode 100644
index 0000000..0c17408
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/CategorySearchRequest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+
+class CategorySearchRequest() : SearchRequest() {
+
+ // add hardcoded default parameters required for Category Search API
+ init {
+ setRequestType()
+ setSearchType()
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for Category Search API
+ * @return A reference request object
+ */
+ private fun setRequestType(): CategorySearchRequest {
+ return set(ApiConstants.REQUEST_TYPE, ApiConstants.REQUEST_TYPE_SEARCH)
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for Category Search API
+ * @return A reference request object
+ */
+ private fun setSearchType(): CategorySearchRequest {
+ return set(ApiConstants.SEARCH_TYPE, ApiConstants.SEARCH_TYPE_CATEGORY)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/ContentSearchRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/ContentSearchRequest.kt
new file mode 100644
index 0000000..0bbfada
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/ContentSearchRequest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+
+/**
+ * Content Search Request Object class. Create the object of this class in order to
+ * send it with Content Search API
+ */
+class ContentSearchRequest() : SearchRequest() {
+
+ // add hardcoded default parameters required for content search API
+ init {
+ setRequestType()
+ setSearchType()
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for content search API
+ * @return A reference request object
+ */
+ private fun setRequestType(): ContentSearchRequest {
+ return set(ApiConstants.REQUEST_TYPE, ApiConstants.REQUEST_TYPE_SEARCH)
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for content search API
+ * @return A reference request object
+ */
+ private fun setSearchType(): ContentSearchRequest {
+ return set(ApiConstants.SEARCH_TYPE, ApiConstants.SEARCH_TYPE_KEYWORD)
+ }
+
+ /**
+ * Method to set catalog name.
+ * Named identifier of the catalog. A catalog is a grouping of items into a broader category
+ * such as blogs, videos, etc. A catalog is a representation
+ * of a group of items and must have a unique name, that is also unique to a domain
+ * (if you have multiple sites).
+ *
+ * @param value catalog name
+ *
+ * @return A reference request object
+ */
+ fun catalogName(value: String): ContentSearchRequest {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("Catalog name cannot be empty")
+ }
+ return set(ApiConstants.CATALOG_NAME, value)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/Operator.kt b/src/main/java/com/bloomreach/discovery/api/request/Operator.kt
new file mode 100644
index 0000000..2f6aefa
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/Operator.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+/**
+ * ENUM to specify AND / OR operator for applying filters/fq/efq
+ */
+enum class Operator {
+ OR,
+ AND
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/ProductSearchRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/ProductSearchRequest.kt
new file mode 100644
index 0000000..c6dcdef
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/ProductSearchRequest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants.REQUEST_TYPE
+import com.bloomreach.discovery.api.ApiConstants.REQUEST_TYPE_SEARCH
+import com.bloomreach.discovery.api.ApiConstants.SEARCH_TYPE
+import com.bloomreach.discovery.api.ApiConstants.SEARCH_TYPE_KEYWORD
+
+/**
+ * Product Search Request Object class. Create the object of this class in order to
+ * send it with Product Search API
+ */
+class ProductSearchRequest(): SearchRequest() {
+
+ // add hardcoded default parameters required for product search API
+ init {
+ setRequestType()
+ setSearchType()
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for product search API
+ * @return A reference request object
+ */
+ private fun setRequestType(): ProductSearchRequest {
+ return set(REQUEST_TYPE, REQUEST_TYPE_SEARCH)
+ }
+
+ /**
+ * Method to set hardcoded default parameters required for product search API
+ * @return A reference request object
+ */
+ private fun setSearchType(): ProductSearchRequest {
+ return set(SEARCH_TYPE, SEARCH_TYPE_KEYWORD)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/RequestMap.kt b/src/main/java/com/bloomreach/discovery/api/request/RequestMap.kt
new file mode 100644
index 0000000..d78d188
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/RequestMap.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+/**
+ * RequestMap class base class for all Request class, which stores the parameters in the map
+ */
+sealed class RequestMap {
+ private val requestMap = mutableMapOf()
+
+ /**
+ * Method to set query parameter as key and value.
+ * If the key is already set, the value will get replaced
+ *
+ * @param key The name of the query parameter
+ * @param value The value that is used for the query parameter value. If the value is
+ * null
the key will be removed
+ *
+ * @return A reference to the current Request object
+ */
+ fun set(key: String, value: String?): T {
+ if (key.isEmpty()) {
+ throw IllegalArgumentException("Key cannot be empty")
+ }
+
+ if (value.isNullOrEmpty()) {
+ requestMap.remove(key)
+ } else {
+ requestMap[key] = value
+ }
+ return this as T
+ }
+
+ /**
+ * Method to add multiple query parameter for same key
+ *
+ * @param key The name of the query parameter
+ * @param value The value that is used for the query parameter value
+ *
+ * @return A reference to the current Request object
+ */
+ fun add(key: String, value: String?): T {
+ if (key.isEmpty()) {
+ throw IllegalArgumentException("Key cannot be empty")
+ }
+
+ if (!value.isNullOrEmpty()) {
+ if (requestMap.containsKey(key)) {
+ val mapValue = requestMap[key]
+ if (mapValue is ArrayList<*>) {
+ (mapValue as ArrayList).add(value)
+ requestMap[key] = mapValue
+ } else if (mapValue is String) {
+ val list = ArrayList()
+ list.add(mapValue as String)
+ list.add(value)
+ requestMap[key] = list
+ }
+ } else {
+ requestMap[key] = value
+ }
+ } else {
+ requestMap.remove(key)
+ }
+ return this as T
+ }
+
+ /**
+ * Method to get Request Map object
+ *
+ * @return A reference request map object
+ */
+ fun getMap(): MutableMap {
+ return requestMap
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/SearchRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/SearchRequest.kt
new file mode 100644
index 0000000..03ddf4a
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/SearchRequest.kt
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+import com.bloomreach.discovery.api.ApiConstants.DEFAULT_ROWS
+import com.bloomreach.discovery.api.ApiConstants.DEFAULT_START
+import com.bloomreach.discovery.api.ApiConstants.FL
+import java.util.stream.Collectors
+
+/**
+ * This class is base class for Search APIs request it provides parameters to be sent with Search APIs
+ */
+sealed class SearchRequest() : RequestMap() {
+
+ // add default parameters required for search API
+ init {
+ rows(DEFAULT_ROWS)
+ start(DEFAULT_START)
+ }
+
+ /**
+ * Method to set rows Search APIs
+ *
+ * @param rows The number of matching items to return per results page in the API response. The maximum value is 200.
+ *
+ * @return A reference to the current Request object
+ */
+ fun rows(rows: Int): T {
+ return set(ApiConstants.ROWS, rows.toString())
+ }
+
+ /**
+ * Method to set start Search APIs
+ *
+ * @param start The number of the first item on a page of results. For example, the first item on the first page is 0, making the start value also 0.
+ *
+ * @return A reference to the current Request object
+ */
+ fun start(start: Int): T {
+ return set(ApiConstants.START, start.toString())
+ }
+
+ /**
+ * Method to set search term for Search APIs
+ *
+ * @param q Query key for Searching
+ *
+ * @return A reference to the current Request object
+ */
+ fun searchTerm(q: String): T {
+ return set(ApiConstants.SEARCH_TERM, q)
+ }
+
+ /**
+ * Method to set Field List for Search APIs
+ *
+ * @param value The comma separated attributes that you want returned in your API response, such as product IDs and prices.
+ *
+ * @return A reference to the current Request object
+ */
+ fun fl(value: String): T {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("")
+ }
+ return set(FL, value)
+ }
+
+ /**
+ * Method to set Field List for Search APIs
+ *
+ * @param values The attributes list that you want returned in your API response, such as product IDs and prices.
+ *
+ * @return A reference to the current Request object
+ */
+ fun fl(values: List): T {
+ var flString = ""
+ if (values.isNullOrEmpty()) {
+ throw IllegalArgumentException()
+ } else if (values.size == 1) {
+ flString = values[0]
+ } else if (values.size > 1) {
+ flString = values.joinToString(",")
+ }
+ return fl(flString)
+ }
+
+ /**
+ * Method to set Field List for Search APIs
+ *
+ * @param values The attributes array that you want returned in your API response, such as product IDs and prices.
+ *
+ * @return A reference to the current Request object
+ */
+ fun fl(values: Array): T {
+ var flString = ""
+ if (values.isNullOrEmpty()) {
+ throw IllegalArgumentException()
+ } else if (values.size == 1) {
+ flString = values[0]
+ } else if (values.size > 1) {
+ flString = values.joinToString(",")
+ }
+ return fl(flString)
+ }
+
+ /**
+ * Method to set sort parameter. You can alter the sequence in which products are
+ * displayed by passing the sort parameter.
+ *
+ * @param value Formatted value for sort parameter. 'price+asc'
+ *
+ * @return A reference to the current Request object
+ */
+ fun sort(value: String?): T {
+ return set(ApiConstants.SORT, value)
+ }
+
+ /**
+ * Method to set sort parameter. You can alter the sequence in which products are
+ * displayed by passing the sort parameter.
+ *
+ * @param sort sort object contains value for paramter on which sorting is to be done and SortOrder specifies the Order Asc or Desc
+ *
+ * @return A reference to the current Request object
+ */
+ fun sort(sort: Sort): T {
+ return sort(sortString(sort))
+ }
+
+ /**
+ * Method to set sort parameter. You can alter the sequence in which products are
+ * displayed by passing the sort parameter.
+ *
+ * @param values list of sort objects contains value for paramter on which sorting is to be done and SortOrder specifies the Order Asc or Desc
+ *
+ * @return A reference to the current Request object
+ */
+ fun sort(values: List?): T {
+ var sortString: String? = null
+ if (values.isNullOrEmpty()) {
+ sortString = null
+ } else if (values.size == 1) {
+ sortString = sortString(values[0]!!)
+ } else if (values.size > 1) {
+ sortString = sortString(values)
+ }
+ return sort(sortString)
+ }
+
+ /**
+ * Method to format Sort object in required String format
+ *
+ * @param sort object
+ *
+ * @return Formatted string for sort parameter
+ */
+ private fun sortString(sort: Sort):String {
+ return "${sort.value}+${sort.order.value}"
+ }
+
+ /**
+ * Method to format List of Sort object in required String format
+ *
+ * @param sortList list of sort object
+ *
+ * @return Formatted string for sort parameter
+ */
+ private fun sortString(sortList: List): String {
+ return sortList.stream()
+ .map { sort -> sortString(sort) }
+ .collect(Collectors.joining(","))
+ }
+
+ /**
+ * Method to set sort parameter. You can alter the sequence in which products are
+ * displayed by passing the sort parameter.
+ *
+ * @param values Array of sort string
+ *
+ * @return A reference to the current Request object
+ */
+ fun sort(values: Array?): T {
+ var sortString: String? = null
+ if (values.isNullOrEmpty()) {
+ sortString = null
+ } else if (values.size == 1) {
+ sortString = values[0]
+ } else if (values.size > 1) {
+ sortString = values.joinToString(",")
+ }
+ return sort(sortString)
+ }
+
+ /**
+ * Method to set facetPrefix for Search APIs.
+ * The facet.prefix parameter limits faceting to terms that start with the specified string prefix.
+ *
+ * @param facetName The name of the facet
+ * @param prefixValue value for facet prefix
+ *
+ * @return A reference to the current Request object
+ */
+ fun facetPrefix(facetName: String, prefixValue: String): T {
+ val key = "f.${facetName}.facet.prefix"
+ return set(key, prefixValue)
+ }
+
+ /**
+ * Method to set facetPrefix for Widget APIs
+ * The facet.prefix parameter limits faceting to terms that start with the specified string prefix.
+ *
+ * @param facetName The name of the facet
+ * @param prefixValue value for facet prefix
+ *
+ * @return A reference to the current Request object
+ */
+ fun facetPrefixWidget(facetName: String, prefixValue: String): T {
+ return set("facet.prefix", "$facetName:$prefixValue")
+ }
+
+ /**
+ * Method to set fq
+ * The fq parameter is an optional parameter that you can add to an API request to filter the results.
+ *
+ * @param value The formatted value to be passed to fq parameter
+ *
+ * @return A reference to the current Request object
+ */
+ fun fq(value: String): T {
+ return add(ApiConstants.FQ, value)
+ }
+
+ /**
+ * Method to set fq with attribute and its single value
+ * The fq parameter is an optional parameter that you can add to an API request to filter the results.
+ *
+ * @param attribute The attribute for fq
+ * @param value The value of the attribute
+ *
+ * @return A reference to the current Request object
+ */
+ fun fq(attribute: String, value: String): T {
+ val fqValue = "$attribute:\"${value}\""
+ return fq(fqValue)
+ }
+
+ /**
+ * Method to set fq with attribute and its multiple value
+ * The fq parameter is an optional parameter that you can add to an API request to filter the results.
+ *
+ * @param attribute The attribute for fq
+ * @param values The list of multiple possible values for given attribute.
+ *
+ * @return A reference to the current Request object
+ */
+ fun fq(attribute: String, values: List): T {
+ var str = ""
+ if (values.size > 1) {
+ for ((index, value) in values.withIndex()) {
+ str += "\"${value}\""
+ if (index != values.size - 1) {
+ str += " OR "
+ }
+ }
+ } else {
+ str += values[0]
+ }
+ return fq(attribute, str)
+ }
+
+ /**
+ * Method to set stats.field
+ * The stats.field allows you to display the maximum and minimum values of any numeric field in your data set for a user query.
+ *
+ * @param value The formatted stats.field value
+ *
+ * @return A reference to the current Request object
+ */
+ fun statsField(value: String?): T {
+ return set(ApiConstants.STATS_FIELD, value)
+ }
+
+ /**
+ * Method to set stats.field
+ * The stats.field allows you to display the maximum and minimum values of any numeric field in your data set for a user query.
+ *
+ * @param values The list of stats.field values
+ *
+ * @return A reference to the current Request object
+ */
+ fun statsField(values: List): T {
+ var sfString: String? = null
+ if (values.isNullOrEmpty()) {
+ sfString = null
+ } else if (values.size == 1) {
+ sfString = values[0]
+ } else if (!values.isNullOrEmpty() && values.size > 1) {
+ sfString = values.joinToString(",")
+ }
+ return statsField(sfString)
+ }
+
+ /**
+ * Method to set stats.field
+ * The stats.field allows you to display the maximum and minimum values of any numeric field in your data set for a user query.
+ *
+ * @param values The array of stats.field values
+ *
+ * @return A reference to the current Request object
+ */
+ fun statsField(values: Array): T {
+ var sfString: String? = null
+ if (values.isNullOrEmpty()) {
+ sfString = null
+ } else if (values.size == 1) {
+ sfString = values[0]
+ } else if (!values.isNullOrEmpty() && values.size > 1) {
+ sfString = values.joinToString(",")
+ }
+ return statsField(sfString)
+ }
+
+ fun efq(value: String): T {
+ return set(ApiConstants.EFQ, value)
+ }
+
+ fun efq(attribute: String, value: String): T {
+ return efq("$attribute:(\"${value}\")")
+ }
+
+ fun efq(attribute: String, value: String, isNot: Boolean): T {
+ return if(isNot) {
+ efq("-$attribute:(\"${value}\")")
+ } else {
+ efq("$attribute:(\"${value}\")")
+ }
+ }
+
+ /**
+ * Method to set efq with attribute and its multiple values
+ *
+ * @param attribute The attribute for efq
+ * @param values The list of multiple possible values for given attribute.
+ * @param operator 'AND' or 'OR' operator for values
+ *
+ * @return A reference to the current Request object
+ */
+ fun efq(attribute: String, values: List, operator: Operator): T {
+ var str = ""
+ if (values.size > 1) {
+ //attribute:("value 1" OR "value 2")
+ for ((index, value) in values.withIndex()) {
+ str += "\"${value}\"" //TODO
+ if (index != values.size - 1) {
+ if (operator == Operator.OR) {
+ str += " OR "
+ } else if (operator == Operator.AND) {
+ str += " AND "
+ }
+ }
+ }
+ } else {
+ str += values[0]
+ }
+ return efq(attribute, str)
+ }
+
+ /**
+ * Method to set efq with multiple attribute and values
+ *
+ * @param values The map of multiple possible attributes and its values.
+ * @param operator 'AND' or 'OR' operator for attributes
+ *
+ * @return A reference to the current Request object
+ */
+ fun efq(values: Map, operator: Operator): T {
+ var formattedStr = ""
+ //attribute1:("value") OR attribute2:("value")
+ if (values.size > 1) {
+ formattedStr = if (operator == Operator.OR) {
+ values.entries.stream()
+ .map { e -> "${e.key}:(\"${e.value}\")" }
+ .collect(Collectors.joining(" OR "))
+ } else {
+ values.entries.stream()
+ .map { e -> "${e.key}:(\"${e.value}\")" }
+ .collect(Collectors.joining(" AND "))
+ }
+ }
+ return efq(formattedStr)
+ }
+
+ /**
+ * Method to set facet.range parameter
+ * Use the facet.range parameter to include ranged facets
+ *
+ * @param value value for the facet range
+ *
+ * @return A reference to the current Request object
+ */
+ fun facetRange(value: String?): T {
+ return set(ApiConstants.FACET_RANGE, value)
+ }
+
+ /**
+ * Method to set list of facet.range parameter
+ * Use the facet.range parameter to include ranged facets
+ *
+ * @param values list for the facet range
+ *
+ * @return A reference to the current Request object
+ */
+ fun facetRange(values: List): T {
+ var frString: String? = null
+ if (values.isNullOrEmpty()) {
+ frString = null
+ } else if (values.size == 1) {
+ frString = values[0]
+ } else if (values.size > 1) {
+ frString = values.joinToString(",")
+ }
+ return facetRange(frString)
+ }
+
+ /**
+ * BOPIS-specific parameter to specify the end-customer's latitude-longitude.
+ *
+ * @param value value for lat long in format 'lat,long'
+ *
+ * @return A reference to the current Request object
+ */
+ fun latLong(value: String?): T {
+ return set(ApiConstants.LAT_LONG, value)
+ }
+
+ /**
+ * Method to set View Id
+ *
+ * @param value A unique identifier for a specific view of your product catalog.
+ *
+ * @return A reference to the current Request object
+ */
+ fun viewId(value: String?): T {
+ return set(ApiConstants.VIEW_ID, value)
+ }
+
+ /**
+ * Method to set user id of the customer
+ *
+ * @param value The universal customer ID of the user.
+ *
+ * @return A reference to the current Request object
+ */
+ fun userId(value: String?): T {
+ return set(ApiConstants.USER_ID, value)
+ }
+
+ /**
+ * Method to set widget Id, The widget_id provided in the Dashboard for the Dynamic Widgets
+ * feature, which is used to provided curated results.
+ *
+ * @param value value for widget id
+ *
+ * @return A reference to the current Request object
+ */
+ fun widgetId(value: String?): T {
+ return set(ApiConstants.WIDGET_ID, value)
+ }
+
+ /**
+ * Method to set title
+ *
+ * @param value The title or name of the product.
+ *
+ * @return A reference to the current Request object
+ */
+ fun title(value: String?): T {
+ return set(ApiConstants.TITLE, value)
+ }
+
+ /**
+ * Method to set url
+ *
+ * @param value The title or name of the product.
+ *
+ * @return A reference to the current Request object
+ */
+ fun url(value: String): T {
+ return set(ApiConstants.URL, value)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/Sort.kt b/src/main/java/com/bloomreach/discovery/api/request/Sort.kt
new file mode 100644
index 0000000..cc95dc5
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/Sort.kt
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+
+/**
+ * Data class for Sort object
+ */
+data class Sort(val value: String, val order: SortOrder)
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/SortOrder.kt b/src/main/java/com/bloomreach/discovery/api/request/SortOrder.kt
new file mode 100644
index 0000000..a7baf68
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/SortOrder.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+/**
+ * ENUM to specify Sort Order Ascending or descending for Search Request
+ */
+enum class SortOrder(val value: String) {
+ ASCENDING("asc"),
+ DESCENDING("desc")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/WidgetApiType.kt b/src/main/java/com/bloomreach/discovery/api/request/WidgetApiType.kt
new file mode 100644
index 0000000..3d56176
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/WidgetApiType.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+/**
+ * Widget TYPE ENUM to specify which type on widget API needs to be called.
+ * This gets added as Path parameter to he request
+ */
+enum class WidgetApiType(val value: String) {
+ ITEM("item"),
+ CATEGORY("category"),
+ KEYWORD("keyword"),
+ PERSONALIZED("personalized"),
+ GLOBAL("global")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/api/request/WidgetRequest.kt b/src/main/java/com/bloomreach/discovery/api/request/WidgetRequest.kt
new file mode 100644
index 0000000..dbb4885
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/api/request/WidgetRequest.kt
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.api.request
+
+import com.bloomreach.discovery.api.ApiConstants
+import com.bloomreach.discovery.api.ApiConstants.DEFAULT_FACET_FLAG
+
+/**
+ * Widget API Request Object class. Create the object of this class in order to
+ * send it with Recommendation Search API
+ */
+class WidgetRequest() : RequestMap() {
+
+ // add default parameters required for search API
+ init {
+ facet(DEFAULT_FACET_FLAG)
+ }
+
+ /**
+ * Method to set rows
+ *
+ * @param rows The number of matching items to return per results page in the API response. The maximum value is 200.
+ *
+ * @return A reference to the current Request object
+ */
+ fun rows(rows: Int): WidgetRequest {
+ return set(ApiConstants.ROWS, rows.toString())
+ }
+
+ /**
+ * Method to set start
+ *
+ * @param start The number of the first item on a page of results. For example, the first item on the first page is 0, making the start value also 0.
+ *
+ * @return A reference to the current Request object
+ */
+ fun start(start: Int): WidgetRequest {
+ return set(ApiConstants.START, start.toString())
+ }
+
+ /**
+ * Method to set itemIds for Item Based Widget API
+ *
+ * @param value Specifies access to a particular set of items. For product catalog, this would be the PID(s).
+ *
+ * @return A reference of Request object
+ */
+ fun itemIds(value: String): WidgetRequest {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("ItemIds can't be empty")
+ }
+ return set(ApiConstants.ITEM_IDS, value)
+ }
+
+ /**
+ * Method to set itemIds for Item Based Widget API
+ *
+ * @param values Array of access to a particular set of items. For product catalog, this would be the PID(s).
+ *
+ * @return A reference to the current Request object
+ */
+ fun itemIds(values: Array): WidgetRequest {
+ var itemIds = ""
+ if (values.isNullOrEmpty()) {
+ throw IllegalArgumentException()
+ } else if (values.size == 1) {
+ itemIds = values[0]
+ } else if (values.size > 1) {
+ itemIds = values.joinToString(",")
+ }
+ return itemIds(itemIds)
+ }
+
+ /**
+ * Method to set itemIds for Item Based Widget API
+ *
+ * @param values List of access to a particular set of items. For product catalog, this would be the PID(s).
+ *
+ * @return A reference to the current Request object
+ */
+ fun itemIds(values: List): WidgetRequest {
+ var itemIds = ""
+ if (values.isNullOrEmpty()) {
+ throw IllegalArgumentException("ItemIds can't be empty")
+ } else if (values.size == 1) {
+ itemIds = values[0]
+ } else if (values.size > 1) {
+ itemIds = values.joinToString(",")
+ }
+ return itemIds(itemIds)
+ }
+
+ /**
+ * Method to set cat Id for Category Based Widget API
+ *
+ * @param value Your site's category ID.
+ *
+ * @return A reference of Request object
+ */
+ fun catId(value: String): WidgetRequest {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("Category Id can't be empty")
+ }
+ return set(ApiConstants.CAT_ID, value)
+ }
+
+ /**
+ * Method to set search query for Keyword and Personalization Based Widget API
+ *
+ * @param value search query.
+ *
+ * @return A reference of Request object
+ */
+ fun query(value: String): WidgetRequest {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("Search query Id can't be empty")
+ }
+ return set(ApiConstants.QUERY, value)
+ }
+
+ /**
+ * Method to set user id required for Personalization-based Recommendation widgets
+ *
+ * @param value The universal customer ID of the user.
+ *
+ * @return A reference of Request object
+ */
+ fun userId(value: String): WidgetRequest {
+ if (value.isEmpty()) {
+ throw IllegalArgumentException("User Id can't be empty")
+ }
+ return set(ApiConstants.USER_ID, value)
+ }
+
+ /**
+ * Method to set context id Item-based Recommendation widget API
+ *
+ * @param value takes a single product ID for producing Context-based merchandising results for the widget.
+ *
+ * @return A reference of Request object
+ */
+ fun contextId(value: String?): WidgetRequest {
+ return set(ApiConstants.CONTEXT_ID, value)
+ }
+
+ /**
+ * Method to set fields Recommendation widget APIs
+ *
+ * @param value A formatted comma-separated list of fields to be sent in the request.
+ *
+ * @return A reference of Request object
+ */
+ fun fields(value: String?): WidgetRequest {
+ return set(ApiConstants.FIELDS, value)
+ }
+
+ /**
+ * Method to set fields Recommendation widget API
+ *
+ * @param values List of fields to be sent in the request.
+ *
+ * @return A reference of Request object
+ */
+ fun fields(values: List?): WidgetRequest {
+ var fieldString: String? = null
+ if (values.isNullOrEmpty()) {
+ fieldString = null
+ } else if (values.size == 1) {
+ fieldString = values[0]
+ } else if (values.size > 1) {
+ fieldString = values.joinToString(",")
+ }
+ return fields(fieldString)
+ }
+
+ /**
+ * Method to set fields Recommendation widget API
+ *
+ * @param values Array of fields to be sent in the request.
+ *
+ * @return A reference of Request object
+ */
+ fun fields(values: Array?): WidgetRequest {
+ var fieldString: String? = null
+ if (values.isNullOrEmpty()) {
+ fieldString = null
+ } else if (values.size == 1) {
+ fieldString = values[0]
+ } else if (values.size > 1) {
+ fieldString = values.joinToString(",")
+ }
+ return fields(fieldString)
+ }
+
+ /**
+ * Method to set filter facet for Keyword and Category Recommendation widget APIs
+ *
+ * @param value A formatted value to be sent in the request.
+ *
+ * @return A reference of Request object
+ */
+ fun filterFacet(value: String): WidgetRequest {
+ return add(ApiConstants.FILTER_FACET, value)
+ }
+
+ /**
+ * Method to set filter facet for Keyword and Category Recommendation widget APIs
+ *
+ * @param attribute filter facet attribute
+ * @param value value for the given attribute
+ *
+ * @return A reference of Request object
+ */
+ fun filterFacet(attribute: String, value: String): WidgetRequest {
+ return filterFacet("$attribute:\"${value}\"")
+ }
+
+ /**
+ * Method to set filter facet for Keyword and Category Recommendation widget APIs
+ *
+ * @param attribute filter facet attribute
+ * @param values The list of multiple possible values for given attribute.
+ * @param operator 'AND' or 'OR' operator for values
+ *
+ * @return A reference of Request object
+ */
+ fun filterFacet(attribute: String, values: List, operator: Operator): WidgetRequest {
+ var str = "$attribute:"
+ if (values.size > 1) {
+ //attribute:"value 1" OR "value 2"
+ for ((index, value) in values.withIndex()) {
+ str += "\"${value}\""
+ if (index != values.size - 1) {
+ if (operator == Operator.OR) {
+ str += " OR "
+ } else if (operator == Operator.AND) {
+ str += " AND "
+ }
+ }
+ }
+ return filterFacet(str)
+ } else {
+ return filterFacet(attribute, values[0])
+ }
+ }
+
+ /**
+ * Method to View Id
+ *
+ * @param value A unique identifier for a specific view of your product catalog.
+ *
+ * @return A reference of Request object
+ */
+ fun viewId(value: String?): WidgetRequest {
+ return set(ApiConstants.VIEW_ID, value)
+ }
+
+ /**
+ * Method to apply facet
+ *
+ * @param value Boolean value to enable or disable facet filtering
+ *
+ * @return A reference of Request object
+ */
+ fun facet(value: Boolean): WidgetRequest {
+ return set(ApiConstants.FACET, value.toString())
+ }
+
+ /**
+ * Method to set filter for Recommendation widget APIs
+ *
+ * @param value The formatted value for given
+ *
+ * @return A reference of Request object
+ */
+ fun filter(value: String): WidgetRequest {
+ return add(ApiConstants.FILTER, value)
+ }
+
+ /**
+ * Method to set filter for Recommendation widget APIs
+ *
+ * @param attribute filter attribute value
+ * @param value The formatted value for given attribute
+ * @param isNot if '-' is needed at start pass value as true. Default set to false,
+ *
+ * @return A reference of Request object
+ */
+ fun filter(attribute: String, value: String, isNot: Boolean = false): WidgetRequest {
+ if (isNot) {
+ return filter("-($attribute:(\"${value}\"))")
+ } else {
+ return filter("($attribute:(\"${value}\"))")
+ }
+ }
+
+ /**
+ * Method to set filter with range for Recommendation widget APIs
+ *
+ * @param attribute filter attribute value
+ * @param startRange The start value for range filter
+ * @param endRange The end value for range filter
+ * @param isNot if '-' is needed at start pass value as true. Default set to false,
+ *
+ * @return A reference of Request object
+ */
+ fun filter(
+ attribute: String,
+ startRange: String,
+ endRange: String,
+ isNot: Boolean = false
+ ): WidgetRequest {
+ if (isNot) {
+ return filter("-($attribute:[\"${startRange}\" TO \"${endRange}\"])")
+ } else {
+ return filter("($attribute:[\"${startRange}\" TO \"${endRange}\"])")
+ }
+ }
+
+ /**
+ * Method to set filter with range for Recommendation widget APIs
+ *
+ * @param attribute filter attribute value
+ * @param startRange The start value for range filter
+ * @param endRange The end value for range filter
+ * @param isNot if '-' is needed at start pass value as true. Default set to false,
+ *
+ * @return A reference of Request object
+ */
+ fun filter(
+ attribute: String,
+ startRange: Int,
+ endRange: Int,
+ isNot: Boolean = false
+ ): WidgetRequest {
+ return filter(attribute, startRange.toString(), endRange.toString(), isNot)
+ }
+
+ /**
+ * Method to set filter with range for Recommendation widget APIs
+ *
+ * @param attribute filter attribute value
+ * @param range The range(0..100) value for range filter
+ * @param isNot if '-' is needed at start pass value as true. Default set to false,
+ *
+ * @return A reference of Request object
+ */
+ fun filter(attribute: String, range: IntRange, isNot: Boolean = false): WidgetRequest {
+ return filter(attribute, range.first.toString(), range.last.toString(), isNot)
+ }
+
+ /**
+ * Method to set filter with attribute and its multiple values
+ *
+ * @param attribute The attribute for filter
+ * @param values The list of multiple possible values for given attribute.
+ * @param operator 'AND' or 'OR' operator for values
+ *
+ * @return A reference to the current Request object
+ */
+ fun filter(attribute: String, values: List, operator: Operator): WidgetRequest {
+ var str = ""
+ if (values.size > 1) {
+ //attribute:("value 1" OR "value 2")
+ for ((index, value) in values.withIndex()) {
+ str += "\"${value}\""
+ if (index != values.size - 1) {
+ if (operator == Operator.OR) {
+ str += " OR "
+ } else if (operator == Operator.AND) {
+ str += " AND "
+ }
+ }
+ }
+ } else {
+ str += "\"${values[0]}\""
+ }
+
+ return add(ApiConstants.FILTER, "$attribute:($str)")
+ }
+
+ /**
+ * Method to set facetPrefix for Widget APIs
+ * The facet.prefix parameter limits faceting to terms that start with the specified string prefix.
+ *
+ * @param facetName The name of the facet
+ * @param prefixValue value for facet prefix
+ *
+ * @return A reference to the current Request object
+ */
+ fun facetPrefix(facetName: String, prefixValue: String): WidgetRequest {
+ return set("facet.prefix", "$facetName:$prefixValue")
+ }
+
+ /**
+ * Method to set url
+ *
+ * @param value The absolute URL of the page where the request is initiated. Do not use a relative URL.
+ *
+ * @return A reference to the current Request object
+ */
+ fun url(value: String): WidgetRequest {
+ return set(ApiConstants.URL, value)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/PixelTracker.kt b/src/main/java/com/bloomreach/discovery/pixel/PixelTracker.kt
new file mode 100644
index 0000000..777aa12
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/PixelTracker.kt
@@ -0,0 +1,567 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel
+
+import android.util.Log
+import com.bloomreach.discovery.pixel.processpixel.PixelProcessor
+import com.bloomreach.discovery.pixel.model.*
+
+/**
+ * PixelTracker Singleton class holds all types of Page view and Event Pixels methods
+ */
+object PixelTracker {
+
+ private val TAG: String = PixelTracker.javaClass.simpleName
+ lateinit var brPixel: BrPixel
+ private val pixelProcessor: PixelProcessor = PixelProcessor()
+
+ /**
+ * Initialise Pixel tracker with BrPixel object
+ * @param brPixel BrPixel object defined for initialisation
+ */
+ fun init(brPixel: BrPixel) {
+ this.brPixel = brPixel
+ }
+
+ /**
+ * Method for sending the Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ */
+ fun pageViewPixel(ref: String, title: String, viewId: String? = null) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based on input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.HOME_PAGE,
+ ref = ref,
+ title = title
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Product Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param prodId This is the unique ID which describes a product or product collection.
+ * @param prodName The name of the product being viewed.
+ * @param sku Unique sku ID that represents the selected variant of this product. If your site does not have SKUs, leave this blank.
+ */
+ fun productPageViewPixel(
+ ref: String,
+ title: String,
+ prodId: String,
+ prodName: String,
+ sku: String? = null
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW, pType = PageType.PRODUCT_PAGE,
+ ref = ref, title = title, prodId = prodId, prodName = prodName, prodSku = sku
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Content Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param catalogs List of CatalogItem that are shown in the page. In case the page has multiple
+ * tabs, only the catalogs of the selected (and visualized) tabs should be included.
+ * If multiple catalogs are shown in the active page (or tab) all of them should be included.
+ * @param itemId Unique ID of the item being shown in the page. This identifier should match
+ * the item_id as specified in the content feed.
+ * @param itemName Name or the title of the content page.
+ */
+ fun contentPageViewPixel(
+ ref: String,
+ title: String,
+ catalogs: List,
+ itemId: String,
+ itemName: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.CONTENT_PAGE,
+ ref = ref,
+ title = title,
+ catalogs = catalogs,
+ itemId = itemId,
+ itemName = itemName
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Content Search Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param catalogs List of CatalogItem that are shown in the page.
+ * @param searchTerm The value of the search query describing the page.
+ */
+ fun contentSearchPageViewPixel(
+ ref: String,
+ title: String,
+ catalogs: List,
+ searchTerm: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.SEARCH_PAGE,
+ ref = ref,
+ title = title,
+ catalogs = catalogs,
+ searchTerm = searchTerm
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Category Search Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param categoryId Unique category ID as referred to in the database/catalog. Bloomreach requires the cat_id field to be unique across your site.
+ * @param category The bread crumb of the page. Value needs to match the crumb value in your feed. eg: "Home|Clothing|Outerwear"
+ */
+ fun categoryPageViewPixel(
+ ref: String,
+ title: String,
+ categoryId: String,
+ category: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.CATEGORY_PAGE,
+ ref = ref,
+ title = title,
+ cat = category,
+ catId = categoryId
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Search Result Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param searchTerm The value of the search query describing the page.
+ */
+ fun searchResultPageViewPixel(ref: String, title: String, searchTerm: String) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.SEARCH_PAGE,
+ ref = ref,
+ title = title,
+ searchTerm = searchTerm
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Conversion Page View Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param isConversion Set to true to indicate this is a Conversion or Thank you page
+ * @param basketValue The total price of the checkout basket including tax, discounts, shipping and/or discounts in the account currency.
+ * @param orderId The order ID associated with the order placed
+ * @param basket List of the PixelBasketItem objects (Products purchased).
+ */
+ fun conversionPageView(
+ ref: String,
+ title: String,
+ isConversion: Boolean,
+ basketValue: String,
+ orderId: String,
+ basket: List
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.PAGE_VIEW,
+ pType = PageType.CONVERSION,
+ ref = ref,
+ title = title,
+ isConversion = if (isConversion) 1 else 0,
+ basketValue = basketValue,
+ orderId = orderId,
+ basket = basket
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method to send any type of Page View Pixel with Custom Parameters
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param pType Maps your site's page type classifications to the values Bloomreach expects for our page type classifications.
+ * @param params Map for custom keys and its associated values
+ */
+ fun customPageViewPixel(
+ ref: String,
+ title: String,
+ pType: PageType,
+ params: MutableMap
+ ) {
+ if (this::brPixel.isInitialized) {
+ // directly add map to the PixelQueue
+ params["ref"] = ref
+ params["title"] = title
+ params["type"] = PixelType.PAGE_VIEW.type
+ params["ptype"] = pType.pType
+ pixelProcessor.processPixel(params)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ //==================== EVENT PIXELS =======================
+
+ /**
+ * Method for sending the Add To Cart Event Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param prodId This is the unique ID which describes a product or product collection.
+ * @param prodName The name of the product being viewed.
+ * @param sku Unique sku ID that represents the selected variant of this product. If your site does not have SKUs, leave this blank.
+ * @param prodCollectionId (Optional) When a product is added to cart from a Product Collection page, set prod_collection_id as the id of the collection.
+ * No need to set prod_collection_id param in the ATC event pixel when a product is added to cart from its own page, independent of any Product Collection it is part of.
+ */
+ fun addToCartEventPixel(
+ ref: String,
+ title: String,
+ prodId: String,
+ prodName: String,
+ sku: String,
+ prodCollectionId: String? = null
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.EVENT,
+ pType = PageType.PRODUCT_PAGE,
+ group = GroupType.CART,
+ eType = "click-add",
+ ref = ref,
+ title = title,
+ prodId = prodId,
+ prodName = prodName,
+ prodSku = sku,
+ prodCollectionId = prodCollectionId
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Search Event Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param prodId This is the unique ID which describes a product or product collection.
+ * @param prodName The name of the product being viewed.
+ * @param sku Unique sku ID that represents the selected variant of this product. If your site does not have SKUs, leave this blank.
+ * @param searchTerm The value of the search query describing the page.
+ * @param catalogs List of CatalogItem that are shown in the page.
+ */
+ fun searchEventPixel(
+ ref: String,
+ title: String,
+ prodId: String,
+ prodName: String,
+ sku: String,
+ searchTerm: String,
+ catalogs: List? = null
+ ) {
+ if (this::brPixel.isInitialized) {
+
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.EVENT,
+ pType = PageType.PRODUCT_PAGE,
+ group = GroupType.SUGGEST,
+ eType = "submit",
+ ref = ref,
+ title = title,
+ prodId = prodId,
+ prodName = prodName,
+ prodSku = sku,
+ searchTerm = searchTerm,
+ catalogs = catalogs
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Suggestion Event Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param prodId This is the unique ID which describes a product or product collection.
+ * @param prodName The name of the product being viewed.
+ * @param sku Unique sku ID that represents the selected variant of this product. If your site does not have SKUs, leave this blank.
+ * @param typedTerm The display query (the one or more letters) that the user has actually typed.
+ This is NOT the suggested word or phrase.
+ * @param searchTerm User's typed search query submitted to search box
+ * @param catalogs List of CatalogItem that are shown in the page.
+ */
+ fun suggestionEventPixel(
+ ref: String,
+ title: String,
+ prodId: String,
+ prodName: String,
+ sku: String,
+ typedTerm: String,
+ searchTerm: String,
+ catalogs: List? = null
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.EVENT,
+ pType = PageType.PRODUCT_PAGE,
+ group = GroupType.SUGGEST,
+ eType = "click",
+ ref = ref,
+ title = title,
+ prodId = prodId,
+ prodName = prodName,
+ prodSku = sku,
+ searchTerm = searchTerm,
+ typedTerm = typedTerm,
+ catalogs = catalogs
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Quick Event Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param prodId This is the unique ID which describes a product or product collection.
+ * @param prodName The name of the product being viewed.
+ * @param sku Unique sku ID that represents the selected variant of this product. If your site does not have SKUs, leave this blank.
+ */
+ fun quickViewEventPixel(
+ ref: String,
+ title: String,
+ prodId: String,
+ prodName: String,
+ sku: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ // create pixel object based ob input
+ val pixelObject = PixelObject(
+ type = PixelType.EVENT,
+ pType = PageType.PRODUCT_PAGE,
+ group = GroupType.PRODUCT,
+ eType = "quickview",
+ ref = ref,
+ title = title,
+ prodId = prodId,
+ prodName = prodName,
+ prodSku = sku
+ )
+
+ // send pixel for further processing
+ pixelProcessor.processPixel(pixelObject)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Custom Event Pixel
+ * @param ref Synthetic URL from referrer screen
+ * @param title Screen name of the app view.
+ * @param eType Event type
+ * @param pType Maps your site's page type classifications to the values Bloomreach expects for our page type classifications.
+ * @param group Specifies the event grouping
+ * @param params Map for custom keys and its associated values
+ */
+ fun customEventPixel(
+ ref: String,
+ title: String,
+ eType: String,
+ pType: PageType,
+ group: GroupType,
+ params: MutableMap
+ ) {
+ if (this::brPixel.isInitialized) {
+ // directly add map to the PixelQueue
+ params["ref"] = ref
+ params["title"] = title
+ params["type"] = PixelType.EVENT.type
+ params["etype"] = eType
+ params["ptype"] = pType.pType
+ params["group"] = group.group
+ pixelProcessor.processPixel(params)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ //==================== WIDGET PIXELS =======================
+
+ /**
+ * Method for sending the Widget View Pixel
+ * @param widgetDataWrId The unique ID of the response. This value has to be populated from the API response metadata.widget.rid.
+ * @param widgetViewDataWq The query string used by the customer which returns a widget suggestion. This is optional for non-query widgets.
+ * @param widgetViewDataWid The widget ID. This is a unique, 6 character alphanumeric value. This value has to be populated from the API response metadata.widget.id.
+ * @param widgetViewDataWty The type of recommendation widget. This value has to be populated from the API response.This value has to be populated from the API response metadata.widget.type.
+ */
+ fun widgetView(
+ widgetDataWrId: String,
+ widgetViewDataWq: String,
+ widgetViewDataWid: String,
+ widgetViewDataWty: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ val params = mutableMapOf()
+ params["type"] = PixelType.EVENT.type
+ params["group"] = GroupType.WIDGET.group
+ params["etype"] = "widget-view"
+ params["wrid"] = widgetDataWrId
+ params["wq"] = widgetViewDataWq
+ params["wid"] = widgetViewDataWid
+ params["wty"] = widgetViewDataWty
+ pixelProcessor.processPixel(params)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Widget Click Pixel
+ * @param widgetDataWrId The unique ID of the response.
+ * @param widgetViewDataWq The query string used by the customer which returns a widget suggestion. This is optional for non-query widgets.
+ * @param widgetViewDataWid The widget ID. This is a unique, 6 character alphanumeric value. This value has to be populated from the API response metadata.widget.id.
+ * @param widgetViewDataWty The type of recommendation widget. This value has to be populated from the API response.This value has to be populated from the API response metadata.widget.type.
+ * @param widgetDataItemId The unique ID (PID) of the clicked product. The PID is used from the API call.
+ */
+ fun widgetClick(
+ widgetDataWrId: String,
+ widgetViewDataWq: String,
+ widgetViewDataWid: String,
+ widgetViewDataWty: String,
+ widgetDataItemId: String
+ ) {
+ if (this::brPixel.isInitialized) {
+ val params = mutableMapOf()
+ params["type"] = PixelType.EVENT.type
+ params["group"] = GroupType.WIDGET.group
+ params["etype"] = "widget-click"
+ params["ptype"] = PageType.OTHER_PAGE.pType
+
+ params["item_id"] = widgetDataItemId
+ params["wrid"] = widgetDataWrId
+ params["wq"] = widgetViewDataWq
+ params["wid"] = widgetViewDataWid
+ params["wty"] = widgetViewDataWty
+ pixelProcessor.processPixel(params)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+
+ /**
+ * Method for sending the Widget Add to cart
+ * @param widgetDataWrId The unique ID of the response. This value has to be populated from the API response metadata.widget.rid.
+ * @param widgetViewDataWq The query string used by the customer which returns a widget suggestion. This is optional for non-query widgets.
+ * @param widgetViewDataWid The widget ID. This is a unique, 6 character alphanumeric value. This value has to be populated from the API response metadata.widget.id.
+ * @param widgetViewDataWty The type of recommendation widget. This value has to be populated from the API response.This value has to be populated from the API response metadata.widget.type.
+ * @param widgetDataItemId The unique ID (PID) of the clicked product. The PID is used from the API call.
+ * @param widgetAtcDataSku Unique SKU id that represents the selected variant of this product (e.g. size M, color blue of a t-shirt). If your site does not have SKUs, leave this blank.
+ */
+ fun widgetAddToCart(
+ widgetDataWrId: String,
+ widgetViewDataWid: String,
+ widgetViewDataWq: String,
+ widgetViewDataWty: String,
+ widgetDataItemId: String,
+ widgetAtcDataSku: String? = null
+ ) {
+ if (this::brPixel.isInitialized) {
+ val params = mutableMapOf()
+ params["type"] = PixelType.EVENT.type
+ params["group"] = GroupType.CART.group
+ params["etype"] = "widget-add"
+ params["item_id"] = widgetDataItemId
+ params["wrid"] = widgetDataWrId
+ params["wid"] = widgetViewDataWid
+ params["wq"] = widgetViewDataWq
+ params["wty"] = widgetViewDataWty
+ if(!widgetAtcDataSku.isNullOrEmpty()) {
+ params["sku"] = widgetAtcDataSku
+ }
+ pixelProcessor.processPixel(params)
+ } else {
+ Log.e(TAG, "Pixel Tracker not initialised")
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/BrPixel.kt b/src/main/java/com/bloomreach/discovery/pixel/model/BrPixel.kt
new file mode 100644
index 0000000..0ffc361
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/BrPixel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * Class containing initialising parameters for the Pixel SDK.
+ *
+ * @property accountId Account Id provided by Bloomreach
+ * @property uuid Android Advertising ID
+ * @property visitorType ENUM type for New User or returning user
+ * @property baseUrl Base Url for the merchant provided bt Bloomreach
+ * @property domainKey The Bloomreach-provided ID of the domain receiving the request.(Optional)
+ * @property userId This parameter is only required if you track users via a universal customer ID.
+ * @property testData Do not declare test_data in the pixel for your live site.
+ * @property currency Currency for the app
+ * @property pixelUrlByRegion Url for Pixel server based on region. Default to NA region
+ * @property customerTier Tier that the user belongs to. eg: Premium, Gold
+ * @property customerCountry Country that the user belongs to or is accessing the site from.
+ * @property customerGeo Geography or Region that the user belongs to.
+ * @property customerProfile Profile of the user.
+ * @property viewId View Id
+ * @property debugMode Debug mode of main application to see Pixel Validator logs. Pass value as BuildConfig.DEBUG
+ */
+
+data class BrPixel(
+ val accountId: String,
+ val uuid: String,
+ val visitorType: VisitorType,
+ val baseUrl: String,
+ var domainKey: String? = null,
+ var userId: String? = null,
+ var testData: Boolean = false,
+ var currency: String? = null,
+ var pixelUrlByRegion: String = PixelRegion.NA.url,
+ //segments
+ var customerTier: String? = null,
+ var customerCountry: String? = null,
+ var customerGeo: String? = null,
+ var customerProfile: String? = null,
+ var viewId: String? = null,
+ var debugMode: Boolean = false
+) {
+ init {
+ require(accountId.isNotEmpty()) { "Bloomreach Account Id is required" }
+ require(uuid.isNotEmpty()) { "UUID is required" }
+ // require(visitor.isNotEmpty()) { "visitor is required" }
+ require(baseUrl.isNotEmpty()) { "baseUrl is required" }
+ }
+}
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/CatalogItem.kt b/src/main/java/com/bloomreach/discovery/pixel/model/CatalogItem.kt
new file mode 100644
index 0000000..20317c4
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/CatalogItem.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * Model class for Catalog Item
+ * @property name Catalog Name
+ * @property viewIds Views show selective content to selective users, such as content-based on regions or stores. This can be Single or Multiple.
+ */
+data class CatalogItem(
+ val name: String,
+ val viewIds: List? = null
+)
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/GroupType.kt b/src/main/java/com/bloomreach/discovery/pixel/model/GroupType.kt
new file mode 100644
index 0000000..5e3736b
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/GroupType.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * GroupType ENUM for events pixels group
+ */
+enum class GroupType(val group: String) {
+ CART("cart"),
+ SUGGEST("suggest"),
+ PRODUCT("product"),
+ WIDGET("widget")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/PageType.kt b/src/main/java/com/bloomreach/discovery/pixel/model/PageType.kt
new file mode 100644
index 0000000..01b09e5
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/PageType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * PageType ENUM to provide page name (pType parameter) for Pixels
+ */
+enum class PageType(val pType: String) {
+ // Any home page or landing page that is considered a home page needs to be classified as
+ HOME_PAGE("homepage"),
+
+ // Any product, product bundle, product collection or sku set pages need to be classified as
+ PRODUCT_PAGE("product"),
+
+ // Any category listing pages, category product listing pages or pages that you consider a category page need to be classified as
+ CATEGORY_PAGE("category"),
+
+ // Any search listing or search results pages need to be classified as
+ SEARCH_PAGE("search"),
+
+ // Any content pages need to be classified as
+ CONTENT_PAGE("content"),
+
+ // Bloomreach Thematic pages need to be classified as
+ THEMATIC_PAGE("thematic"),
+
+ // Any Conversion/ Thank You pages as well as any page types
+ CONVERSION("conversion"),
+
+ //Any page types that are not one of the above need to be classified as
+ OTHER_PAGE("other")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/PixelBasketItem.kt b/src/main/java/com/bloomreach/discovery/pixel/model/PixelBasketItem.kt
new file mode 100644
index 0000000..c208213
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/PixelBasketItem.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * Class containing product details for the purchase.
+ * @property prodId Product Id of the purchased product
+ * @property name Name of the purchased product
+ * @property sku SKU number of the purchased product
+ * @property quantity Purchased quantity of the product
+ * @property price Price of the product
+ */
+data class PixelBasketItem(
+ val prodId: String,
+ val name: String,
+ val sku: String?,
+ val quantity: String,
+ val price: String
+)
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/PixelObject.kt b/src/main/java/com/bloomreach/discovery/pixel/model/PixelObject.kt
new file mode 100644
index 0000000..a10fe52
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/PixelObject.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * Model class for PixelObject. Pixel Object will hold all the fields provided by merchant app
+ * and will be used for further process and converting it to required Query Parameter
+ */
+internal data class PixelObject(
+ val type: PixelType, //ENUM
+ val pType: PageType, //ENUM
+ val title: String,
+ val ref: String,
+
+ val eType: String? = null,
+ val prodId: String? = null,
+ val prodName: String? = null,
+ val prodSku: String? = null,
+ val prodCollectionId: String? = null,
+
+ val catalogs: List? = null, //Array of catalogs
+ val itemId: String? = null,
+ val itemName: String? = null,
+
+ val isConversion: Int? = null,
+ val basketValue: String? = null,
+ val orderId: String? = null,
+ val basket: List? = null,
+
+ val group: GroupType? = null,
+ val searchTerm: String? = null,
+ val typedTerm: String? = null,
+ val catId: String? = null,
+ val cat: String? = null
+
+)
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/PixelRegion.kt b/src/main/java/com/bloomreach/discovery/pixel/model/PixelRegion.kt
new file mode 100644
index 0000000..a2e3e22
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/PixelRegion.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * PixelRegion ENUM to provide pixel end point based on region
+ */
+enum class PixelRegion(val url: String) {
+ NA("p.brsrvr.com"),
+ EU("p-eu.brsrvr.com"),
+ TEST("p-test.brsrvr.com")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/PixelType.kt b/src/main/java/com/bloomreach/discovery/pixel/model/PixelType.kt
new file mode 100644
index 0000000..a2ab1b6
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/PixelType.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * PixelType ENUM to provide tne type of Pixel to be sent
+ */
+enum class PixelType(val type: String) {
+ // for page view pixels
+ PAGE_VIEW("pageview"),
+
+ // for event pixels
+ EVENT("event")
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/VisitorType.kt b/src/main/java/com/bloomreach/discovery/pixel/model/VisitorType.kt
new file mode 100644
index 0000000..3fcc8c3
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/VisitorType.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model
+
+/**
+ * VisitorType ENUM to provide hitcount based on new or returning user
+ */
+enum class VisitorType(val hitCount: Int) {
+ NEW_USER(1),
+ RETURNING_USER(2)
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/validator/PixelValidatorBody.kt b/src/main/java/com/bloomreach/discovery/pixel/model/validator/PixelValidatorBody.kt
new file mode 100644
index 0000000..da9c202
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/validator/PixelValidatorBody.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model.validator
+
+/**
+ * Request Body for posting to Pixel Validator call
+ */
+internal data class PixelValidatorBody(
+ val source: String,
+ val protocol: String,
+ val host: String,
+ val port: String,
+ val query: String,
+ val params: MutableMap,
+ val file: String,
+ val hash: String,
+ val path: String,
+ val relative: String,
+ val segments: List
+)
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/Param.kt b/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/Param.kt
new file mode 100644
index 0000000..da304c8
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/Param.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model.validator.response
+
+import com.fasterxml.jackson.annotation.JsonProperty
+
+/**
+ * Param Response object from Pixel Validator API response
+ */
+data class Param(
+ @JsonProperty("description")
+ val description: String?,
+ @JsonProperty("name")
+ val name: String,
+ @JsonProperty("value")
+ val value: String
+)
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/PixelValidatorResponse.kt b/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/PixelValidatorResponse.kt
new file mode 100644
index 0000000..4ea4b47
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/model/validator/response/PixelValidatorResponse.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.model.validator.response
+
+import com.fasterxml.jackson.annotation.JsonProperty
+
+/**
+ * Response object from Pixel Validator API response
+ */
+data class PixelValidatorResponse(
+ @JsonProperty("displayName")
+ val displayName: String,
+ @JsonProperty("errors")
+ val errors: List,
+ @JsonProperty("merchant")
+ val merchant: String,
+ @JsonProperty("params")
+ val params: List?,
+ @JsonProperty("success")
+ val success: List,
+ @JsonProperty("url")
+ val url: String,
+ @JsonProperty("warns")
+ val warns: List
+)
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/network/RestClient.kt b/src/main/java/com/bloomreach/discovery/pixel/network/RestClient.kt
new file mode 100644
index 0000000..32a6953
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/network/RestClient.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.network
+
+import android.net.Uri
+import android.util.Log
+import com.bloomreach.discovery.BuildConfig
+import com.bloomreach.discovery.pixel.PixelTracker
+import com.bloomreach.discovery.pixel.model.validator.PixelValidatorBody
+import com.bloomreach.discovery.pixel.model.validator.response.PixelValidatorResponse
+import com.fasterxml.jackson.databind.ObjectMapper
+import java.io.BufferedReader
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.OutputStreamWriter
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
+
+/**
+ * RestClient class to perform API call in the SDK
+ */
+internal class RestClient {
+
+ private val pixelValidatorUrl = "https://tools.bloomreach.com/pixel-validator/validatePixel"
+
+ /**
+ * Method to call Pixel Validator API
+ * @param postBody required post body to be sent to Pixel Validator API
+ *
+ * @return PixelValidatorResponse response object
+ */
+ fun validatePixel(postBody: PixelValidatorBody): PixelValidatorResponse? {
+ val inputStream: InputStream
+ var result: String? = null
+
+ try {
+ // Create URL
+ val url = URL(pixelValidatorUrl)
+
+// Log.i("", pixelValidatorUrl)
+ // Create HttpURLConnection
+ val conn: HttpsURLConnection = url.openConnection() as HttpsURLConnection
+ conn.doOutput = true
+ // set request method POST
+ conn.requestMethod = "POST"
+
+ // set required headers
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setRequestProperty("Accept", "application/json");
+
+ // Jackson Convertor ObjectMapper class for converting Model to required String to be sent as POST body
+ val mapper = ObjectMapper()
+
+ // OutputStream to send post request body
+ val os: OutputStream = conn.outputStream
+ val osw = OutputStreamWriter(os, "UTF-8")
+ osw.write(mapper.writeValueAsString(postBody))
+
+ osw.flush()
+ osw.close()
+ os.close() //close the OutputStream
+
+ // Launch POST request
+ conn.connect()
+ if (conn.responseCode in 200..299) {
+ // API Success
+ // Receive response as inputStream
+ inputStream = conn.inputStream
+
+ if (inputStream != null) {
+ // Convert input stream to string
+ result = inputStream.bufferedReader().use(BufferedReader::readText)
+
+ //Jackson Convertor ObjectMapper convert respnse json to PixelValidatorResponse Object
+ inputStream.close()
+ val responseMapper = ObjectMapper()
+ return responseMapper.readValue(result, PixelValidatorResponse::class.java)
+ } else {
+ inputStream.close()
+ Log.e("PixelValidator", "PixelValidator failure")
+ }
+ } else {
+ // API failure
+ Log.e("PixelValidator", "PixelValidator failure with response code ${conn.responseCode}")
+ }
+ } catch (err: Exception) {
+ Log.e("PixelValidator", "Error when executing get request: ${err.localizedMessage}")
+ }
+ return null
+ }
+
+ /**
+ * Method to call Submit Pixel API for all types of Pixel
+ * @param uriBuilder Uri.Builder with query parametes to be sent
+ *
+ * @return String to know success or error
+ */
+ fun submitPixel(uriBuilder: Uri.Builder): String? {
+ // Pass each Pixel through through retry Retry Strategy
+ val retryStrategy = RetryStrategy()
+ //check if retry is needed
+ while (retryStrategy.shouldRetry()) {
+ val inputStream: InputStream
+ var result: String? = null
+ try {
+ // append base endpoint for Pixel call
+ uriBuilder.scheme("https")
+ uriBuilder.authority(PixelTracker.brPixel.pixelUrlByRegion)
+ uriBuilder.appendPath("pix.gif")
+ // Create URL
+ val url = URL(uriBuilder.build().toString())
+
+ Log.i("Submit Pixel ", uriBuilder.toString())
+
+ // Create HttpURLConnection
+ val conn: HttpsURLConnection = url.openConnection() as HttpsURLConnection
+ // conn.doOutput = true
+ // API request set to GET
+ conn.requestMethod = "GET";
+ // set none cache
+ conn.setRequestProperty("Cache-Control", "no-cache")
+
+ // set user agent property with required SDK version
+
+ conn.setRequestProperty(
+ "User-Agent",
+ "Bloomreach/${BuildConfig.SDK_VERSION} " + System.getProperty("http.agent")
+ )
+
+ conn.defaultUseCaches = false;
+ conn.useCaches = false;
+ // Launch GET request
+ conn.connect()
+
+ val responseCode = conn.responseCode
+ Log.i("SubmitPixel", "responseCode: $responseCode")
+ if (responseCode in 200..299) { // success
+ // Receive response as inputStream
+ inputStream = conn.inputStream
+
+ if (inputStream != null) {
+ // Convert input stream to string
+ result = inputStream.bufferedReader().use(BufferedReader::readText)
+
+ inputStream.close()
+ return result
+ } else {
+ result = "error: inputStream is null"
+ inputStream.close()
+ return result
+ }
+ } else if (responseCode in 400..499) {
+ // skip the pixel and don't retry
+ return null
+ } else {
+ // if error inform retryStrategy class to check if to continue with retry
+ retryStrategy.errorOccured()
+ }
+
+ } catch (err: Exception) {
+ Log.e("SubmitPixel", "Error: ${err.localizedMessage}")
+ retryStrategy.errorOccured()
+ }
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/network/RetryStrategy.kt b/src/main/java/com/bloomreach/discovery/pixel/network/RetryStrategy.kt
new file mode 100644
index 0000000..5f90ebb
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/network/RetryStrategy.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.network
+
+/**
+ * RetryStrategy - Retry mechanism for Pixel call with network failure scenerios
+ */
+class RetryStrategy {
+
+ private val DEFAULT_RETRIES = 3
+ private val DEFAULT_WAIT_TIME_IN_MILLI: Long = 1000
+
+ private var numberOfRetries = DEFAULT_RETRIES
+ private var numberOfTriesLeft = 3
+ private var timeToWait: Long = DEFAULT_WAIT_TIME_IN_MILLI
+
+ /**
+ * @return true if there are tries left
+ */
+ fun shouldRetry(): Boolean {
+ return numberOfTriesLeft > 0
+ }
+
+ @Throws(Exception::class)
+ fun errorOccured() {
+ if (!shouldRetry()) {
+ throw Exception(
+ "Retry Failed: Total " + numberOfRetries
+ + " attempts made at interval " + getTimeToWait()
+ + "ms"
+ )
+ }
+ numberOfTriesLeft--
+ waitUntilNextTry()
+ }
+
+ private fun getTimeToWait(): Long {
+ return timeToWait
+ }
+
+ private fun waitUntilNextTry() {
+ try {
+ Thread.sleep(getTimeToWait())
+ } catch (ignored: InterruptedException) {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/processpixel/FormatterUtils.kt b/src/main/java/com/bloomreach/discovery/pixel/processpixel/FormatterUtils.kt
new file mode 100644
index 0000000..98a56ef
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/processpixel/FormatterUtils.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel.processpixel
+
+import android.net.Uri
+import android.util.Log
+import com.bloomreach.discovery.pixel.model.CatalogItem
+import com.bloomreach.discovery.pixel.model.PixelBasketItem
+import com.bloomreach.discovery.pixel.model.VisitorType
+import java.net.URLDecoder
+import java.net.URLEncoder
+import kotlin.random.Random
+
+/**
+ * Utility class for performing string formatting operations
+ */
+internal object FormatterUtils {
+
+ /**
+ * Method to generate a random number
+ *
+ * @return random number in string
+ */
+ fun generateRand(): String {
+ // generate random Long
+ return Random.nextLong().toString()
+ }
+
+ /**
+ * Method to format Cookie2 value
+ * @param uuid Android random string
+ * @param hitcount ENUM VisitorType (The hitcount value should be 1 for a new visitor, or 2 for returning visitors.)
+ *
+ * @return cookie2 - String value in 'uid={{UUID}}:v=app:ts=0:hc={{hitcount}}' format
+ */
+ fun formatCookieValue(uuid: String, hitcount: VisitorType): String {
+ // convert uid={{UUID}}:v=app:ts=0:hc={{hitcount}}
+ return "uid=$uuid:v=app:ts=0:hc=${hitcount.hitCount}"
+ }
+
+ /**
+ * Method to format url value
+ * @param baseurl Base Url for the merchant provided bt Bloomreach
+ * @param pType Page classification type
+ * @param title Title of the screen
+ * @return url - String value in 'http://merchantname.app/ptype/title' format
+ */
+ fun formatUrl(baseurl: String, pType: String, title: String): String {
+ // convert in format http://merchantname.app/ptype/title
+ return "$baseurl$pType/$title"
+ }
+
+ /**
+ * Method to format catalog value
+ * The catalog name is encoded by prefixing "cat" + "the index of the catalog starting from 0" + "=" + "the catalog name"
+ * @param catalogs Array of the CatalogItem objects
+ * @return cataLogs - String value in required format
+ */
+ fun formatCatalog(catalogs: List?): String {
+
+ if (catalogs.isNullOrEmpty()) {
+ Log.i(FormatterUtils.javaClass.simpleName, "Catalogs is Null or Empty")
+ return ""
+ }
+ return buildString {
+ for ((catalogIndex, catalog) in catalogs.withIndex()) {
+ // Each catalog in catalogs is separated by an "!"
+ if (catalogIndex != 0) this.append("!")
+ // add "cat" + "the index of the catalog starting from 0"
+ this.append("cat$catalogIndex=${catalog.name}")
+
+ if (!catalog.viewIds.isNullOrEmpty()) {
+ for ((viewIndex, viewId) in catalog.viewIds.withIndex()) {
+ // Catalog name and view_id are separated by a ":"
+ if (viewIndex == 0) this.append(":")
+ // Multiple view_ids are separated by a "."
+ else this.append(".")
+ // The view_id is encoded by prefixing "v" + "the index of the view_id starting from 0" + "=" + "the view_id"
+ this.append("v$viewIndex=$viewId")
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to format basket value
+ * Each product in the cart will be separated by !. Each product's details will be separated by '.
+ * @param basketItems Array of the PixelBasketItem objects
+ * @return basket - String value in required format
+ */
+ fun formatBasket(basketItems: List?): String {
+ if (basketItems.isNullOrEmpty()) {
+ Log.e(FormatterUtils.javaClass.simpleName, "Basket is Null or Empty")
+ return ""
+ }
+ return buildString {
+ for (basketItem in basketItems) {
+ // add prodId as '!i'
+ this.append("!i${basketItem.prodId}")
+
+ // s - Sku id, only applies if you have skus.
+ if (!basketItem.sku.isNullOrEmpty()) {
+ this.append("'s${basketItem.sku}")
+ }
+
+ // n
+ this.append("'n${basketItem.name}")
+
+ // q
+ this.append("'q${basketItem.quantity}")
+
+ // p - This should be the unit price per product and not total price. If the item is on sale, this is the unit sale price.
+ this.append("'p${basketItem.price}")
+ }
+ }
+ }
+
+ /**
+ * Method to convert Map to Uri.Builder for network calls
+ *
+ * @param queryMap array of the PixelBasketItem objects
+ * @return Uri.Builder - String value in required format
+ */
+ fun mapToUriBuilder(queryMap: MutableMap): Uri.Builder {
+ val uriBuilder = Uri.Builder()
+ queryMap.forEach { mapObject ->
+ uriBuilder.appendQueryParameter(mapObject.key, mapObject.value ?: "")
+ }
+ return uriBuilder
+ }
+
+ /**
+ * Method to convert Map to Uri.Builder for network calls
+ *
+ * @param queryMap array of the PixelBasketItem objects
+ * @return Uri.Builder - String value in required format
+ */
+ fun mapToUriBuilderForApi(queryMap: MutableMap): Uri.Builder {
+ val uriBuilder = Uri.Builder()
+ queryMap.forEach { mapObject ->
+ if(mapObject.value is String) {
+ uriBuilder.appendQueryParameter(mapObject.key, getUrlDecodeString(mapObject.value as String))
+ } else if(mapObject.value is List<*>) { // check for fq
+ for(value in mapObject.value as List<*>) {
+ uriBuilder.appendQueryParameter(mapObject.key, getUrlDecodeString(value as String))
+ }
+ }
+ }
+ return uriBuilder
+ }
+
+ private fun getUrlDecodeString(value: String): String {
+ return URLDecoder.decode(value, "UTF-8")
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/processpixel/PageViewPixelFormatter.kt b/src/main/java/com/bloomreach/discovery/pixel/processpixel/PageViewPixelFormatter.kt
new file mode 100644
index 0000000..6154221
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/processpixel/PageViewPixelFormatter.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel.processpixel
+
+import com.bloomreach.discovery.pixel.model.PixelObject
+
+/**
+ * Class for creating query parameter as String for each Page View Pixel in required format
+ */
+internal class PageViewPixelFormatter {
+
+ /**
+ * Method to generating query parameter String for Product Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with value in required format
+ */
+ fun prepareProductPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ queryMap.put("prod_id", pixelObject.prodId)
+ queryMap.put("prod_name", pixelObject.prodName)
+ if(pixelObject.prodSku != null) {
+ queryMap.put("sku", pixelObject.prodSku)
+ }
+ return queryMap
+ }
+
+ /**
+ * Method to generating query parameter String for Content Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with value in required format
+ */
+ fun prepareContentPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ if (!pixelObject.catalogs.isNullOrEmpty()) {
+ queryMap.put("catalogs", FormatterUtils.formatCatalog(pixelObject.catalogs))
+ }
+ queryMap.put("item_id", pixelObject.itemId)
+ queryMap.put("item_name", pixelObject.itemName)
+ return queryMap
+ }
+
+ /**
+ * Method to generating query parameter String for Content Search Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ fun prepareContentSearchPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ if (!pixelObject.catalogs.isNullOrEmpty()) {
+ queryMap.put("catalogs", FormatterUtils.formatCatalog(pixelObject.catalogs))
+ }
+ queryMap.put("search_term", pixelObject.searchTerm)
+ return queryMap
+ }
+
+ /**
+ * Method to generating query parameter String for Search Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ fun prepareSearchPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ queryMap.put("search_term", pixelObject.searchTerm)
+ return queryMap
+ }
+
+ /**
+ * Method to generating query parameter String for Category Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ fun prepareCategoryPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ queryMap.put("cat", pixelObject.cat)
+ queryMap.put("cat_id", pixelObject.catId)
+ return queryMap
+ }
+
+ /**
+ * Method to generating query parameter String for Conversion Page View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ fun prepareConversionPageViewQuery(pixelObject: PixelObject, queryMap: MutableMap): MutableMap {
+ queryMap.put("is_conversion", pixelObject.isConversion.toString())
+ queryMap.put("basket_value", pixelObject.basketValue)
+ queryMap.put("order_id", pixelObject.orderId)
+ queryMap.put("basket", FormatterUtils.formatBasket(pixelObject.basket))
+ return queryMap
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/processpixel/PixelProcessor.kt b/src/main/java/com/bloomreach/discovery/pixel/processpixel/PixelProcessor.kt
new file mode 100644
index 0000000..eb0e39f
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/processpixel/PixelProcessor.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+package com.bloomreach.discovery.pixel.processpixel
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.bloomreach.discovery.BuildConfig
+import com.bloomreach.discovery.pixel.PixelTracker
+import com.bloomreach.discovery.pixel.model.PageType
+import com.bloomreach.discovery.pixel.model.PixelObject
+import com.bloomreach.discovery.pixel.model.PixelType
+import com.bloomreach.discovery.pixel.network.RestClient
+import com.bloomreach.discovery.pixel.processpixel.FormatterUtils.generateRand
+import com.bloomreach.discovery.pixel.submitpixel.ListenableQueue
+import com.bloomreach.discovery.pixel.submitpixel.PixelQueue
+import com.bloomreach.discovery.pixel.validator.PixelValidator
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+/**
+ * Class for processing all the pixel and converting it in required query parameter string
+ */
+internal class PixelProcessor {
+
+ //private val pixelJob: PixelJob = PixelJob()
+ private val restClient: RestClient = RestClient()
+ private val pixelValidator: PixelValidator = PixelValidator()
+ private val pageViewPixelFormatter = PageViewPixelFormatter()
+
+ init {
+ PixelQueue.pixels.registerListener(object :
+ ListenableQueue.Listener> {
+ @RequiresApi(Build.VERSION_CODES.N)
+ override fun onElementAdded(element: MutableMap) {
+ performApi()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.N)
+ override fun onElementRemoved(element: MutableMap) {
+ performApi()
+ }
+ })
+ }
+
+ @RequiresApi(Build.VERSION_CODES.N)
+ private fun performApi() {
+ val nextElementFromQueue = PixelQueue.get()
+ if (nextElementFromQueue != null) {
+ val uriBuilder = FormatterUtils.mapToUriBuilder(nextElementFromQueue)
+ CoroutineScope(Dispatchers.IO).launch {
+ //API call
+ val response = restClient.submitPixel(uriBuilder)
+ PixelQueue.remove()
+ }
+ }
+ }
+
+ /**
+ * Method to process Pixel triggered by Merchant app and convert it in required query parameter string
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ */
+ fun processPixel(pixelObject: PixelObject) {
+ val queryMap = mutableMapOf()
+
+ // process generic value
+ prepareGlobalQuery(pixelObject, queryMap)
+
+ when (pixelObject.type) {
+ PixelType.PAGE_VIEW -> {
+ processPageViewPixel(pixelObject, queryMap)
+ }
+ PixelType.EVENT -> {
+ processEventPixel(pixelObject, queryMap)
+ }
+ }
+
+ // Validate Pixel only when in DEBUG mode
+ if (PixelTracker.brPixel.debugMode) {
+ pixelValidator.validatePixel(queryMap)
+ }
+
+ // add the processed Map to Queue for further process
+ PixelQueue.add(queryMap)
+ }
+
+ fun processPixel(queryMap: MutableMap) {
+
+ queryMap.put("acct_id", PixelTracker.brPixel.accountId)
+
+ queryMap.put(
+ "cookie2", FormatterUtils.formatCookieValue(
+ PixelTracker.brPixel.uuid,
+ PixelTracker.brPixel.visitorType
+ )
+ )
+
+ queryMap.put("rand", generateRand())
+
+ if (PixelTracker.brPixel.testData) {
+ queryMap.put("test_data", PixelTracker.brPixel.testData.toString())
+ }
+
+ queryMap.put(
+ "url", FormatterUtils.formatUrl(
+ PixelTracker.brPixel.baseUrl,
+ queryMap["ptype"] ?: "",
+ queryMap["title"] ?: ""
+ )
+ )
+
+ // customer user id
+ if (!PixelTracker.brPixel.userId.isNullOrEmpty()) {
+ queryMap.put("user_id", PixelTracker.brPixel.userId)
+ }
+
+ // customer tier
+ if (!PixelTracker.brPixel.customerTier.isNullOrEmpty()) {
+ queryMap.put("customer_tier", PixelTracker.brPixel.customerTier)
+ }
+
+ // customer country present
+ if (!PixelTracker.brPixel.customerCountry.isNullOrEmpty()) {
+ queryMap.put("customer_country", PixelTracker.brPixel.customerCountry)
+ }
+
+ // customer geo present
+ if (!PixelTracker.brPixel.customerGeo.isNullOrEmpty()) {
+ queryMap.put("customer_geo", PixelTracker.brPixel.customerGeo)
+ }
+
+ // customer profile present
+ if (!PixelTracker.brPixel.customerProfile.isNullOrEmpty()) {
+ queryMap.put("customer_profile", PixelTracker.brPixel.customerProfile)
+ }
+
+ // Validate Pixel only when in DEBUG mode
+ if (PixelTracker.brPixel.debugMode) {
+ pixelValidator.validatePixel(queryMap)
+ }
+
+ //viewId
+ if (!PixelTracker.brPixel.viewId.isNullOrEmpty()) {
+ queryMap.put("view_id", PixelTracker.brPixel.viewId)
+ }
+
+ //Currency
+ if (!PixelTracker.brPixel.currency.isNullOrEmpty()) {
+ queryMap.put("currency", PixelTracker.brPixel.currency)
+ }
+
+ //DomainKey
+ if (!PixelTracker.brPixel.domainKey.isNullOrEmpty()) {
+ queryMap.put("domain_key", PixelTracker.brPixel.domainKey)
+ }
+
+ // add the processed Map to Queue for further process
+ PixelQueue.add(queryMap)
+ }
+
+ /**
+ * Method to process only Pixel View Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @return String value in required format
+ */
+ private fun processPageViewPixel(
+ pixelObject: PixelObject,
+ queryMap: MutableMap
+ ): MutableMap {
+ // do processing based on pType for each PageView Pixels
+ when (pixelObject.pType) {
+ PageType.HOME_PAGE -> {
+ return queryMap
+ }
+
+ PageType.PRODUCT_PAGE -> {
+ return pageViewPixelFormatter.prepareProductPageViewQuery(pixelObject, queryMap)
+ }
+
+ PageType.CONTENT_PAGE -> {
+ return pageViewPixelFormatter.prepareContentPageViewQuery(pixelObject, queryMap)
+ }
+
+ PageType.SEARCH_PAGE -> {
+ return if (pixelObject.catalogs.isNullOrEmpty()) {
+ pageViewPixelFormatter.prepareSearchPageViewQuery(pixelObject, queryMap)
+ } else {
+ pageViewPixelFormatter.prepareContentSearchPageViewQuery(
+ pixelObject,
+ queryMap
+ )
+ }
+ }
+
+ PageType.CATEGORY_PAGE -> {
+ return pageViewPixelFormatter.prepareCategoryPageViewQuery(pixelObject, queryMap)
+ }
+
+ PageType.CONVERSION -> {
+ return pageViewPixelFormatter.prepareConversionPageViewQuery(pixelObject, queryMap)
+ }
+
+// PageType.OTHER_PAGE -> {
+// return pageViewPixelFormatter.prepareConversionPageViewQuery(pixelObject, queryMap)
+// }
+ else -> return queryMap
+ }
+ }
+
+ /**
+ * Method to process only Event Pixel
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ private fun processEventPixel(
+ pixelObject: PixelObject,
+ queryMap: MutableMap
+ ): MutableMap {
+ queryMap.put("group", pixelObject.group?.group)
+
+ queryMap.put("etype", pixelObject.eType)
+
+ queryMap.put("prod_id", pixelObject.prodId)
+
+ queryMap.put("prod_name", pixelObject.prodName)
+
+ if (!pixelObject.prodSku.isNullOrEmpty()) {
+ queryMap.put("sku", pixelObject.prodSku)
+ }
+
+ if (!pixelObject.prodCollectionId.isNullOrEmpty()) {
+ queryMap.put("prod_collection_id", pixelObject.prodCollectionId)
+ }
+
+ if (!pixelObject.searchTerm.isNullOrEmpty()) {
+ queryMap.put("q", pixelObject.searchTerm)
+ }
+
+ if (!pixelObject.typedTerm.isNullOrEmpty()) {
+ queryMap.put("aq", pixelObject.typedTerm)
+ }
+
+ if (!pixelObject.catalogs.isNullOrEmpty()) {
+ queryMap.put("catalogs", FormatterUtils.formatCatalog(pixelObject.catalogs))
+ }
+
+ return queryMap
+ }
+
+ /**
+ * Method to process Global parameter required for Pixel and convert in required format
+ * @param pixelObject internal object which holds data for fields required to generate query parameter String
+ * @param queryMap reference of Map where the values will be added
+ * @return Map with values in required format
+ */
+ private fun prepareGlobalQuery(
+ pixelObject: PixelObject,
+ queryMap: MutableMap
+ ): MutableMap {
+ queryMap.put("acct_id", PixelTracker.brPixel.accountId)
+
+ queryMap.put(
+ "cookie2", FormatterUtils.formatCookieValue(
+ PixelTracker.brPixel.uuid,
+ PixelTracker.brPixel.visitorType
+ )
+ )
+
+ queryMap.put("rand", generateRand())
+
+ queryMap.put("type", pixelObject.type.type)
+
+ queryMap.put("ptype", pixelObject.pType.pType)
+
+ if (PixelTracker.brPixel.testData) {
+ queryMap.put("test_data", PixelTracker.brPixel.testData.toString())
+ }
+
+ queryMap.put("title", pixelObject.title)
+
+ queryMap.put(
+ "url", FormatterUtils.formatUrl(
+ PixelTracker.brPixel.baseUrl,
+ pixelObject.pType.pType,
+ pixelObject.title
+ )
+ )
+
+ queryMap.put("ref", pixelObject.ref)
+
+ // customer user id
+ if (!PixelTracker.brPixel.userId.isNullOrEmpty()) {
+ queryMap.put("user_id", PixelTracker.brPixel.userId)
+ }
+
+ // customer tier
+ if (!PixelTracker.brPixel.customerTier.isNullOrEmpty()) {
+ queryMap.put("customer_tier", PixelTracker.brPixel.customerTier)
+ }
+
+ // customer country present
+ if (!PixelTracker.brPixel.customerCountry.isNullOrEmpty()) {
+ queryMap.put("customer_country", PixelTracker.brPixel.customerCountry)
+ }
+
+ // customer geo present
+ if (!PixelTracker.brPixel.customerGeo.isNullOrEmpty()) {
+ queryMap.put("customer_geo", PixelTracker.brPixel.customerGeo)
+ }
+
+ // customer profile present
+ if (!PixelTracker.brPixel.customerProfile.isNullOrEmpty()) {
+ queryMap.put("customer_profile", PixelTracker.brPixel.customerProfile)
+ }
+
+ //viewId
+ if (!PixelTracker.brPixel.viewId.isNullOrEmpty()) {
+ queryMap.put("view_id", PixelTracker.brPixel.viewId)
+ }
+
+ //Currency
+ if (!PixelTracker.brPixel.currency.isNullOrEmpty()) {
+ queryMap.put("currency", PixelTracker.brPixel.currency)
+ }
+
+ //DomainKey
+ if (!PixelTracker.brPixel.domainKey.isNullOrEmpty()) {
+ queryMap.put("domain_key", PixelTracker.brPixel.domainKey)
+ }
+
+ return queryMap
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/submitpixel/ListenableQueue.kt b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/ListenableQueue.kt
new file mode 100644
index 0000000..7fc7f6d
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/ListenableQueue.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.submitpixel
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.util.*
+import java.util.function.Consumer
+
+/**
+ * ListenableQueue Decorator pattern class to provide callbacks when item is added or removed from the Queue
+ */
+class ListenableQueue(
+ var delegate: Queue
+) : AbstractQueue() {
+ private val listeners: MutableList> = ArrayList()
+
+ fun registerListener(listener: Listener): ListenableQueue {
+ listeners.add(listener)
+ return this
+ }
+
+ override fun iterator(): MutableIterator {
+ return delegate.iterator()
+ }
+
+ override fun offer(e: E): Boolean {
+ // here, add the element and if Queue was empty then notify listeners
+ if (delegate.size == 0) {
+ return if (delegate.offer(e)) {
+ listeners.forEach(Consumer { listener: Listener ->
+ listener.onElementAdded(e)
+ })
+ true
+ } else {
+ false
+ }
+ }
+ return delegate.offer(e)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.N)
+ override fun poll(): E? {
+ // here, remove the element and if Queue was not empty then notify listeners
+ val element = delegate.poll()
+ if (delegate.size > 0) {
+ listeners.forEach(Consumer { listener: Listener ->
+ listener.onElementRemoved(element)
+ })
+ }
+ return element
+ }
+
+ override fun peek(): E? {
+ return delegate.peek()
+ }
+
+ override val size: Int
+ get() = delegate.size
+
+ interface Listener {
+ fun onElementAdded(element: E)
+ fun onElementRemoved(element: E)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelJob.kt b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelJob.kt
new file mode 100644
index 0000000..102dff9
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelJob.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.submitpixel
+
+import com.bloomreach.discovery.pixel.processpixel.FormatterUtils
+import com.bloomreach.discovery.pixel.network.RestClient
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+/**
+ * PixelJob class to trigger submit api call continuously if Queue is not empty
+ */
+internal class PixelJob {
+ private val restClient: RestClient = RestClient()
+ var isInProgress = false
+
+ /**
+ * Method to keep continuously push the Pixels from Queue to API call and removing from Queue
+ */
+ fun pushPixels() {
+ if (PixelQueue.get() != null) {
+ isInProgress = true
+ val uriBuilder = FormatterUtils.mapToUriBuilder(PixelQueue.get()!!)
+ CoroutineScope(Dispatchers.IO).launch {
+ val response = restClient.submitPixel(uriBuilder)
+ //Log.i("PixelJob", response.toString())
+ // PixelQueue.remove()
+ if (PixelQueue.get() != null) {
+ // if Pixels are lying in Queue, pick the next pixel.
+ pushPixels()
+ } else {
+ isInProgress = false
+ }
+ }
+ } else {
+ isInProgress = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelQueue.kt b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelQueue.kt
new file mode 100644
index 0000000..b909082
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/submitpixel/PixelQueue.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.submitpixel
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import java.util.*
+
+/**
+ * PixelQueue class to hold pixels query parameter map to be sent to PIXEL API
+ */
+internal object PixelQueue {
+ val pixels: ListenableQueue> =
+ ListenableQueue(LinkedList())
+
+ /**
+ * Method to add Pixel to Queue
+ * @param queryMap Map of query parameter
+ */
+ fun add(queryMap: MutableMap) {
+ // add to Queue
+ pixels.add(queryMap)
+ }
+
+ /**
+ * Method to pick Pixel from Queue
+ * @return Map of query parameters
+ */
+ fun get(): MutableMap? {
+ // Get the element at the front of the Queue (without removing it)
+ // if the Queue is empty, element() throws NoSuchElementException while peek() returns null.
+ return pixels.peek()
+ }
+
+ /**
+ * Method to remove Pixel from Queue
+ */
+ @RequiresApi(Build.VERSION_CODES.N)
+ fun remove() {
+ //remove an element from the Queue (dequeue operation)
+ //poll() method is similar to remove() (dequeue operation), but it returns null if the Queue is empty instead of throwing an exception.
+ pixels.poll()
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bloomreach/discovery/pixel/validator/PixelValidator.kt b/src/main/java/com/bloomreach/discovery/pixel/validator/PixelValidator.kt
new file mode 100644
index 0000000..cf16dc4
--- /dev/null
+++ b/src/main/java/com/bloomreach/discovery/pixel/validator/PixelValidator.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 Bloomreach
+ */
+
+package com.bloomreach.discovery.pixel.validator
+
+import android.util.Log
+import com.bloomreach.discovery.pixel.PixelTracker
+import com.bloomreach.discovery.pixel.model.validator.PixelValidatorBody
+import com.bloomreach.discovery.pixel.model.validator.response.PixelValidatorResponse
+import com.bloomreach.discovery.pixel.network.RestClient
+import com.bloomreach.discovery.pixel.processpixel.FormatterUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+/**
+ * PixelValidator class to format Pixel Validator request and perform the API call and format the response
+ */
+internal class PixelValidator {
+ private val restClient: RestClient = RestClient()
+ private val protocol = "https"
+ private val path = "pix.gif"
+
+ /**
+ * Method to format the Pixel validator request in required format and perform validator API call
+ *
+ * @param queryMap Map of pixel values in required format
+ */
+ fun validatePixel(queryMap: MutableMap) {
+
+ // operations to form post body
+ val uriBuilder = FormatterUtils.mapToUriBuilder(queryMap)
+ val query = uriBuilder.build().toString()
+ uriBuilder.scheme(protocol)
+ uriBuilder.authority(PixelTracker.brPixel.pixelUrlByRegion)
+ uriBuilder.appendPath(path)
+
+ val postBody = PixelValidatorBody(
+ source = uriBuilder.build().toString(),
+ protocol = protocol,
+ host = PixelTracker.brPixel.pixelUrlByRegion,
+ port = "",
+ query = query,
+ params = queryMap,
+ file = path,
+ hash = "",
+ path = "/$path",
+ relative = "/$path$query",
+ segments = listOf(path)
+ )
+
+// for Rest Call
+ CoroutineScope(Dispatchers.IO).launch {
+ // Validate Pixel REST call
+ val response = restClient.validatePixel(postBody)
+ printPixelValidatorResponse(response)
+ }
+ }
+
+ /**
+ * Method to format the Pixel validator response object in required format and print it to logs.
+ *
+ * @param response PixelValidatorResponse received from Pixel validator call
+ */
+ private fun printPixelValidatorResponse(response: PixelValidatorResponse?) {
+ if (response == null) {
+ return
+ }
+
+ if (!response.errors.isNullOrEmpty()) {
+ Log.v(
+ "Pixel Validator",
+ "\n==========PIXEL VALIDATOR ERROR========= \n\n${formResponseString(response)} \n\n"
+ )
+ } else if (!response.warns.isNullOrEmpty()) {
+ Log.v(
+ "Pixel Validator",
+ "\n==========PIXEL VALIDATOR WARNING========= \n\n${formResponseString(response)} \n\n"
+ )
+ } else {
+ Log.v(
+ "Pixel Validator",
+ "\n==========PIXEL VALIDATOR SUCCESS========= \n\n${formResponseString(response)} \n\n"
+ )
+ }
+ }
+
+ /**
+ * Method to format the Pixel validator response object in required format
+ *
+ * @param response PixelValidatorResponse received from Pixel validator call
+ */
+ private fun formResponseString(response: PixelValidatorResponse): String {
+ return buildString {
+ this.append("Pixel Name: ${response.displayName}\n\n")
+
+ if (!response.errors.isNullOrEmpty()) {
+ this.append("Error in Parameters ==>")
+ for (error in response.errors) {
+ this.append("${error.name}: ${error.value}\n")
+ this.append("Description: ${error.description}\n\n")
+ }
+ }
+
+ if (!response.warns.isNullOrEmpty()) {
+ this.append("Warning in Parameters ==>\n")
+ for (warn in response.warns) {
+ this.append("${warn.name}: ${warn.value}\n")
+ this.append("Description: ${warn.description}\n\n")
+ }
+ }
+
+ if (!response.success.isNullOrEmpty()) {
+ this.append("Success Parameters ==>\n")
+ for (success in response.success) {
+ this.append("${success.name}: ${success.value}\n")
+ }
+ }
+ }
+ }
+
+
+}
\ No newline at end of file