Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,21 @@ jobs:
- 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 }}
run: ./gradlew sonar -x test --no-watch-fs

- name: Upload Artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: report-java-${{ matrix.Java }}
path: build/reports/**
retention-days: 5

- name: Run Sonar analysis
if: matrix.Java == '17'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew sonar -x test --no-watch-fs

build:
runs-on: ubuntu-latest
needs: [test]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Library providing basic generic functionality required by any HTTP web service.

```
// https://mvnrepository.com/artifact/ee.bitweb/spring-core
implementation group: 'ee.bitweb', name: 'spring-core', version: '3.2.0'
implementation group: 'ee.bitweb', name: 'spring-core', version: '4.0.0'
```

Review available versions in [Maven Central](https://mvnrepository.com/artifact/ee.bitweb/spring-core).
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.4.0'
version '4.0.0'
java {
sourceCompatibility = '17'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public void write(Map<String, String> container) {

if (currentContext != null) {
MDC.setContextMap(currentContext);
} else {
MDC.clear();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ee.bitweb.core.retrofit;

import ee.bitweb.core.exception.CoreException;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(value = ConditionalOnEnabledRetrofitMapper.MapperEnabled.class)
public @interface ConditionalOnEnabledRetrofitMapper {

String mapper();

class MapperEnabled implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnEnabledRetrofitMapper.class.getName()
);
if (attributes == null) return false;

Object mapperKeyOb = attributes.get("mapper");

if (mapperKeyOb == null) return false;

String mapperKey = (String) mapperKeyOb;

RetrofitProperties config = Binder.get(
context.getEnvironment()
).bind(
RetrofitProperties.PREFIX, RetrofitProperties.class
).orElseThrow(
() -> new CoreException("Error occurred while trying to bind environment to RetrofitProperties")
);

return config.getLogging().getMappers().contains(mapperKey);
}
}
}
111 changes: 111 additions & 0 deletions src/main/java/ee/bitweb/core/retrofit/RetrofitAutoConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
package ee.bitweb.core.retrofit;

import com.fasterxml.jackson.databind.ObjectMapper;
import ee.bitweb.core.retrofit.builder.LoggingLevel;
import ee.bitweb.core.retrofit.interceptor.auth.AuthTokenInjectInterceptor;
import ee.bitweb.core.retrofit.interceptor.auth.TokenProvider;
import ee.bitweb.core.retrofit.interceptor.auth.criteria.AuthTokenCriteria;
import ee.bitweb.core.retrofit.interceptor.auth.criteria.WhitelistCriteria;
import ee.bitweb.core.retrofit.logging.NoopRetrofitLoggingInterceptor;
import ee.bitweb.core.retrofit.logging.RetrofitLoggingInterceptor;
import ee.bitweb.core.retrofit.logging.RetrofitLoggingInterceptorImplementation;
import ee.bitweb.core.retrofit.logging.mappers.*;
import ee.bitweb.core.retrofit.logging.writers.RetrofitLogLoggerWriterAdapter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import retrofit2.Converter;
import retrofit2.converter.jackson.JacksonConverterFactory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;

@Slf4j
@Configuration
@RequiredArgsConstructor
@ConditionalOnProperty(value = RetrofitProperties.PREFIX + ".auto-configuration", havingValue = "true")
@EnableConfigurationProperties({RetrofitProperties.class})
public class RetrofitAutoConfiguration {

@Bean
Expand Down Expand Up @@ -64,4 +74,105 @@ public AuthTokenCriteria defaultCriteria(RetrofitProperties properties) {

return criteria;
}

@Bean("defaultRetrofitLoggingInterceptor")
@Primary
public RetrofitLoggingInterceptor defaultRetrofitLoggingInterceptor(
List<RetrofitLoggingMapper> mappers,
RetrofitProperties properties
) {
if (properties.getLogging().getLevel() == LoggingLevel.NONE) {
log.info("Create Default Retrofit Logging Interceptor for logging level NONE");

return new NoopRetrofitLoggingInterceptor();
}

log.info(
"Create Default Retrofit Logging Interceptor with writer {}",
RetrofitLogLoggerWriterAdapter.class.getSimpleName()
);

for (RetrofitLoggingMapper mapper : mappers) {
log.info("Applying Retrofit Log Data Mapper: {}", mapper.getClass());
}

return new RetrofitLoggingInterceptorImplementation(mappers, new RetrofitLogLoggerWriterAdapter());
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitRequestMethodMapper.KEY)
public RetrofitRequestMethodMapper retrofitRequestMethodMapper() {
return new RetrofitRequestMethodMapper();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitRequestUrlMapper.KEY)
public RetrofitRequestUrlMapper retrofitRequestUrlMapper() {
return new RetrofitRequestUrlMapper();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitRequestHeadersMapper.KEY)
public RetrofitRequestHeadersMapper retrofitRequestHeadersMapper(
RetrofitProperties retrofitProperties
) {
return new RetrofitRequestHeadersMapper(new HashSet<>(retrofitProperties.getLogging().getSuppressedHeaders()));
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitRequestBodySizeMapper.KEY)
public RetrofitRequestBodySizeMapper retrofitRequestBodySizeMapper() {
return new RetrofitRequestBodySizeMapper();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitRequestBodyMapper.KEY)
public RetrofitRequestBodyMapper retrofitRequestBodyMapper(
RetrofitProperties retrofitProperties
) {
return new RetrofitRequestBodyMapper(
retrofitProperties.getLogging().getMaxLoggableRequestBodySize().intValue(),
new HashSet<>(retrofitProperties.getLogging().getRedactedBodyUrls())
);
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitResponseStatusCodeMapper.KEY)
public RetrofitResponseStatusCodeMapper retrofitResponseStatusCodeMapper() {
return new RetrofitResponseStatusCodeMapper();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitResponseHeadersMapper.KEY)
public RetrofitResponseHeadersMapper retrofitResponseHeadersMapper(
RetrofitProperties retrofitProperties
) {
return new RetrofitResponseHeadersMapper(new HashSet<>(retrofitProperties.getLogging().getSuppressedHeaders()));
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitResponseBodySizeMapper.KEY)
public RetrofitResponseBodySizeMapper retrofitResponseBodySizeMapper() {
return new RetrofitResponseBodySizeMapper();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledRetrofitMapper(mapper = RetrofitResponseBodyMapper.KEY)
public RetrofitResponseBodyMapper responseBodyMapper(
RetrofitProperties retrofitProperties
) {
return new RetrofitResponseBodyMapper(
new HashSet<>(retrofitProperties.getLogging().getRedactedBodyUrls()),
retrofitProperties.getLogging().getMaxLoggableResponseBodySize().intValue()
);
}
}
34 changes: 26 additions & 8 deletions src/main/java/ee/bitweb/core/retrofit/RetrofitProperties.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package ee.bitweb.core.retrofit;

import ee.bitweb.core.retrofit.builder.LoggingLevel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.*;

import static ee.bitweb.core.retrofit.RetrofitProperties.PREFIX;

@Component
@Setter
@Getter
@Validated
Expand Down Expand Up @@ -47,7 +44,28 @@ public static class Logging {

@NotNull
private LoggingLevel level = LoggingLevel.BASIC;

@NotNull
private Long maxLoggableRequestBodySize = 1024 * 10L;

@NotNull
private Long maxLoggableResponseBodySize = 1024 * 10L;

private List<@NotBlank String> suppressedHeaders = new ArrayList<>();
private List<@NotBlank String> redactedBodyUrls = new ArrayList<>();

private List<@NotBlank String> mappers = new ArrayList<>();

public List<@NotBlank String> getMappers() {
if (level == LoggingLevel.CUSTOM) {
return mappers;
} else {
Set<String> enabledMappers = new HashSet<>(level.getMappers());
enabledMappers.addAll(mappers);

return enabledMappers.stream().toList();
}
}
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class RetrofitRequestExecutor {
public static <T> T execute(Call<Response<T>> request) {
retrofit2.Response<Response<T>> response = doRequest(request);

if (response.body().getData() == null) {
if (response.body() == null || response.body().getData() == null) {
throw RetrofitException.of(EMPTY_RESPONSE_BODY_ERROR, request, response);
}

Expand Down
48 changes: 41 additions & 7 deletions src/main/java/ee/bitweb/core/retrofit/builder/LoggingLevel.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
package ee.bitweb.core.retrofit.builder;

import ee.bitweb.core.retrofit.logging.mappers.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import okhttp3.logging.HttpLoggingInterceptor;

import java.util.ArrayList;
import java.util.List;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum LoggingLevel {

NONE(HttpLoggingInterceptor.Level.NONE),
BASIC(HttpLoggingInterceptor.Level.BASIC),
HEADERS(HttpLoggingInterceptor.Level.HEADERS),
BODY(HttpLoggingInterceptor.Level.BODY);
NONE(List.of()),
BASIC(basicMappers()),
HEADERS(headerMappers()),
BODY(bodyMappers()),
CUSTOM(List.of());

private final List<String> mappers;

private static List<String> basicMappers() {
return new ArrayList<>(List.of(
RetrofitRequestMethodMapper.KEY,
RetrofitRequestUrlMapper.KEY,
RetrofitRequestBodySizeMapper.KEY,
RetrofitResponseStatusCodeMapper.KEY,
RetrofitResponseBodySizeMapper.KEY
));
}

private static List<String> headerMappers() {
List<String> mappers = basicMappers();
mappers.addAll(List.of(
RetrofitRequestHeadersMapper.KEY,
RetrofitResponseHeadersMapper.KEY
));

return mappers;
}

private static List<String> bodyMappers() {
List<String> mappers = headerMappers();
mappers.addAll(List.of(
RetrofitRequestBodyMapper.KEY,
RetrofitResponseBodyMapper.KEY
));

@Getter(AccessLevel.PACKAGE)
private HttpLoggingInterceptor.Level level;
return mappers;
}
}
Loading