Skip to content

Commit 4a1ae0e

Browse files
authored
Refactor SwitcherContextBase to use instance instead of reflection (#328)
* Refactor SwitcherContextBase to use instance instead of reflection * Fix possible thread race when setting up context instance * Fixes code smell
1 parent 919b2e7 commit 4a1ae0e

21 files changed

+152
-60
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ You can also use environment variables using the standard notation ${VALUE:DEFAU
6161

6262
```
6363
#required
64-
switcher.context -> Feature class that extends SwitcherContext
64+
switcher.context -> Feature class that extends SwitcherContext/SwitcherContextBase
6565
switcher.url -> Switcher-API URL
6666
switcher.apikey -> Switcher-API key generated for the application/component
6767
switcher.component -> Application/component name

src/main/java/com/github/switcherapi/client/ContextBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ SwitcherProperties build() {
5353
}
5454

5555
/**
56-
* @param contextLocation Feature class that extends SwitcherContext
56+
* @param context Feature class that extends SwitcherContext
5757
* @return ContextBuilder
5858
*/
59-
public ContextBuilder contextLocation(String contextLocation) {
60-
switcherProperties.setValue(ContextKey.CONTEXT_LOCATION, contextLocation);
59+
public ContextBuilder context(String context) {
60+
switcherProperties.setValue(ContextKey.CONTEXT_LOCATION, context);
6161
return this;
6262
}
6363

src/main/java/com/github/switcherapi/client/SwitcherConfig.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
abstract class SwitcherConfig {
44

5-
protected String contextLocation;
65
protected String url;
76
protected String apikey;
87
protected String domain;
@@ -29,9 +28,14 @@ abstract class SwitcherConfig {
2928
*/
3029
protected abstract void configureClient();
3130

32-
public void setContextLocation(String contextLocation) {
33-
this.contextLocation = contextLocation;
34-
}
31+
/**
32+
* Initialize the Switcher Client using a context properties file.<br>
33+
* - Load context properties file {@link SwitcherContextBase#loadProperties(String)}<br>
34+
* - Initialize client {@link SwitcherContextBase#initializeClient()}<br>
35+
*
36+
* @param contextFile path to the context file
37+
*/
38+
protected abstract void configureClient(String contextFile);
3539

3640
public void setUrl(String url) {
3741
this.url = url;

src/main/java/com/github/switcherapi/client/SwitcherContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ public abstract class SwitcherContext extends SwitcherContextBase {
3333
* After loading the properties, it will validate the arguments and load the Switchers in memory.
3434
*/
3535
public static void loadProperties() {
36-
loadProperties("switcherapi");
36+
SwitcherContextBase.contextBase = null;
37+
SwitcherContextBase.loadProperties("switcherapi");
38+
SwitcherContextBase.initializeClient();
3739
}
3840

3941
/**
@@ -121,5 +123,5 @@ public static boolean contextBol(ContextKey contextKey) {
121123
public static void configure(ContextBuilder builder) {
122124
SwitcherContextBase.configure(builder);
123125
}
124-
126+
125127
}

src/main/java/com/github/switcherapi/client/SwitcherContextBase.java

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
* // Initialize the Switcher Client using ContextBuilder
6666
* public void configureClient() {
6767
* Features.configure(ContextBuilder.builder()
68-
* .contextLocation("com.business.config.Features")
68+
* .context("com.business.config.Features")
6969
* .apiKey("API_KEY")
7070
* .domain("Playground")
7171
* .component("switcher-playground")
@@ -91,15 +91,17 @@ public abstract class SwitcherContextBase extends SwitcherConfig {
9191
private static ScheduledExecutorService scheduledExecutorService;
9292
private static ExecutorService watcherExecutorService;
9393
private static SnapshotWatcher watcherSnapshot;
94+
protected static SwitcherContextBase contextBase;
9495

9596
static {
9697
switcherProperties = new SwitcherPropertiesImpl();
9798
}
9899

99100
@Override
100101
protected void configureClient() {
102+
setContextBase(this);
101103
configure(ContextBuilder.builder(true)
102-
.contextLocation(contextLocation)
104+
.context(contextBase.getClass().getName())
103105
.url(url)
104106
.apiKey(apikey)
105107
.domain(domain)
@@ -116,35 +118,47 @@ protected void configureClient() {
116118
.truststorePath(truststore.getPath())
117119
.truststorePassword(truststore.getPassword()));
118120

121+
switcherProperties.setValue(ContextKey.CONTEXT_LOCATION, contextBase.getClass().getName());
119122
initializeClient();
120123
}
121-
124+
125+
@Override
126+
protected void configureClient(String contextFile) {
127+
setContextBase(this);
128+
loadProperties(contextFile);
129+
130+
switcherProperties.setValue(ContextKey.CONTEXT_LOCATION, contextBase.getClass().getName());
131+
initializeClient();
132+
}
133+
134+
private static synchronized void setContextBase(SwitcherContextBase contextBase) {
135+
SwitcherContextBase.contextBase = contextBase;
136+
}
137+
122138
/**
123139
* Load properties from the resources' folder, look up for a given context file name (without extension).<br>
124-
* After loading the properties, it will validate the arguments and load the Switchers in memory.
125140
* <p>
126-
* Use this method optionally if you want to load the settings from a customized file name.
141+
* Use this method optionally if you want to load the settings from properties file.<br>
127142
* </p>
128143
*
129144
* Features must inherit {@link SwitcherContextBase}
130145
* <pre>
131-
* // Load from resources/switcherapi-test.properties
146+
* // Load from resources/switcherapi-test.properties
132147
* Features.loadProperties("switcherapi-test");
133148
* </pre>
134149
* @param contextFilename to load properties from
135150
*/
136151
public static void loadProperties(String contextFilename) {
137152
try (InputStream input = SwitcherContextBase.class
138153
.getClassLoader().getResourceAsStream(String.format("%s.properties", contextFilename))) {
139-
154+
140155
Properties prop = new Properties();
141-
prop.load(input);
142-
143-
switcherProperties.loadFromProperties(prop);
144-
initializeClient();
145-
} catch (IOException io) {
146-
throw new SwitcherContextException(io.getMessage());
147-
}
156+
prop.load(input);
157+
158+
switcherProperties.loadFromProperties(prop);
159+
} catch (IOException io) {
160+
throw new SwitcherContextException(io.getMessage());
161+
}
148162
}
149163

150164
/**
@@ -201,17 +215,29 @@ private static void validateContext() throws SwitcherContextException {
201215
* It will ensure that only properly annotated Switchers can be used.
202216
*/
203217
private static void validateSwitcherKeys() {
204-
try {
205-
switcherKeys = new HashSet<>();
206-
207-
final Class<?> clazz = Class.forName(contextStr(ContextKey.CONTEXT_LOCATION));
208-
for (Field field : clazz.getFields()) {
209-
if (field.isAnnotationPresent(SwitcherKey.class)) {
210-
switcherKeys.add(field.getName());
211-
}
218+
if (Objects.nonNull(contextBase)) {
219+
registerSwitcherKey(contextBase.getClass().getFields());
220+
} else {
221+
try {
222+
final Class<?> clazz = Class.forName(contextStr(ContextKey.CONTEXT_LOCATION));
223+
registerSwitcherKey(clazz.getFields());
224+
} catch(ClassNotFoundException e){
225+
throw new SwitcherContextException(e.getMessage());
226+
}
227+
}
228+
}
229+
230+
/**
231+
* Register Switcher Keys based on the annotation {@link SwitcherKey}
232+
*
233+
* @param fields to be registered
234+
*/
235+
private static void registerSwitcherKey(Field[] fields) {
236+
switcherKeys = new HashSet<>();
237+
for (Field field : fields) {
238+
if (field.isAnnotationPresent(SwitcherKey.class)) {
239+
switcherKeys.add(field.getName());
212240
}
213-
} catch (ClassNotFoundException e) {
214-
throw new SwitcherContextException(e.getMessage());
215241
}
216242
}
217243

src/main/java/com/github/switcherapi/client/remote/ClientWS.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public interface ClientWS {
5353
* @param token Access token
5454
* @return the execution based on the configured switcher
5555
*/
56-
CriteriaResponse executeCriteriaService(final Switcher switcher, final String token);
56+
CriteriaResponse executeCriteria(final Switcher switcher, final String token);
5757

5858
/**
5959
* Returns the token to access all available endpoints

src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static ClientWS build(SwitcherProperties switcherProperties, ExecutorServ
5252
}
5353

5454
@Override
55-
public CriteriaResponse executeCriteriaService(final Switcher switcher, final String token) {
55+
public CriteriaResponse executeCriteria(final Switcher switcher, final String token) {
5656
final String url = switcherProperties.getValue(ContextKey.URL);
5757

5858
try {
@@ -183,6 +183,7 @@ public SwitchersCheck checkSwitchers(Set<String> switchers, final String token)
183183
try {
184184
final HttpResponse<String> response = client.send(HttpRequest.newBuilder()
185185
.uri(URI.create(String.format(CHECK_SWITCHERS, url)))
186+
.timeout(Duration.ofMillis(timeoutMs))
186187
.headers(HEADER_AUTHORIZATION, String.format(TOKEN_TEXT, token),
187188
CONTENT_TYPE[0], CONTENT_TYPE[1])
188189
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(new SwitchersCheck(switchers)))

src/main/java/com/github/switcherapi/client/service/remote/ClientRemoteService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public CriteriaResponse executeCriteria(final Switcher switcher) {
4747
try {
4848
this.auth(tokenStatus);
4949

50-
return this.clientWs.executeCriteriaService(switcher,
50+
return this.clientWs.executeCriteria(switcher,
5151
Optional.of(this.authResponse).orElseGet(AuthResponse::new).getToken());
5252
} catch (final SwitcherRemoteException e) {
5353
if (tokenStatus != TokenStatus.SILENT) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.github.switcherapi.client;
2+
3+
import com.github.switcherapi.SwitchersBase;
4+
import com.github.switcherapi.fixture.MockWebServerHelper;
5+
import org.junit.jupiter.api.AfterAll;
6+
import org.junit.jupiter.api.BeforeAll;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.io.IOException;
10+
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
class SwitcherConfigNativeTest extends MockWebServerHelper {
14+
15+
@BeforeAll
16+
static void setup() throws IOException {
17+
MockWebServerHelper.setupMockServer();
18+
}
19+
20+
@AfterAll
21+
static void tearDown() throws IOException {
22+
MockWebServerHelper.tearDownMockServer();
23+
}
24+
25+
@Test
26+
void shouldUseNativeContext() {
27+
SwitchersBase context = buildSwitcherClientConfigMinimal(new SwitchersBase(), String.format("http://localhost:%s", mockBackEnd.getPort()));
28+
context.configureClient();
29+
30+
givenResponse(generateMockAuth(10));
31+
givenResponse(generateCriteriaResponse("true", false));
32+
33+
assertTrue(SwitchersBase.getSwitcher(SwitchersBase.USECASE11).isItOn());
34+
}
35+
36+
@Test
37+
void shouldUseNativeContextFromProperties() {
38+
SwitchersBase context = new SwitchersBase();
39+
context.configureClient("switcherapi-native");
40+
41+
assertTrue(SwitchersBase.getSwitcher(SwitchersBase.USECASE11).isItOn());
42+
}
43+
44+
private <T extends SwitcherConfig> T buildSwitcherClientConfigMinimal(T classConfig, String url) {
45+
classConfig.setUrl(url);
46+
classConfig.setApikey("[API-KEY]");
47+
classConfig.setDomain("domain");
48+
classConfig.setComponent("component");
49+
return classConfig;
50+
}
51+
52+
}

src/test/java/com/github/switcherapi/client/SwitcherConfigTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ private <T extends SwitcherConfig> T buildSwitcherClientConfig(T classConfig, St
4848
truststore.setPath(null);
4949
truststore.setPassword(null);
5050

51-
classConfig.setContextLocation(SwitchersBase.class.getName());
5251
classConfig.setUrl("http://localhost:3000");
5352
classConfig.setApikey("[API-KEY]");
5453
classConfig.setComponent(component);
@@ -67,7 +66,6 @@ private <T extends SwitcherConfig> T buildSwitcherClientConfigMinimal(T classCon
6766
SwitcherConfig.SnapshotConfig snapshot = new SwitcherConfig.SnapshotConfig();
6867
snapshot.setLocation(SNAPSHOTS_LOCAL);
6968

70-
classConfig.setContextLocation(SwitchersBase.class.getName());
7169
classConfig.setEnvironment("fixture1");
7270
classConfig.setLocal(true);
7371
classConfig.setSnapshot(snapshot);

0 commit comments

Comments
 (0)