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(); // We didn't do anything this iteration, just reduce the count
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