28 Nisan 2023 Cuma

SpringWebFlux WebClient Test

Giriş
Açıklaması şöyle
Basically, there are two general directions for writing tests for the reactive chain:

1. Directly call the methods to test and receive the outcome by invoking block() on the resulting Mono or Flux.
2. Wrap these calls with the StepVerifier class provided by the Reactor framework.
StepVerifier
Bir Flux veya Mono nesnesini test etmek içindir. Testing sonunda verifyComplete() veya bir türevinin çağrısını yapmak gerekir

assertNext metodu
Örnek
Şöyle yaparız
@Test
public void verifyGetByNameReturnsEntryForSuccessfulRequestUsingStepVerifier() {
    InventoryEntry expectedEntry = new InventoryEntry(42, "...", 5);

    mockBackEnd.enqueue(assembleResponse(expectedEntry));

    StepVerifier
            .create(cut.getByName(expectedEntry.name()))
            .assertNext(entry -> assertThat(entry).isEqualTo(expectedEntry))
            .verifyComplete();
}
expectNext metodu
Örnek
Şöyle yaparız
@Test
public void testFluxStream() {
  Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);

  StepVerifier.create(flux)
    .expectNext(1)
    .expectNext(2)
    .expectNext(3)
    .expectNext(4)
    .expectNext(5)
    .verifyComplete();
}

26 Nisan 2023 Çarşamba

SpringMVC ProblemDetail Sınıfı - RFC 7807 İçindir

Giriş
Şu satırı dahil ederiz.
import org.springframework.http.ProblemDetail;
RFC 7807 Nedir?
Açıklaması şöyle
ProblemDetail is a standard format that is defined in RFC 7807 for describing errors and exceptions in HTTP APIs. The format includes a set of predefined fields, such as the error type, error code, error message, and additional details about the error.
Açıklaması şöyle
So you know how you would handle custom exceptions with @ControllerAdvice where you would have your custom messages wrapped inside a ResponseEntity. It is a tedious process and you still need to figure out what your message should look like. Imagine if the message was standardized but you could still extend it. Welcome to the Problem Detail RFC. SpringBoot 3.0 provides a ProblemDetail class to do the trick.
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Eski Yöntem
Eski kodlarda şöyle yapılıyor
Örnek
Elimizde şöyle bir kod olsun.
 /**
  * Data Transport Object to represent errors
  */
 public class ErrorModel {

    private final List<String> messages;

    @JsonCreator
    public ErrorModel(@JsonProperty("messages") List<String> messages) {
        this.messages = messages;
    }

    public ErrorModel(String message) {
        this.messages = Collections.singletonList(message);
    }

    public List<String> getMessages() {
        return messages;
    }
 }
Rest servisinden fırlatılan exception'ı yakalayıp daha anlaşılır bir nesne taşıyan ResponseEntity dönmek için şöyle yaparız.
@ControllerAdvice
public class ExceptionHandlers {

  @ExceptionHandler
  public ResponseEntity<ErrorModel> handle(ValidationException ex) {
    return ResponseEntity.badRequest()
      .body(new ErrorModel(ex.getMessages()));
  }

  //...
}
Yeni Yöntem
Artık ProblemDetail kullanılıyor. RFC ile tanımlı alanların açıklaması şöyle
"type" (string)
- A URI reference [RFC3986] that identifies the
    problem type.  This specification encourages that, when
    dereferenced, it provide human-readable documentation for the
    problem type (e.g., using HTML [W3C.REC-html5-20141028]).  When
    this member is not present, its value is assumed to be "about:blank".

"title" (string)
- A short, human-readable summary of the problem
    type.  It SHOULD NOT change from occurrence to occurrence of the
    problem, except for purposes of localization (e.g., using
    proactive content negotiation; see [RFC7231], Section 3.4).

"status" (number) 
- The HTTP status code ([RFC7231], Section 6)
    generated by the origin server for this occurrence of the problem.

"detail" (string) 
- A human-readable explanation specific to this occurrence of the problem.

"instance" (string) 
- A URI reference that identifies the specific
    occurrence of the problem.  It may or may not yield further
    information if dereferenced.
Çıktı şöyle
{
 "title": "Bad Request",
 "status": 400,
 "detail": "The request is invalid.",
 "type": "https://example.com/errors/bad-request",
 "instance": "/orders/1234"
}
constructor
Örnek
Şöyle yaparız
@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(value = { CustomException.class })
  protected ResponseEntity<Object> handleCustomException(CustomException ex, 
    WebRequest request) {
    ProblemDetail problemDetail = new ProblemDetails();
    problemDetail.setStatus(HttpStatus.BAD_REQUEST);
    problemDetail.setTitle("Custom Exception");
    problemDetail.setDetail(ex.getMessage());
    return handleExceptionInternal(ex, problemDetail, new HttpHeaders(),
      HttpStatus.BAD_REQUEST, request);
  }
}
forStatusAndDetail metodu
Örnek
Şöyle yaparız
@ControllerAdvice
public class ProblemDetailErrorHandlingControllerAdvice {

  @ExceptionHandler
  public ProblemDetail onException(IllegalArgumentException e) {
    return ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(400), e.getMessage());
  }
}
Çıktısı şöyle
{
  "type":"about:blank",
  "title":"Bad Request",
  "status":400,
  "detail":"User with firstName=Natashaa not found.",
  "instance":"/users"
}


24 Nisan 2023 Pazartesi

