1 package io.getunleash.repository;
2
3 import io.getunleash.FeatureToggle;
4 import io.getunleash.Segment;
5 import io.getunleash.UnleashException;
6 import io.getunleash.event.EventDispatcher;
7 import io.getunleash.event.UnleashReady;
8 import io.getunleash.lang.Nullable;
9 import io.getunleash.util.Throttler;
10 import io.getunleash.util.UnleashConfig;
11 import io.getunleash.util.UnleashScheduledExecutor;
12 import java.util.Collections;
13 import java.util.List;
14 import java.util.function.Consumer;
15 import java.util.stream.Collectors;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18
19 public class FeatureRepository implements IFeatureRepository {
20 private static final Logger LOGGER = LoggerFactory.getLogger(FeatureRepository.class);
21 private final UnleashConfig unleashConfig;
22 private final BackupHandler<FeatureCollection> featureBackupHandler;
23 private final FeatureBootstrapHandler featureBootstrapHandler;
24 private final FeatureFetcher featureFetcher;
25 private final EventDispatcher eventDispatcher;
26
27 private final Throttler throttler;
28
29 private FeatureCollection featureCollection;
30 private boolean ready;
31
32 public FeatureRepository(UnleashConfig unleashConfig) {
33 this(unleashConfig, new FeatureBackupHandlerFile(unleashConfig));
34 }
35
36 public FeatureRepository(
37 UnleashConfig unleashConfig,
38 final BackupHandler<FeatureCollection> featureBackupHandler) {
39 this.unleashConfig = unleashConfig;
40 this.featureBackupHandler = featureBackupHandler;
41 this.featureFetcher = unleashConfig.getUnleashFeatureFetcherFactory().apply(unleashConfig);
42 this.featureBootstrapHandler = new FeatureBootstrapHandler(unleashConfig);
43 this.eventDispatcher = new EventDispatcher(unleashConfig);
44 this.throttler =
45 new Throttler(
46 (int) unleashConfig.getFetchTogglesInterval(),
47 300,
48 unleashConfig.getUnleashURLs().getFetchTogglesURL());
49 this.initCollections(unleashConfig.getScheduledExecutor());
50 }
51
52 protected FeatureRepository(
53 UnleashConfig unleashConfig,
54 BackupHandler<FeatureCollection> featureBackupHandler,
55 EventDispatcher eventDispatcher,
56 FeatureFetcher featureFetcher,
57 FeatureBootstrapHandler featureBootstrapHandler) {
58 this.unleashConfig = unleashConfig;
59 this.featureBackupHandler = featureBackupHandler;
60 this.featureFetcher = featureFetcher;
61 this.featureBootstrapHandler = featureBootstrapHandler;
62 this.eventDispatcher = eventDispatcher;
63 this.throttler =
64 new Throttler(
65 (int) unleashConfig.getFetchTogglesInterval(),
66 300,
67 unleashConfig.getUnleashURLs().getFetchTogglesURL());
68 this.initCollections(unleashConfig.getScheduledExecutor());
69 }
70
71 protected FeatureRepository(
72 UnleashConfig unleashConfig,
73 FeatureBackupHandlerFile featureBackupHandler,
74 UnleashScheduledExecutor executor,
75 FeatureFetcher featureFetcher,
76 FeatureBootstrapHandler featureBootstrapHandler) {
77 this.unleashConfig = unleashConfig;
78 this.featureBackupHandler = featureBackupHandler;
79 this.featureFetcher = featureFetcher;
80 this.featureBootstrapHandler = featureBootstrapHandler;
81 this.eventDispatcher = new EventDispatcher(unleashConfig);
82 this.throttler =
83 new Throttler(
84 (int) unleashConfig.getFetchTogglesInterval(),
85 300,
86 unleashConfig.getUnleashURLs().getFetchTogglesURL());
87 this.initCollections(executor);
88 }
89
90 @SuppressWarnings("FutureReturnValueIgnored")
91 private void initCollections(UnleashScheduledExecutor executor) {
92 this.featureCollection = this.featureBackupHandler.read();
93 if (this.featureCollection.getToggleCollection().getFeatures().isEmpty()) {
94 this.featureCollection = this.featureBootstrapHandler.read();
95 }
96
97 if (unleashConfig.isSynchronousFetchOnInitialisation()) {
98 if (this.unleashConfig.getStartupExceptionHandler() != null) {
99 updateFeatures(this.unleashConfig.getStartupExceptionHandler()).run();
100 } else {
101 updateFeatures(
102 e -> {
103 throw e;
104 })
105 .run();
106 }
107 }
108
109 if (!unleashConfig.isDisablePolling()) {
110 Runnable updateFeatures = updateFeatures(this.eventDispatcher::dispatch);
111 if (unleashConfig.getFetchTogglesInterval() > 0) {
112 executor.setInterval(updateFeatures, 0, unleashConfig.getFetchTogglesInterval());
113 } else {
114 executor.scheduleOnce(updateFeatures);
115 }
116 }
117 }
118
119 private Runnable updateFeatures(final Consumer<UnleashException> handler) {
120 return () -> {
121 if (throttler.performAction()) {
122 try {
123 ClientFeaturesResponse response = featureFetcher.fetchFeatures();
124 eventDispatcher.dispatch(response);
125 if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) {
126 SegmentCollection segmentCollection = response.getSegmentCollection();
127 featureCollection =
128 new FeatureCollection(
129 response.getToggleCollection(),
130 segmentCollection != null
131 ? segmentCollection
132 : new SegmentCollection(Collections.emptyList()));
133
134 featureBackupHandler.write(featureCollection);
135 } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) {
136 if (!ready && unleashConfig.isSynchronousFetchOnInitialisation()) {
137 throw new UnleashException(
138 String.format(
139 "Could not initialize Unleash, got response code %d",
140 response.getHttpStatusCode()),
141 null);
142 }
143 if (ready) {
144 throttler.handleHttpErrorCodes(response.getHttpStatusCode());
145 }
146 return;
147 }
148 throttler.decrementFailureCountAndResetSkips();
149 if (!ready) {
150 eventDispatcher.dispatch(new UnleashReady());
151 ready = true;
152 }
153 } catch (UnleashException e) {
154 handler.accept(e);
155 }
156 } else {
157 throttler.skipped();
158 }
159 };
160 }
161
162 @Override
163 public @Nullable FeatureToggle getToggle(String name) {
164 return featureCollection.getToggleCollection().getToggle(name);
165 }
166
167 @Override
168 public List<String> getFeatureNames() {
169 return featureCollection.getToggleCollection().getFeatures().stream()
170 .map(FeatureToggle::getName)
171 .collect(Collectors.toList());
172 }
173
174 @Override
175 public Segment getSegment(Integer id) {
176 return featureCollection.getSegmentCollection().getSegment(id);
177 }
178
179 public Integer getFailures() {
180 return this.throttler.getFailures();
181 }
182
183 public Integer getSkips() {
184 return this.throttler.getSkips();
185 }
186 }
187