-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathEventSupport.java
174 lines (154 loc) · 6.24 KB
/
EventSupport.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package dev.openfeature.sdk;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import edu.umd.cs.findbugs.annotations.Nullable;
import lombok.extern.slf4j.Slf4j;
/**
* Util class for storing and running handlers.
*/
@Slf4j
class EventSupport {
// we use a v4 uuid as a "placeholder" for anonymous clients, since
// ConcurrentHashMap doesn't support nulls
private static final String defaultClientUuid = UUID.randomUUID().toString();
private final ExecutorService taskExecutor = Executors.newCachedThreadPool();
private final Map<String, HandlerStore> handlerStores = new ConcurrentHashMap<>();
private final HandlerStore globalHandlerStore = new HandlerStore();
/**
* Run all the event handlers associated with this client name.
* If the client name is null, handlers attached to unnamed clients will run.
*
* @param clientName the client name to run event handlers for, or null
* @param event the event type
* @param eventDetails the event details
*/
public void runClientHandlers(@Nullable String clientName, ProviderEvent event, EventDetails eventDetails) {
clientName = Optional.ofNullable(clientName)
.orElse(defaultClientUuid);
// run handlers if they exist
Optional.ofNullable(handlerStores.get(clientName))
.filter(store -> Optional.of(store).isPresent())
.map(store -> store.handlerMap.get(event))
.ifPresent(handlers -> handlers
.forEach(handler -> runHandler(handler, eventDetails)));
}
/**
* Run all the API (global) event handlers.
*
* @param event the event type
* @param eventDetails the event details
*/
public void runGlobalHandlers(ProviderEvent event, EventDetails eventDetails) {
globalHandlerStore.handlerMap.get(event)
.forEach(handler -> {
runHandler(handler, eventDetails);
});
}
/**
* Add a handler for the specified client name, or all unnamed clients.
*
* @param clientName the client name to add handlers for, or else the unnamed
* client
* @param event the event type
* @param handler the handler function to run
*/
public void addClientHandler(@Nullable String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
final String name = Optional.ofNullable(clientName)
.orElse(defaultClientUuid);
// lazily create and cache a HandlerStore if it doesn't exist
HandlerStore store = Optional.ofNullable(this.handlerStores.get(name))
.orElseGet(() -> {
HandlerStore newStore = new HandlerStore();
this.handlerStores.put(name, newStore);
return newStore;
});
store.addHandler(event, handler);
}
/**
* Remove a client event handler for the specified event type.
*
* @param clientName the name of the client handler to remove, or null to remove
* from unnamed clients
* @param event the event type
* @param handler the handler ref to be removed
*/
public void removeClientHandler(String clientName, ProviderEvent event, Consumer<EventDetails> handler) {
clientName = Optional.ofNullable(clientName)
.orElse(defaultClientUuid);
this.handlerStores.get(clientName).removeHandler(event, handler);
}
/**
* Add a global event handler of the specified event type.
*
* @param event the event type
* @param handler the handler to be added
*/
public void addGlobalHandler(ProviderEvent event, Consumer<EventDetails> handler) {
this.globalHandlerStore.addHandler(event, handler);
}
/**
* Remove a global event handler for the specified event type.
*
* @param event the event type
* @param handler the handler ref to be removed
*/
public void removeGlobalHandler(ProviderEvent event, Consumer<EventDetails> handler) {
this.globalHandlerStore.removeHandler(event, handler);
}
/**
* Get all client names for which we have event handlers registered.
*
* @return set of client names
*/
public Set<String> getAllClientNames() {
return this.handlerStores.keySet();
}
/**
* Run the passed handler on the taskExecutor.
*
* @param handler the handler to run
* @param eventDetails the event details
*/
public void runHandler(Consumer<EventDetails> handler, EventDetails eventDetails) {
taskExecutor.submit(() -> {
try {
handler.accept(eventDetails);
} catch (Exception e) {
log.error("Exception in event handler {}", handler, e);
}
});
}
/**
* Stop the event handler task executor.
*/
public void shutdown() {
taskExecutor.shutdown();
}
// Handler store maintains a set of handlers for each event type.
// Each client in the SDK gets it's own handler store, which is lazily
// instantiated when a handler is added to that client.
static class HandlerStore {
private final Map<ProviderEvent, List<Consumer<EventDetails>>> handlerMap;
{
handlerMap = new ConcurrentHashMap<ProviderEvent, List<Consumer<EventDetails>>>();
handlerMap.put(ProviderEvent.PROVIDER_READY, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_ERROR, new ArrayList<>());
handlerMap.put(ProviderEvent.PROVIDER_STALE, new ArrayList<>());
}
void addHandler(ProviderEvent event, Consumer<EventDetails> handler) {
handlerMap.get(event).add(handler);
}
void removeHandler(ProviderEvent event, Consumer<EventDetails> handler) {
handlerMap.get(event).remove(handler);
}
}
}