Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AMSDK-11312] - Handle boot event + Bugfixes #19

Merged
merged 6 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public void error(final ExtensionError extensionError) {

final Event updateIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.UPDATE_IDENTITIES,
IdentityConstants.EventType.EDGE_IDENTITY,
IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build();
IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asXDMMap()).build();
MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback);
}

Expand Down Expand Up @@ -162,7 +162,7 @@ public void error(final ExtensionError extensionError) {

final Event removeIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.REMOVE_IDENTITIES,
IdentityConstants.EventType.EDGE_IDENTITY,
IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build();
IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asXDMMap()).build();
MobileCore.dispatchEvent(removeIdentitiesEvent, errorCallback);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final class EventSource {
static final String REQUEST_RESET = "com.adobe.eventSource.requestReset";
static final String SHARED_STATE = "com.adobe.eventSource.sharedState";
static final String RESET_COMPLETE = "com.adobe.eventSource.resetComplete";
static final String BOOTED = "com.adobe.eventSource.booted";
private EventSource() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class IdentityExtension extends Extension {
* Called during the Identity extension's registration.
* The following listeners are registered during this extension's registration.
* <ul>
* <li> Listener {@link ListenerEventHubBoot} to listen for event with eventType {@link IdentityConstants.EventType#HUB}
* and EventSource {@link IdentityConstants.EventSource#BOOTED}</li>
* <li> Listener {@link ListenerEdgeIdentityRequestIdentity} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY}
* and EventSource {@link IdentityConstants.EventSource#REQUEST_IDENTITY}</li>
* <li> Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityConstants.EventType#GENERIC_IDENTITY}
Expand Down Expand Up @@ -63,12 +65,14 @@ public void error(final ExtensionError extensionError) {
}
};

extensionApi.registerEventListener(IdentityConstants.EventType.HUB, IdentityConstants.EventSource.BOOTED, ListenerEventHubBoot.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY, ListenerEdgeIdentityRequestIdentity.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.UPDATE_IDENTITY, ListenerEdgeIdentityUpdateIdentity.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REMOVE_IDENTITY, ListenerEdgeIdentityRemoveIdentity.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.HUB, IdentityConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback);
extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback);
state.bootUp();
}

/**
Expand All @@ -91,16 +95,32 @@ protected String getVersion() {
return IdentityConstants.EXTENSION_VERSION;
}


/**
* Call this method with the EventHub's Boot event to handle the boot operation of the {@code Identity} Extension.
* <p>
* On boot share the initial identities loaded from persistence to XDM shared state.
*
* @param event the boot {@link Event}
*/
void handleEventHubBoot(final Event event) {

// share the initial XDMSharedState on bootUp
final Map currentIdentities = state.getIdentityProperties().toXDMData(false);
if (currentIdentities == null || currentIdentities.isEmpty()) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Nothing loaded from persistence for initial Identity XDM shared state on boot");
PravinPK marked this conversation as resolved.
Show resolved Hide resolved
return;
}

shareIdentityXDMSharedState(event);
}

