1 package com.atlassian.sal.core.features;
2
3 import com.atlassian.sal.api.features.SiteDarkFeaturesStorage;
4 import com.atlassian.sal.api.pluginsettings.PluginSettings;
5 import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
6 import com.google.common.collect.ImmutableSet;
7 import io.atlassian.util.concurrent.ResettableLazyReference;
8 import org.apache.commons.lang3.StringUtils;
9
10 import javax.annotation.Nullable;
11 import java.util.ArrayList;
12 import java.util.LinkedList;
13 import java.util.List;
14 import java.util.function.Function;
15
16 import static com.google.common.base.Preconditions.checkNotNull;
17
18
19
20
21
22
23
24
25 public class DefaultSiteDarkFeaturesStorage implements SiteDarkFeaturesStorage {
26 private static final String SITE_WIDE_DARK_FEATURES = "atlassian.sitewide.dark.features";
27
28 private final ResettableLazyReference<ImmutableSet<String>> cache = new ResettableLazyReference<ImmutableSet<String>>() {
29 @Override
30 protected ImmutableSet<String> create() {
31 return ImmutableSet.copyOf(load());
32 }
33 };
34
35 private final PluginSettingsFactory pluginSettingsFactory;
36
37 public DefaultSiteDarkFeaturesStorage(final PluginSettingsFactory pluginSettingsFactory) {
38 this.pluginSettingsFactory = pluginSettingsFactory;
39 }
40
41 @Override
42 public boolean contains(String featureKey) {
43 final String trimmedFeatureKey = checkNotNull(StringUtils.trimToNull(featureKey), "featureKey must not be blank");
44 return cache.get().contains(trimmedFeatureKey);
45 }
46
47 @Override
48 public void enable(final String featureKey) {
49 final String trimmedFeatureKey = checkNotNull(StringUtils.trimToNull(featureKey), "featureKey must not be blank");
50 if (!cache.get().contains(trimmedFeatureKey)) {
51 update(addFeatureKey(trimmedFeatureKey));
52 cache.reset();
53 }
54 }
55
56 @Override
57 public void disable(final String featureKey) {
58 final String trimmedFeatureKey = checkNotNull(StringUtils.trimToNull(featureKey), "featureKey must not be blank");
59 if (cache.get().contains(trimmedFeatureKey)) {
60 update(removeFeatureKey(trimmedFeatureKey));
61 cache.reset();
62 }
63 }
64
65 @Override
66 public ImmutableSet<String> getEnabledDarkFeatures() {
67 return cache.get();
68 }
69
70
71
72
73
74
75
76
77
78
79
80 private synchronized void update(final Function<List<String>, List<String>> transformer) {
81
82
83
84
85 final List<String> storedFeatureKeys = load();
86 final List<String> updatedFeatureKeys = transformer.apply(storedFeatureKeys);
87 store(updatedFeatureKeys);
88 }
89
90 private synchronized List<String> load() {
91 final PluginSettings globalSettings = pluginSettingsFactory.createGlobalSettings();
92 final Object value = globalSettings.get(SITE_WIDE_DARK_FEATURES);
93 return extractFeatureKeys(value);
94 }
95
96 private List<String> extractFeatureKeys(@Nullable final Object value) {
97 final LinkedList<String> storedFeatureKeys = new LinkedList<>();
98 if (value instanceof List) {
99 final List list = List.class.cast(value);
100 for (final Object listItem : list) {
101 if (listItem instanceof String) {
102 storedFeatureKeys.addLast(String.class.cast(listItem));
103 }
104 }
105 }
106 return storedFeatureKeys;
107 }
108
109 private synchronized void store(final List<String> updatedFeatureKeys) {
110 final PluginSettings globalSettings = pluginSettingsFactory.createGlobalSettings();
111 globalSettings.put(SITE_WIDE_DARK_FEATURES, updatedFeatureKeys);
112 }
113
114 private Function<List<String>, List<String>> addFeatureKey(final String featureKey) {
115 return storedFeatureKeys -> {
116 if (storedFeatureKeys == null) {
117 return null;
118 }
119
120 final List<String> result = new ArrayList<>(storedFeatureKeys);
121 if (!storedFeatureKeys.contains(featureKey)) {
122 result.add(featureKey);
123 }
124 return result;
125 };
126 }
127
128 private Function<List<String>, List<String>> removeFeatureKey(final String featureKey) {
129 return storedFeatureKeys -> {
130 if (storedFeatureKeys == null) {
131 return storedFeatureKeys;
132 }
133
134 final List<String> result = new ArrayList<>(storedFeatureKeys);
135 result.remove(featureKey);
136 return result;
137 };
138 }
139 }