Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.optimizely.ab.event.internal.payload.Event.ClientEngine;
import com.optimizely.ab.internal.EventTagUtils;
import com.optimizely.ab.notification.NotificationBroadcaster;
import com.optimizely.ab.notification.NotificationCenter;
import com.optimizely.ab.notification.NotificationListener;

import org.slf4j.Logger;
Expand Down Expand Up @@ -93,6 +94,8 @@ public class Optimizely {
@VisibleForTesting final EventHandler eventHandler;
@VisibleForTesting final ErrorHandler errorHandler;
@VisibleForTesting final NotificationBroadcaster notificationBroadcaster = new NotificationBroadcaster();
public final NotificationCenter notificationCenter = new NotificationCenter();

@Nullable private final UserProfileService userProfileService;

private Optimizely(@Nonnull ProjectConfig projectConfig,
Expand Down Expand Up @@ -206,6 +209,9 @@ private void sendImpression(@Nonnull ProjectConfig projectConfig,
}

notificationBroadcaster.broadcastExperimentActivated(experiment, userId, filteredAttributes, variation);

notificationCenter.sendNotifications(NotificationCenter.NotificationType.Activate, experiment, userId,
filteredAttributes, variation, impressionEvent);
} else {
logger.info("Experiment has \"Launched\" status so not dispatching event during activation.");
}
Expand Down Expand Up @@ -292,6 +298,8 @@ public void track(@Nonnull String eventName,

notificationBroadcaster.broadcastEventTracked(eventName, userId, filteredAttributes, eventValue,
conversionEvent);
notificationCenter.sendNotifications(NotificationCenter.NotificationType.Track, eventName, userId,
filteredAttributes, eventTags, conversionEvent);
}

//======== FeatureFlag APIs ========//
Expand Down Expand Up @@ -688,6 +696,7 @@ public UserProfileService getUserProfileService() {
*
* @param listener listener to add
*/
@Deprecated
public void addNotificationListener(@Nonnull NotificationListener listener) {
notificationBroadcaster.addListener(listener);
}
Expand All @@ -697,13 +706,15 @@ public void addNotificationListener(@Nonnull NotificationListener listener) {
*
* @param listener listener to remove
*/
@Deprecated
public void removeNotificationListener(@Nonnull NotificationListener listener) {
notificationBroadcaster.removeListener(listener);
}

/**
* Remove all {@link NotificationListener}.
*/
@Deprecated
public void clearNotificationListeners() {
notificationBroadcaster.clearListeners();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
*
* Copyright 2017, Optimizely and contributors
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.optimizely.ab.notification;

import com.optimizely.ab.config.Experiment;
import com.optimizely.ab.config.Variation;
import com.optimizely.ab.event.LogEvent;

import java.util.Map;


public abstract class ActivateNotification extends NotificationListener {

/**
* Base notify called with var args. This method parses the parameters and calls the abstract method.
* @param args - variable argument list based on the type of notification.
*/
@Override
public final void notify(Object... args) {
assert(args[0] instanceof Experiment);
Experiment experiment = (Experiment) args[0];
assert(args[1] instanceof String);
String userId = (String) args[1];
assert(args[2] instanceof java.util.Map);
Map<String, String> attributes = (Map<String, String>) args[2];
assert(args[3] instanceof Variation);
Variation variation = (Variation) args[3];
assert(args[4] instanceof LogEvent);
LogEvent logEvent = (LogEvent) args[4];

onActivate(experiment, userId, attributes, variation, logEvent);
}

/**
* onActivate called when an activate was triggered
* @param experiment - The experiment object being activated.
* @param userId - The userId passed into activate.
* @param attributes - The filtered attribute list passed into activate
* @param variation - The variation that was returned from activate.
* @param event - The impression event that was triggered.
*/
public abstract void onActivate(@javax.annotation.Nonnull Experiment experiment,
@javax.annotation.Nonnull String userId,
@javax.annotation.Nonnull Map<String, String> attributes,
@javax.annotation.Nonnull Variation variation,
@javax.annotation.Nonnull LogEvent event) ;

}

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
/**
* Manages Optimizely SDK notification listeners and broadcasts messages.
*/
@Deprecated
public class NotificationBroadcaster {

private static final Logger logger = LoggerFactory.getLogger(NotificationBroadcaster.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
*
* Copyright 2017, Optimizely and contributors
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab.notification;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


/**
* This class handles impression and conversion notificationsListeners. It replaces NotificationBroadcaster and is intended to be
* more flexible.
*/
public class NotificationCenter {
/**
* NotificationType is used for the notification types supported.
*/
public enum NotificationType {

Activate(ActivateNotification.class), // Activate was called. Track an impression event
Track(TrackNotification.class); // Track was called. Track a conversion event

private Class notificationTypeClass;

NotificationType(Class notificationClass) {
this.notificationTypeClass = notificationClass;
}

public Class getNotificationTypeClass() {
return notificationTypeClass;
}
};


// the notification id is incremented and is assigned as the callback id, it can then be used to remove the notification.
private int notificationListenerID = 1;

final private static Logger logger = LoggerFactory.getLogger(NotificationCenter.class);

// notification holder holds the id as well as the notification.
private static class NotificationHolder
{
int notificationId;
NotificationListener notificationListener;

NotificationHolder(int id, NotificationListener notificationListener) {
notificationId = id;
this.notificationListener = notificationListener;
}
}

/**
* Instantiate a new NotificationCenter
*/
public NotificationCenter() {
notificationsListeners.put(NotificationType.Activate, new ArrayList<NotificationHolder>());
notificationsListeners.put(NotificationType.Track, new ArrayList<NotificationHolder>());
}

// private list of notification by notification type.
// we used a list so that notification order can mean something.
private Map<NotificationType, ArrayList<NotificationHolder>> notificationsListeners =new HashMap<NotificationType, ArrayList<NotificationHolder>>();


/**
* Add a notification listener to the notification center.
*
* @param notificationType - enum NotificationType to add.
* @param notificationListener - Notification to add.
* @return the notification id used to remove the notification. It is greater than 0 on success.
*/
public int addNotification(NotificationType notificationType, NotificationListener notificationListener) {

Class clazz = notificationType.notificationTypeClass;
if (clazz == null || !clazz.isInstance(notificationListener)) {
logger.warn("Notification listener was the wrong type. It was not added to the notification center.");
return -1;
}

for (NotificationHolder holder : notificationsListeners.get(notificationType)) {
if (holder.notificationListener == notificationListener) {
logger.warn("Notificication listener was already added");
return -1;
}
}
int id = notificationListenerID++;
notificationsListeners.get(notificationType).add(new NotificationHolder(id, notificationListener ));
logger.info("Notification listener {} was added with id {}", notificationListener.toString(), id);
return id;
}

/**
* Remove the notification listener based on the notificationId passed back from addNotification.
* @param notificationID the id passed back from add notification.
* @return true if removed otherwise false (if the notification is already registered, it returns false).
*/
public boolean removeNotification(int notificationID) {
for (NotificationType type : NotificationType.values()) {
for (NotificationHolder holder : notificationsListeners.get(type)) {
if (holder.notificationId == notificationID) {
notificationsListeners.get(type).remove(holder);
logger.info("Notification listener removed {}", notificationID);
return true;
}
}
}

logger.warn("Notification listener with id {} not found", notificationID);

return false;
}

/**
* Clear out all the notification listeners.
*/
public void clearAllNotifications() {
for (NotificationType type : NotificationType.values()) {
clearNotifications(type);
}
}

/**
* Clear notification listeners by notification type.
* @param notificationType type of notificationsListeners to remove.
*/
public void clearNotifications(NotificationType notificationType) {
notificationsListeners.get(notificationType).clear();
}

// fire a notificaiton of a certain type. The arg list changes depending on the type of notification sent.
public void sendNotifications(NotificationType notificationType, Object ...args) {
ArrayList<NotificationHolder> holders = notificationsListeners.get(notificationType);
for (NotificationHolder holder : holders) {
try {
holder.notificationListener.notify(args);
}
catch (Exception e) {
logger.error("Unexpected exception calling notification listener {}", holder.notificationId, e);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public abstract class NotificationListener {
* @param eventValue an integer to be aggregated for the event
* @param logEvent the log event sent to the event dispatcher
*/
@Deprecated
public void onEventTracked(@Nonnull String eventKey,
@Nonnull String userId,
@Nonnull Map<String, String> attributes,
Expand All @@ -60,9 +61,18 @@ public void onEventTracked(@Nonnull String eventKey,
* @param attributes a map of attributes about the user
* @param variation the key of the variation that was bucketed
*/
@Deprecated
public void onExperimentActivated(@Nonnull Experiment experiment,
@Nonnull String userId,
@Nonnull Map<String, String> attributes,
@Nonnull Variation variation) {
}

/**
* This is the new method of notification. Implementation classes such as {@link com.optimizely.ab.notification.ActivateNotification}
* will implement this call and provide another method with the correct parameters
* Notify called when a notification is triggered via the {@link com.optimizely.ab.notification.NotificationCenter}
* @param args - variable argument list based on the type of notification.
*/
public abstract void notify(Object... args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
*
* Copyright 2017, Optimizely and contributors
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab.notification;

import java.util.Map;
import javax.annotation.Nonnull;

import com.optimizely.ab.event.LogEvent;

/**
* This class handles the track event notification.
*/
public abstract class TrackNotification extends NotificationListener {

/**
* Base notify called with var args. This method parses the parameters and calls the abstract method.
* @param args - variable argument list based on the type of notification.
*/
@Override
public final void notify(Object... args) {
assert(args[0] instanceof String);
String eventKey = (String) args[0];
assert(args[1] instanceof String);
String userId = (String) args[1];
assert(args[2] instanceof java.util.Map);
Map<String, String> attributes = (Map<String, String>) args[2];
assert(args[3] instanceof java.util.Map);
Map<String, ?> eventTags = (Map<String, ?>) args[3];
assert(args[4] instanceof LogEvent);
LogEvent logEvent = (LogEvent) args[4];

onTrack(eventKey, userId,attributes,eventTags, logEvent);
}

/**
* onTrack is called when a track event is triggered
* @param eventKey - The event key that was triggered.
* @param userId - user id passed into track.
* @param attributes - filtered attributes list after passed into track
* @param eventTags - event tags if any were passed in.
* @param event - The event being recorded.
*/
public abstract void onTrack(@Nonnull String eventKey,
@Nonnull String userId,
@Nonnull Map<String, String> attributes,
@Nonnull Map<String, ?> eventTags,
@Nonnull LogEvent event) ;
}
Loading