SpringSecurity ServerAuthenticationEntryPoint Arayüzü - WebFlux İle Kullanılır

Örnek
Şöyle yaparız
mport org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class CustomAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

    //Authentication entry point has commence method when failures occur
    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return new AuthFailureHandler().formatResponse(response);
    }
}

SpringSecurity ServerAccessDeniedHandler Arayüzü - WebFlux İle Kullanılır

Örnek
Şöyle yaparız
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class CustomAccessDeniedHandler implements ServerAccessDeniedHandler {

  //Access Denied / unauthorized has handle method when failures occur
  @Override
  public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException accessDeniedException) {
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(HttpStatus.FORBIDDEN);
    return new AuthFailureHandler().formatResponse(response);
  }
}
Cevabı formatlayan kod şöyledir
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.godwinpinto.authable.application.rest.auth.json.ApiResponse;
import lombok.NoArgsConstructor;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@NoArgsConstructor
public class AuthFailureHandler {

 public Mono<Void> formatResponse(ServerHttpResponse response) {
   response.getHeaders()
     .add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
   ObjectMapper mapper = new ObjectMapper();
   ApiResponse apiResponse = new ApiResponse(response.getStatusCode()
     .value(), "Access Denied", null);
   StringBuilder json = new StringBuilder();
   try {
     json.append(mapper.writeValueAsString(apiResponse));
   } catch (JsonProcessingException jsonProcessingException) {
   }

   String responseBody = json.toString();
   byte[] bytes = responseBody.getBytes(StandardCharsets.UTF_8);
   DataBuffer buffer = response.bufferFactory()
     .wrap(bytes);
   return response.writeWith(Mono.just(buffer));
  }
}

23 Nisan 2023 Pazar

SpringMVC RestTemplate.setInterceptors metodu

Örnek
Şöyle yaparız.
List<ClientHttpRequestInterceptor> interceptors = ...;
restTemplate.setInterceptors(interceptors);
Örnek
Şöyle yaparız
@Configuration
public class RestTemplateConfig {

  @Bean
  public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new
        SimpleClientHttpRequestFactory()));

    restTemplate.setInterceptors(Collections.singletonList(new RestTemplateFilter()));
    return restTemplate;
  }
}
Açıklaması şöyle
If we don’t initialise Rest Template with the BufferingClientHttpRequestFactory class, we will get an OK response to our requests but we will get a null value for the response body.
interceptor şöyledir
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

public class RestTemplateFilter implements ClientHttpRequestInterceptor {
  private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateFilter.class);

  @Override
  public ClientHttpResponse intercept(HttpRequest request, byte[] body,
      ClientHttpRequestExecution execution) throws IOException {

    LOGGER.info("RestTemplate does a http/s to - {} with HTTP Method : {}", 
      request.getURI(), request.getMethodValue());

    ClientHttpResponse response = execution.execute(request, body);

    if (response.getStatusCode().is4xxClientError() || response.getStatusCode()
        .is5xxServerError()) {
      LOGGER.error("RestTemplate received a bad response from : {} 
        - with response status : {} and body : {} ",
      request.getURI(), response.getRawStatusCode(), 
        new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8));
    } else {
      LOGGER.info("RestTemplate received a good response from : {}
        - with response status {}",
          request.getURI(),
          response.getRawStatusCode());
    }
    return response;
  }
}
Açıklaması şöyle
In intercept method, the HTTP request is logged, including the HTTP method, URI and request body (if any), as well as the HTTP response, including status code, status text and response body (if any).

14 Nisan 2023 Cuma

SpringBatch JobRepositoryFactoryBean Arayüzü

Giriş
Şu satırı dahil ederiz 
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
JobRepository nesnesi yaratır

Örnek
Şöyle yaparız
@Bean
 public JobRepository hazelCastJobRepo(DataSource dataSource, 
  HazelcastTransactionManager hazelcastTransactionManager) {

  JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
  jobRepositoryFactoryBean.setDataSource(dataSource);
  jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
  jobRepositoryFactoryBean.setTransactionManager(hazelcastTransactionManager);
  return jobRepositoryFactoryBean.getObject();
}
Örnek
Şöyle yaparız
@Autowired
private DataSource dataSource;

@Bean
public JobRepository jobRepository() throws Exception {
  JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
  factory.setDataSource(dataSource);
  factory.setTransactionManager(transactionManager());
  factory.afterPropertiesSet();
  return factory.getObject();
}

@Bean
public PlatformTransactionManager transactionManager() {
  return new DataSourceTransactionManager(dataSource);
}

@Bean
public JobLauncher jobLauncher() throws Exception {
  SimpleJobLauncher launcher = new SimpleJobLauncher();
  launcher.setJobRepository(jobRepository());
  launcher.setTaskExecutor(new SimpleAsyncTaskExecutor());
  launcher.afterPropertiesSet();
  return launcher;
}

SpringCache SimpleCacheManager Sınıfı

Giriş
Şu satırı dahil ederiz
import  org.springframework.cache.support.SimpleCacheManager;
Örnek
Şöyle yaparız
@Configuration
@EnableCaching
public class CachingConfig {

  @Bean
  public CacheManager cacheManager() {
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(Arrays.asList(
      new ConcurrentMapCache("directory"), 
      new ConcurrentMapCache("addresses")));
    return cacheManager;
  }
}
Örnek
Şöyle yaparız.
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
  <property name="caches">
    <set>
      <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
         p:name="foo"/>
    </set>
  </property>
</bean>