/**
* Handles update identity requests to add/update customer identifiers.
*
* @param event the edge update identity {@link Event}
*/
void handleUpdateIdentities(final Event event) {
if (!canProcessEvents()) {
Copy link
Contributor Author

@PravinPK PravinPK Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to check for this condition for every event since
state.bootUp happens in the construction without fail

MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process update identity event. canProcessEvents returned false.");
return;
}
final Map<String, Object> eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners
final IdentityMap map = IdentityMap.fromData(eventData);
if (map == null) {
Expand All @@ -118,10 +138,6 @@ void handleUpdateIdentities(final Event event) {
* @param event the edge remove identity request {@link Event}
*/
void handleRemoveIdentity(final Event event) {
if (!canProcessEvents()) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process remove identity event. canProcessEvents returned false.");
return;
}
final Map<String, Object> eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners
final IdentityMap map = IdentityMap.fromData(eventData);
if (map == null) {
Expand All @@ -145,11 +161,6 @@ void handleGenericIdentityRequest(final Event event) {
* @param event an event of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState}
*/
void handleHubSharedState(final Event event) {
if (!canProcessEvents()) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process direct Identity shared state change event. canProcessEvents returned false.");
return;
}

if (event == null || event.getEventData() == null) {
return;
}
Expand Down Expand Up @@ -190,10 +201,6 @@ void handleHubSharedState(final Event event) {
* @param event the identity request {@link Event}
*/
void handleIdentityRequest(final Event event) {
if (!canProcessEvents()) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request identities event. canProcessEvents returned false.");
return;
}

Map<String, Object> xdmData = state.getIdentityProperties().toXDMData(true);
Event responseEvent = new Event.Builder(IdentityConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME,
Expand All @@ -219,10 +226,6 @@ public void error(ExtensionError extensionError) {
* @param event the identity request reset {@link Event}
*/
void handleRequestReset(final Event event) {
if (!canProcessEvents()) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false.");
return;
}
state.resetIdentifiers();
shareIdentityXDMSharedState(event);

Expand Down Expand Up @@ -286,27 +289,4 @@ public void error(final ExtensionError extensionError) {
extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), event, errorCallback);
}

/**
* Determines if this Identity is ready to handle events, this is determined by if the extension has booted up
*
* @return True if we can process events, false otherwise
*/
private boolean canProcessEvents() {
if (state.hasBooted()) {
return true;
} // we have booted, return true

final ExtensionApi extensionApi = super.getApi();
if (extensionApi == null) {
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process events");
return false;
}

if (state.bootupIfReady()) {
extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), null, null);
return true;
}

return false; // cannot handle any events until we have booted
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ void addItem(final IdentityItem item, final String namespace, final boolean isFi
* @return a {@link Map} representing this {@link IdentityMap} object
*/
Map<String, List<Map<String, Object>>> toObjectMap() {

final Map<String, List<Map<String, Object>>> map = new HashMap<>();

for (String namespace : identityItems.keySet()) {
Expand Down Expand Up @@ -215,8 +216,15 @@ boolean clearItemsForNamespace(final String namespace) {
*
* @return {@link Map<String,Object>} representation of IdentityMap
*/
Map<String, Object> asEventData() {
return new HashMap<String, Object>(identityItems);
Map<String, Object> asXDMMap() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bummer asEventData never worked as we deserialize it while parsing from the event.

the new function gives the IdentityMap in the predefined XDMFormat

final Map<String, Object> xdmData = new HashMap<>();

final Map<String, List<Map<String, Object>>> identityMap = this.toObjectMap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we consolidate toObjectMap and asXDMMap into one?

if (identityMap != null || !identityMap.isEmpty() ) {
xdmData.put(IdentityConstants.XDMKeys.IDENTITY_MAP, identityMap);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If using as EventData, don't include the "identityMap" object (see iOS implementation). I think instead of adding this asXDMMap method, you can just use the existing toObjectMap method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had the same comment, either reuse toObjectMap or keep only toXDMMap

}

return xdmData;
}


Expand Down Expand Up @@ -256,7 +264,7 @@ static IdentityMap fromData(Map<String, Object> data) {
// ========================================================================================
// private methods
// ========================================================================================
private void addItemToMap(final IdentityItem item, final String namespace, final boolean isFirstItem) {
private void addItemToMap(final IdentityItem newItem, final String namespace, final boolean isFirstItem) {
// check if namespace exists
final List<IdentityItem> itemList;

Expand All @@ -266,10 +274,14 @@ private void addItemToMap(final IdentityItem item, final String namespace, final
itemList = new ArrayList<>();
}

if (isFirstItem) {
itemList.add(0, item);
// Check if the item already exist in the current ItemList
Copy link
Contributor Author

@PravinPK PravinPK Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an extra if statementnt to check and replace the IdentityItem if it already exists

int index = itemList.indexOf(newItem);
if (index >= 0) {
itemList.set(index,newItem);
} else if (isFirstItem) {
itemList.add(0, newItem);
} else {
itemList.add(item);
itemList.add(newItem);
}

identityItems.put(namespace, itemList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
*/
class IdentityState {
private static String LOG_TAG = "IdentityState";
private boolean hasBooted = false;
private IdentityProperties identityProperties;

/**
Expand All @@ -39,22 +38,11 @@ IdentityProperties getIdentityProperties() {
return identityProperties;
}

/**
* @return Returns true if this extension has booted, false otherwise
*/
boolean hasBooted() {
return hasBooted;
}

/**
* Completes init for the Identity extension.
*
* @return True if we should share state after bootup, false otherwise
*/
boolean bootupIfReady() {
if (hasBooted) {
return true;
}
void bootUp() {
PravinPK marked this conversation as resolved.
Show resolved Hide resolved
// Load properties from local storage
identityProperties = IdentityStorageService.loadPropertiesFromPersistence();

Expand All @@ -75,9 +63,7 @@ boolean bootupIfReady() {
IdentityStorageService.savePropertiesToPersistence(identityProperties);
}

hasBooted = true;
MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Edge Identity has successfully booted up");
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2021 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.edge.identity;

import com.adobe.marketing.mobile.Event;
import com.adobe.marketing.mobile.ExtensionApi;
import com.adobe.marketing.mobile.ExtensionListener;
import com.adobe.marketing.mobile.LoggingMode;
import com.adobe.marketing.mobile.MobileCore;

public class ListenerEventHubBoot extends ExtensionListener {
PravinPK marked this conversation as resolved.
Show resolved Hide resolved
/**
* Constructor.
*
* @param extensionApi an instance of {@link ExtensionApi}
* @param type the {@link String} eventType this listener is registered to handle
* @param source the {@link String} eventSource this listener is registered to handle
*/
ListenerEventHubBoot(final ExtensionApi extensionApi, final String type, final String source) {
super(extensionApi, type, source);
}

/**
* Method that gets called when event with event type {@link IdentityConstants.EventType#HUB}
* and with event source {@link IdentityConstants.EventSource#BOOTED} is dispatched through eventHub.
*
* @param event the boot {@link Event}
*/
@Override
public void hear(final Event event) {

final IdentityExtension parentExtension = getIdentityExtension();

if (parentExtension == null) {
MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG,
"The parent extension associated with the ListenerEventHubBoot is null, ignoring this event.");
return;
}

parentExtension.handleEventHubBoot(event);
}

/**
* Returns the parent extension associated with the listener.
*
* @return a {@link IdentityExtension} object registered with the eventHub
*/
IdentityExtension getIdentityExtension() {
return (IdentityExtension) getParentExtension();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void test_ListenersRegistration() {
// constructor is called in the setup step()

// verify 2 listeners are registered
verify(mockExtensionApi, times(6)).registerEventListener(anyString(),
verify(mockExtensionApi, times(7)).registerEventListener(anyString(),
anyString(), any(Class.class), any(ExtensionErrorCallback.class));

// verify listeners are registered with correct event source and type
Expand All @@ -109,6 +109,8 @@ public void test_ListenersRegistration() {
eq(IdentityConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture());
verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.HUB),
eq(IdentityConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture());
verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.HUB),
eq(IdentityConstants.EventSource.BOOTED), eq(ListenerEventHubBoot.class), callbackCaptor.capture());

// verify the callback
ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue();
Expand Down Expand Up @@ -183,6 +185,7 @@ public void test_handleIdentityRequest_loadsPersistedECID() {
final ArgumentCaptor<Event> requestEventCaptor = ArgumentCaptor.forClass(Event.class);

// test
extension = new IdentityExtension(mockExtensionApi);
extension.handleIdentityRequest(event);

// verify
Expand Down Expand Up @@ -513,8 +516,8 @@ public void test_handleRemoveIdentity() throws Exception {
assertTrue(eventCaptor.getAllValues().isEmpty());

// verify persistence
verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture());
Map<String, String> persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1));
verify(mockSharedPreferenceEditor, times(3)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture());
Map<String, String> persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(2));
assertNull(persistedData.get("identityMap.UserId[0].id"));
assertEquals("token", persistedData.get("identityMap.PushId[0].id"));
}
Expand Down Expand Up @@ -552,8 +555,8 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce


// verify persistence
verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture());
Map<String, String> persistedData = flattenJSONString(persistenceValueCaptor.getValue());
verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture());
Map<String, String> persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1));
assertEquals("someGAID", persistedData.get("identityMap.GAID[0].id"));
assertEquals("someECID", persistedData.get("identityMap.ECID[0].id"));
assertEquals("someIDFA", persistedData.get("identityMap.IDFA[0].id"));
Expand All @@ -578,7 +581,7 @@ public void test_handleRemoveIdentity_NullData() throws Exception {
verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), eq(removeIdentityEvent), any(ExtensionErrorCallback.class));

// verify persistence
verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString());
verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString());// once during constructor and other during remove IdentityEvent
}


Expand Down
Loading