Skip to content

Commit be54320

Browse files
committed
feat(sdk): add linter and fix linter findings
relates to STACKITSDK-219
1 parent 6bbe05b commit be54320

File tree

18 files changed

+490
-260
lines changed

18 files changed

+490
-260
lines changed

build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
id 'signing'
44
id 'idea'
55
id 'eclipse'
6+
id 'pmd'
67

78
id 'com.diffplug.spotless' version '6.21.0'
89

@@ -13,6 +14,7 @@ plugins {
1314

1415
allprojects {
1516
apply plugin: 'com.diffplug.spotless'
17+
apply plugin: 'pmd'
1618

1719
repositories {
1820
mavenCentral()
@@ -64,6 +66,32 @@ allprojects {
6466
endWithNewline()
6567
}
6668
}
69+
70+
pmd {
71+
consoleOutput = true
72+
toolVersion = "7.12.0"
73+
74+
//rulesMinimumPriority = 5
75+
//ruleSets = [
76+
// "category/java/bestpractices.xml",
77+
// "category/java/codestyle.xml",
78+
// "category/java/design.xml",
79+
// "category/java/documentation.xml",
80+
// "category/java/errorprone.xml",
81+
// "category/java/multithreading.xml",
82+
// "category/java/performance.xml",
83+
// "category/java/security.xml",
84+
85+
// //"category/java/errorprone.xml",
86+
// // "category/java/bestpractices.xml"
87+
//]
88+
89+
// This tells PMD to use your custom ruleset file.
90+
ruleSetFiles = rootProject.files("config/pmd/pmd-ruleset.xml")
91+
92+
// This is important: it prevents PMD from using its default rules.
93+
ruleSets = []
94+
}
6795
}
6896

6997
def configureMavenCentralPublishing(Project project) {

config/pmd/pmd-ruleset.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0"?>
2+
<ruleset name="Custom Ruleset"
3+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
6+
7+
<description>
8+
Custom ruleset that excludes generated code from PMD analysis.
9+
</description>
10+
11+
<exclude-pattern>.*/cloud/stackit/sdk/.*/model/.*</exclude-pattern>
12+
<exclude-pattern>.*/cloud/stackit/sdk/.*/api/.*</exclude-pattern>
13+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ApiCallback.java</exclude-pattern>
14+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ApiClient.java</exclude-pattern>
15+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ApiResponse.java</exclude-pattern>
16+
<exclude-pattern>.*/cloud/stackit/sdk/.*/GzipRequestInterceptor.java</exclude-pattern>
17+
<exclude-pattern>.*/cloud/stackit/sdk/.*/JSON.java</exclude-pattern>
18+
<exclude-pattern>.*/cloud/stackit/sdk/.*/Pair.java</exclude-pattern>
19+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ProgressRequestBody.java</exclude-pattern>
20+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ProgressResponseBody.java</exclude-pattern>
21+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ServerConfiguration.java</exclude-pattern>
22+
<exclude-pattern>.*/cloud/stackit/sdk/.*/ServerVariable.java</exclude-pattern>
23+
<exclude-pattern>.*/cloud/stackit/sdk/.*/StringUtil.java</exclude-pattern>
24+
25+
<rule ref="category/java/bestpractices.xml">
26+
<exclude name="UnitTestContainsTooManyAsserts"/>
27+
<exclude name="UnitTestAssertionsShouldIncludeMessage"/>
28+
</rule>
29+
30+
<rule ref="category/java/codestyle.xml">
31+
<exclude name="LocalVariableCouldBeFinal"/>
32+
<exclude name="MethodArgumentCouldBeFinal"/>
33+
<exclude name="AtLeastOneConstructor"/>
34+
<exclude name="LongVariable"/>
35+
<exclude name="OnlyOneReturn"/>
36+
</rule>
37+
38+
<rule ref="category/java/design.xml">
39+
</rule>
40+
41+
<rule ref="category/java/documentation.xml">
42+
<exclude name="CommentRequired"/>
43+
</rule>
44+
45+
<rule ref="category/java/errorprone.xml">
46+
<exclude name="AvoidFieldNameMatchingMethodName"/>
47+
</rule>
48+
49+
<rule ref="category/java/multithreading.xml">
50+
</rule>
51+
52+
<rule ref="category/java/performance.xml">
53+
</rule>
54+
55+
<rule ref="category/java/security.xml">
56+
</rule>
57+
58+
</ruleset>

core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919
import java.security.interfaces.RSAPrivateKey;
2020
import java.security.spec.InvalidKeySpecException;
2121
import java.util.Date;
22-
import java.util.HashMap;
2322
import java.util.Map;
2423
import java.util.UUID;
24+
import java.util.concurrent.ConcurrentHashMap;
2525
import java.util.concurrent.TimeUnit;
26+
import java.util.concurrent.locks.Lock;
27+
import java.util.concurrent.locks.ReentrantLock;
28+
2629
import okhttp3.*;
2730
import org.jetbrains.annotations.NotNull;
2831

29-
/* KeyFlowAuthenticator handles the Key Flow Authentication based on the Service Account Key. */
32+
/*
33+
* KeyFlowAuthenticator handles the Key Flow Authentication based on the Service Account Key.
34+
*/
3035
public class KeyFlowAuthenticator implements Authenticator {
3136
private static final String REFRESH_TOKEN = "refresh_token";
3237
private static final String ASSERTION = "assertion";
@@ -44,6 +49,8 @@ public class KeyFlowAuthenticator implements Authenticator {
4449
private final String tokenUrl;
4550
private long tokenLeewayInSeconds = DEFAULT_TOKEN_LEEWAY;
4651

52+
Lock tokenRefreshLock = new ReentrantLock();
53+
4754
/**
4855
* Creates the initial service account and refreshes expired access token.
4956
*
@@ -140,19 +147,19 @@ public Request authenticate(Route route, @NotNull Response response) throws IOEx
140147

141148
protected static class KeyFlowTokenResponse {
142149
@SerializedName("access_token")
143-
private String accessToken;
150+
private final String accessToken;
144151

145152
@SerializedName("refresh_token")
146-
private String refreshToken;
153+
private final String refreshToken;
147154

148155
@SerializedName("expires_in")
149156
private long expiresIn;
150157

151158
@SerializedName("scope")
152-
private String scope;
159+
private final String scope;
153160

154161
@SerializedName("token_type")
155-
private String tokenType;
162+
private final String tokenType;
156163

157164
public KeyFlowTokenResponse(
158165
String accessToken,
@@ -184,13 +191,21 @@ protected String getAccessToken() {
184191
* @throws IOException request for new access token failed
185192
* @throws ApiException response for new access token with bad status code
186193
*/
187-
public synchronized String getAccessToken()
194+
public String getAccessToken()
188195
throws IOException, ApiException, InvalidKeySpecException {
189-
if (token == null) {
190-
createAccessToken();
191-
} else if (token.isExpired()) {
192-
createAccessTokenWithRefreshToken();
196+
try {
197+
tokenRefreshLock.lock();
198+
199+
if (token == null) {
200+
createAccessToken();
201+
} else if (token.isExpired()) {
202+
createAccessTokenWithRefreshToken();
203+
}
204+
}
205+
finally {
206+
tokenRefreshLock.unlock();
193207
}
208+
194209
return token.getAccessToken();
195210
}
196211

@@ -204,7 +219,6 @@ public synchronized String getAccessToken()
204219
*/
205220
protected void createAccessToken()
206221
throws InvalidKeySpecException, IOException, JsonSyntaxException, ApiException {
207-
String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer";
208222
String assertion;
209223
try {
210224
assertion = generateSelfSignedJWT();
@@ -213,9 +227,11 @@ protected void createAccessToken()
213227
"could not find required algorithm for jwt signing. This should not happen and should be reported on https://github.com/stackitcloud/stackit-sdk-java/issues",
214228
e);
215229
}
216-
Response response = requestToken(grant, assertion).execute();
217-
parseTokenResponse(response);
218-
response.close();
230+
231+
String grant = "urn:ietf:params:oauth:grant-type:jwt-bearer";
232+
try (Response response = requestToken(grant, assertion).execute()) {
233+
parseTokenResponse(response);
234+
}
219235
}
220236

221237
/**
@@ -228,13 +244,13 @@ protected void createAccessToken()
228244
protected synchronized void createAccessTokenWithRefreshToken()
229245
throws IOException, JsonSyntaxException, ApiException {
230246
String refreshToken = token.refreshToken;
231-
Response response = requestToken(REFRESH_TOKEN, refreshToken).execute();
232-
parseTokenResponse(response);
233-
response.close();
247+
try (Response response = requestToken(REFRESH_TOKEN, refreshToken).execute()) {
248+
parseTokenResponse(response);
249+
}
234250
}
235251

236252
private synchronized void parseTokenResponse(Response response)
237-
throws ApiException, JsonSyntaxException, IOException {
253+
throws ApiException, JsonSyntaxException {
238254
if (response.code() != HttpURLConnection.HTTP_OK) {
239255
String body = null;
240256
if (response.body() != null) {
@@ -256,10 +272,10 @@ private synchronized void parseTokenResponse(Response response)
256272
response.body().close();
257273
}
258274

259-
private Call requestToken(String grant, String assertionValue) throws IOException {
275+
private Call requestToken(String grant, String assertionValue) {
260276
FormBody.Builder bodyBuilder = new FormBody.Builder();
261277
bodyBuilder.addEncoded("grant_type", grant);
262-
String assertionKey = grant.equals(REFRESH_TOKEN) ? REFRESH_TOKEN : ASSERTION;
278+
String assertionKey = REFRESH_TOKEN.equals(grant) ? REFRESH_TOKEN : ASSERTION;
263279
bodyBuilder.addEncoded(assertionKey, assertionValue);
264280
FormBody body = bodyBuilder.build();
265281

@@ -289,7 +305,7 @@ private String generateSelfSignedJWT()
289305
prvKey = saKey.getCredentials().getPrivateKeyParsed();
290306
Algorithm algorithm = Algorithm.RSA512(prvKey);
291307

292-
Map<String, Object> jwtHeader = new HashMap<>();
308+
Map<String, Object> jwtHeader = new ConcurrentHashMap<>();
293309
jwtHeader.put("kid", saKey.getCredentials().getKid());
294310

295311
return JWT.create()

core/src/main/java/cloud/stackit/sdk/core/auth/SetupAuth.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.nio.file.Paths;
1919
import java.util.Map;
2020
import javax.swing.filechooser.FileSystemView;
21+
2122
import okhttp3.Interceptor;
2223

2324
public class SetupAuth {
@@ -40,9 +41,11 @@ public class SetupAuth {
4041
* let it handle the rest. Will be removed in April 2026.
4142
*/
4243
@Deprecated
44+
public SetupAuth() {
45+
// deprecated
46+
}
4347
// TODO: constructor of SetupAuth should be private after deprecated constructors/methods are
4448
// removed (only static methods should remain)
45-
public SetupAuth() {}
4649

4750
/**
4851
* Set up the KeyFlow Authentication and can be integrated in an OkHttp client, by adding
@@ -186,7 +189,7 @@ protected static void loadPrivateKey(
186189
try {
187190
String privateKey = getPrivateKey(cfg, env);
188191
saKey.getCredentials().setPrivateKey(privateKey);
189-
} catch (Exception e) {
192+
} catch (CredentialsInFileNotFoundException | IOException e) {
190193
throw new PrivateKeyNotFoundException("could not find private key", e);
191194
}
192195
}
@@ -216,6 +219,7 @@ protected static void loadPrivateKey(
216219
* </ol>
217220
*
218221
* @param cfg
222+
* @param env
219223
* @return found private key
220224
* @throws CredentialsInFileNotFoundException throws if no private key could be found
221225
* @throws IOException throws if the provided path can not be found or the file within the

core/src/main/java/cloud/stackit/sdk/core/config/CoreConfiguration.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ public class CoreConfiguration {
1313
private String tokenCustomUrl;
1414
private Long tokenExpirationLeeway;
1515

16-
public CoreConfiguration() {}
17-
1816
public Map<String, String> getDefaultHeader() {
1917
return defaultHeader;
2018
}

core/src/main/java/cloud/stackit/sdk/core/exception/ApiException.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55

66
/** ApiException class. */
77
public class ApiException extends Exception {
8-
private static final long serialVersionUID = 1L;
8+
private static final long serialVersionUID = 8115526329759018011L;
99

10-
private int code = 0;
11-
private Map<String, List<String>> responseHeaders = null;
12-
private String responseBody = null;
10+
private int code;
11+
private Map<String, List<String>> responseHeaders;
12+
private String responseBody;
1313

1414
/** Constructor for ApiException. */
15-
public ApiException() {}
15+
public ApiException() {
16+
super();
17+
}
1618

1719
/**
1820
* Constructor for ApiException.
@@ -162,6 +164,7 @@ public String getResponseBody() {
162164
*
163165
* @return The exception message
164166
*/
167+
@Override
165168
public String getMessage() {
166169
return String.format(
167170
"Message: %s%nHTTP response code: %s%nHTTP response body: %s%nHTTP response headers: %s",

core/src/main/java/cloud/stackit/sdk/core/exception/CredentialsInFileNotFoundException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cloud.stackit.sdk.core.exception;
22

33
public class CredentialsInFileNotFoundException extends RuntimeException {
4+
private static final long serialVersionUID = -3290974267932615412L;
45

56
public CredentialsInFileNotFoundException(String msg) {
67
super(msg);

core/src/main/java/cloud/stackit/sdk/core/exception/PrivateKeyNotFoundException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cloud.stackit.sdk.core.exception;
22

33
public class PrivateKeyNotFoundException extends RuntimeException {
4+
private static final long serialVersionUID = -81419539524374575L;
45

56
public PrivateKeyNotFoundException(String msg) {
67
super(msg);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cloud.stackit.sdk.core.utils;
22

3+
@SuppressWarnings("PMD.TestClassWithoutTestCases")
34
public class TestUtils {
45
public static final String MOCK_SERVICE_ACCOUNT_KEY =
56
"{\"id\":\"id\",\"publicKey\":\"publicKey\",\"createdAt\":\"2025-03-26T15:08:45.915+00:00\",\"keyType\":\"keyType\",\"keyOrigin\":\"keyOrigin\",\"keyAlgorithm\":\"keyAlgo\",\"active\":true,\"validUntil\":\"2025-03-26T15:08:45.915+00:00\",\"credentials\":{\"aud\":\"aud\",\"iss\":\"iss\",\"kid\":\"kid\",\"privateKey\":\"privateKey\",\"sub\":\"sub\"}}\n";
7+
8+
public static final String MOCK_SERVICE_ACCOUNT_PRIVATE_KEY = "privateKey";
69
}
Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
package cloud.stackit.sdk.core.utils;
22

33
public final class Utils {
4-
public static boolean isStringSet(String input) {
5-
return input != null && !input.trim().isEmpty();
4+
private Utils() {}
5+
6+
/*
7+
* Assert a string is not null and not empty
8+
*
9+
* @param input The string to check
10+
* @return check result
11+
* */
12+
public static boolean isStringSet(final String input) {
13+
return input != null && !checkTrimEmpty(input);
14+
}
15+
16+
/*
17+
* Assert a string is not empty. Helper method because String.trim().length() == 0
18+
* / String.trim().isEmpty() is an inefficient way to validate a blank String.
19+
*
20+
* @param input The string to check
21+
* @return check result
22+
* */
23+
private static boolean checkTrimEmpty(String input) {
24+
for (int i = 0; i < input.length(); i++) {
25+
if (!Character.isWhitespace(input.charAt(i))) {
26+
return false;
27+
}
28+
}
29+
return true;
630
}
731
}

0 commit comments

Comments
 (0)