Skip to content
13 changes: 12 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
version: 2

updates:

- package-ecosystem: "gradle"
directory: "/"
schedule:
Expand All @@ -14,4 +16,13 @@ updates:
- "*"
update-types:
- "minor"
- "patch"
- "patch"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
reviewers:
- "Jorich"
- "pr11t"
- "rammrain"
11 changes: 9 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '17', '21' ]
name: Test with Java ${{ matrix.Java }}
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -19,15 +23,18 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
java-version: ${{ matrix.java }}

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.6

- name: Run tests and generate reports
run: ./gradlew testAndReport

- name: Run Sonar analysis
if: matrix.Java == '17'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand All @@ -36,7 +43,7 @@ jobs:
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: report
name: report-java-${{ matrix.Java }}
path: build/reports/**
retention-days: 5

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.5
gradle-version: 8.6
- name: Publish packages
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ plugins {
}

group 'ee.bitweb'
version '3.2.0'
version '3.3.0'
java {
sourceCompatibility = '17'
}
Expand Down
35 changes: 8 additions & 27 deletions src/main/java/ee/bitweb/core/trace/thread/MDCTaskDecorator.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
package ee.bitweb.core.trace.thread;

import lombok.RequiredArgsConstructor;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContextHolder;
import ee.bitweb.core.trace.thread.decorator.SecurityAwareMDCTaskDecorator;

import java.util.Map;
/**
* @deprecated use BasicMDCTaskDecorator or SecurityAwareMDCTaskDecorator
*/
@Deprecated(since = "3.3.0", forRemoval = true)
public class MDCTaskDecorator extends SecurityAwareMDCTaskDecorator {

@RequiredArgsConstructor
public class MDCTaskDecorator implements TaskDecorator {

private final ThreadTraceIdResolver resolver;

@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
var securityContext = SecurityContextHolder.getContext();

return () -> {
try {
MDC.setContextMap(contextMap);
SecurityContextHolder.setContext(securityContext);
resolver.resolve();

runnable.run();
} finally {
MDC.clear();
SecurityContextHolder.clearContext();
}
};
public MDCTaskDecorator(ThreadTraceIdResolver resolver) {
super(resolver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ee.bitweb.core.trace.thread.decorator;

import ee.bitweb.core.trace.thread.ThreadTraceIdResolver;
import lombok.RequiredArgsConstructor;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

import java.util.HashMap;
import java.util.Map;

@RequiredArgsConstructor
public class BasicMDCTaskDecorator implements TaskDecorator {

private final ThreadTraceIdResolver resolver;

@Override
public Runnable decorate(Runnable runnable) {
final Map<String, String> contextMap = MDC.getCopyOfContextMap();

return () -> {
try {
MDC.setContextMap(contextMap == null ? new HashMap<>() : contextMap);
resolver.resolve();

runnable.run();
} finally {
MDC.clear();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ee.bitweb.core.trace.thread.decorator;

import ee.bitweb.core.trace.thread.ThreadTraceIdResolver;
import lombok.RequiredArgsConstructor;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.HashMap;
import java.util.Map;

@RequiredArgsConstructor
public class SecurityAwareMDCTaskDecorator implements TaskDecorator {

private final ThreadTraceIdResolver resolver;

@Override
public Runnable decorate(Runnable runnable) {
final Map<String, String> contextMap = MDC.getCopyOfContextMap();
final SecurityContext securityContext = SecurityContextHolder.getContext();

return () -> {
try {
MDC.setContextMap(contextMap == null ? new HashMap<>() : contextMap);
SecurityContextHolder.setContext(securityContext == null ? SecurityContextHolder.createEmptyContext() : securityContext);
resolver.resolve();

runnable.run();
} finally {
MDC.clear();
SecurityContextHolder.clearContext();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ee.bitweb.core.trace.thread.decorator;

import ee.bitweb.core.trace.thread.ThreadTraceIdResolver;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockSettings;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.MDC;

import static org.junit.jupiter.api.Assertions.*;

@Tag("unit")
@ExtendWith(MockitoExtension.class)
class BasicMDCTaskDecoratorTest {

@Mock
private ThreadTraceIdResolver resolver;

@Test
void testMDCIsPopulatedAndCleared() {
Mockito.when(resolver.resolve()).thenReturn(null);
MDC.put("custom-key", "custom-value");

new BasicMDCTaskDecorator(resolver).decorate(() -> {
assertEquals("custom-value", MDC.get("custom-key"));
}).run();

assertNull(MDC.get("custom-key"));

Mockito.verifyNoMoreInteractions(resolver);
}

@Test
void testMDCIsPopulatedAndClearedWhenExceptionIsThrown() {
Mockito.when(resolver.resolve()).thenReturn(null);
MDC.put("custom-key", "custom-value");

try {
new BasicMDCTaskDecorator(resolver).decorate(() -> {
assertEquals("custom-value", MDC.get("custom-key"));

throw new RuntimeException();
}).run();
} catch (RuntimeException ignored) {
assertNull(MDC.get("custom-key"));
}

Mockito.verifyNoMoreInteractions(resolver);
}

@Test
void testMDCIsPopulatedGivenMDCContextMapIsNull() {
Mockito.when(resolver.resolve()).thenReturn(null);
assertNull(MDC.getCopyOfContextMap());

new BasicMDCTaskDecorator(resolver).decorate(() -> {
assertNotNull(MDC.getCopyOfContextMap());
}).run();

Mockito.verifyNoMoreInteractions(resolver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ee.bitweb.core.trace.thread.decorator;

import ee.bitweb.core.trace.thread.ThreadTraceIdResolver;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.MDC;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import static org.junit.jupiter.api.Assertions.*;

@Tag("unit")
@ExtendWith(MockitoExtension.class)
class SecurityAwareMDCTaskDecoratorTest {

@Mock
private ThreadTraceIdResolver resolver;

@Test
void testMDCIsPopulatedAndCleared() {
Mockito.when(resolver.resolve()).thenReturn(null);
MDC.put("custom-key", "custom-value");

new SecurityAwareMDCTaskDecorator(resolver).decorate(() -> {
assertEquals("custom-value", MDC.get("custom-key"));
}).run();

assertNull(MDC.get("custom-key"));

Mockito.verifyNoMoreInteractions(resolver);
}

@Test
void testMDCIsPopulatedAndClearedWhenExceptionIsThrown() {
Mockito.when(resolver.resolve()).thenReturn(null);
MDC.put("custom-key", "custom-value");

try {
new SecurityAwareMDCTaskDecorator(resolver).decorate(() -> {
assertEquals("custom-value", MDC.get("custom-key"));

throw new RuntimeException();
}).run();
} catch (RuntimeException ignored) {
assertNull(MDC.get("custom-key"));
}

Mockito.verifyNoMoreInteractions(resolver);
}

@Test
void testMDCIsPopulatedGivenMDCContextMapIsNull() {
Mockito.when(resolver.resolve()).thenReturn(null);
assertNull(MDC.getCopyOfContextMap());

new SecurityAwareMDCTaskDecorator(resolver).decorate(() -> {
assertNotNull(MDC.getCopyOfContextMap());
}).run();

Mockito.verifyNoMoreInteractions(resolver);
}

@Test
void testDecorateCanHandleNullSecurityContext() {
Mockito.when(resolver.resolve()).thenReturn(null);

Runnable task;

try (MockedStatic<SecurityContextHolder> holder = Mockito.mockStatic(SecurityContextHolder.class)) {
holder.when(SecurityContextHolder::getContext).thenReturn(null);

task = new SecurityAwareMDCTaskDecorator(resolver).decorate(() -> {
assertNotNull(SecurityContextHolder.getContext());
});
}

task.run();

Mockito.verifyNoMoreInteractions(resolver);
}
}