JAVA-14288 Rename spring-5-reactive-modules to spring-reactive-modules (#12659)
* JAVA-14288 Rename spring-5-reactive-modules to spring-reactive-modules * JAVA-14288 Remove failing module * JAVA-14288 Revert commenting spring-cloud-openfeign-2 module
This commit is contained in:
3
spring-reactive-modules/README.md
Normal file
3
spring-reactive-modules/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Spring Reactive
|
||||
|
||||
This module contains modules about Spring Reactive
|
||||
63
spring-reactive-modules/pom.xml
Normal file
63
spring-reactive-modules/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>spring-reactive-modules</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>spring-5-data-reactive</module>
|
||||
<module>spring-5-reactive</module>
|
||||
<module>spring-5-reactive-2</module>
|
||||
<module>spring-5-reactive-3</module>
|
||||
<module>spring-5-reactive-client</module>
|
||||
<module>spring-5-reactive-oauth</module>
|
||||
<module>spring-5-reactive-security</module>
|
||||
<module>spring-reactive</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<properties>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
10
spring-reactive-modules/spring-5-data-reactive/README.md
Normal file
10
spring-reactive-modules/spring-5-data-reactive/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## Spring Data Reactive Project
|
||||
|
||||
This module contains articles about reactive Spring 5 Data
|
||||
|
||||
### The Course
|
||||
The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||
|
||||
### Relevant Articles
|
||||
- [A Quick Look at R2DBC with Spring Data](https://www.baeldung.com/spring-data-r2dbc)
|
||||
- [Spring Data Reactive Repositories with Couchbase](https://www.baeldung.com/spring-data-reactive-couchbase)
|
||||
155
spring-reactive-modules/spring-5-data-reactive/pom.xml
Normal file
155
spring-reactive-modules/spring-5-data-reactive/pom.xml
Normal file
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-5-data-reactive</artifactId>
|
||||
<name>spring-5-data-reactive</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-bom</artifactId>
|
||||
<version>${log4j2.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>${reactor-core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-couchbase-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
<version>${spring-tx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>${spring-data-r2dbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<version>${r2dbc-h2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.couchbase.mock</groupId>
|
||||
<artifactId>CouchbaseMock</artifactId>
|
||||
<version>${couchbaseMock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- Replacing default-compile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<!-- Replacing default-testCompile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-testCompile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<spring-tx.version>5.2.2.RELEASE</spring-tx.version>
|
||||
<spring-data-r2dbc.version>1.0.0.RELEASE</spring-data-r2dbc.version>
|
||||
<r2dbc-h2.version>0.8.1.RELEASE</r2dbc-h2.version>
|
||||
<httpclient.version>4.5.2</httpclient.version>
|
||||
<couchbaseMock.version>1.5.23</couchbaseMock.version>
|
||||
<reactor-core.version>3.3.1.RELEASE</reactor-core.version>
|
||||
<!-- This spring-boot.version is set manually, For upgrading this please refer http://team.baeldung.com/browse/JAVA-2802 -->
|
||||
<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
|
||||
<log4j2.version>2.17.1</log4j2.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baeldung.couchbase;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
|
||||
public class ReactiveCouchbaseApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ReactiveCouchbaseApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.baeldung.couchbase.configuration;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:couchbase.properties")
|
||||
public class CouchbaseProperties {
|
||||
|
||||
private final List<String> bootstrapHosts;
|
||||
private final String bucketName;
|
||||
private final String bucketPassword;
|
||||
private final int port;
|
||||
|
||||
public CouchbaseProperties(@Value("${spring.couchbase.bootstrap-hosts}") final List<String> bootstrapHosts, @Value("${spring.couchbase.bucket.name}") final String bucketName, @Value("${spring.couchbase.bucket.password}") final String bucketPassword,
|
||||
@Value("${spring.couchbase.port}") final int port) {
|
||||
this.bootstrapHosts = Collections.unmodifiableList(bootstrapHosts);
|
||||
this.bucketName = bucketName;
|
||||
this.bucketPassword = bucketPassword;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public List<String> getBootstrapHosts() {
|
||||
return bootstrapHosts;
|
||||
}
|
||||
|
||||
public String getBucketName() {
|
||||
return bucketName;
|
||||
}
|
||||
|
||||
public String getBucketPassword() {
|
||||
return bucketPassword;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.couchbase.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveCouchbaseRepositories("com.baeldung.couchbase.domain.repository.n1ql")
|
||||
@Primary
|
||||
public class N1QLReactiveCouchbaseConfiguration extends ReactiveCouchbaseConfiguration {
|
||||
|
||||
public N1QLReactiveCouchbaseConfiguration(CouchbaseProperties couchbaseProperties) {
|
||||
super(couchbaseProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.baeldung.couchbase.configuration;
|
||||
|
||||
import com.couchbase.client.java.env.CouchbaseEnvironment;
|
||||
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.config.BeanNames;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
|
||||
|
||||
private CouchbaseProperties couchbaseProperties;
|
||||
|
||||
public ReactiveCouchbaseConfiguration(final CouchbaseProperties couchbaseProperties) {
|
||||
this.couchbaseProperties = couchbaseProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getBootstrapHosts() {
|
||||
return couchbaseProperties.getBootstrapHosts();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketName() {
|
||||
return couchbaseProperties.getBucketName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketPassword() {
|
||||
return couchbaseProperties.getBucketPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseEnvironment couchbaseEnvironment() {
|
||||
return DefaultCouchbaseEnvironment
|
||||
.builder()
|
||||
.bootstrapHttpDirectPort(couchbaseProperties.getPort())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
|
||||
public IndexManager couchbaseIndexManager() {
|
||||
return new IndexManager(true, true, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baeldung.couchbase.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveCouchbaseRepositories("com.baeldung.couchbase.domain.repository.view")
|
||||
public class ViewReactiveCouchbaseConfiguration extends ReactiveCouchbaseConfiguration {
|
||||
|
||||
public ViewReactiveCouchbaseConfiguration(CouchbaseProperties couchbaseProperties) {
|
||||
super(couchbaseProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.baeldung.couchbase.domain;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Document
|
||||
public class Person {
|
||||
|
||||
@Id private UUID id;
|
||||
private String firstName;
|
||||
|
||||
public Person(final UUID id, final String firstName) {
|
||||
this.id = id;
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
private Person() {
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Person person = (Person) o;
|
||||
return Objects.equals(id, person.id) && Objects.equals(firstName, person.firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, firstName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baeldung.couchbase.domain.repository.n1ql;
|
||||
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
@N1qlPrimaryIndexed
|
||||
public interface N1QLPersonRepository extends ReactiveCrudRepository<Person, UUID> {
|
||||
|
||||
Flux<Person> findAllByFirstName(final String firstName);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baeldung.couchbase.domain.repository.n1ql;
|
||||
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
@N1qlPrimaryIndexed
|
||||
public interface N1QLSortingPersonRepository extends ReactiveSortingRepository<Person, UUID> {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.baeldung.couchbase.domain.repository.view;
|
||||
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
@ViewIndexed(designDoc = ViewPersonRepository.DESIGN_DOCUMENT)
|
||||
public interface ViewPersonRepository extends ReactiveCrudRepository<Person, UUID> {
|
||||
|
||||
String DESIGN_DOCUMENT = "person";
|
||||
|
||||
@View(designDocument = ViewPersonRepository.DESIGN_DOCUMENT)
|
||||
Flux<Person> findByFirstName(String firstName);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.baeldung.r2dbc;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = "com.baeldung.r2dbc")
|
||||
public class R2dbcApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(R2dbcApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.baeldung.r2dbc.configuration;
|
||||
|
||||
import io.r2dbc.h2.H2ConnectionConfiguration;
|
||||
import io.r2dbc.h2.H2ConnectionFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories(basePackages = "com.baeldung.r2dbc.repository")
|
||||
public class R2DBCConfiguration extends AbstractR2dbcConfiguration {
|
||||
@Bean
|
||||
public H2ConnectionFactory connectionFactory() {
|
||||
return new H2ConnectionFactory(
|
||||
H2ConnectionConfiguration.builder()
|
||||
.url("mem:testdb;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4")
|
||||
.username("sa")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.baeldung.r2dbc.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table
|
||||
public class Player {
|
||||
@Id
|
||||
Integer id;
|
||||
String name;
|
||||
Integer age;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.r2dbc.repository;
|
||||
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
|
||||
import com.baeldung.r2dbc.model.Player;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface PlayerRepository extends ReactiveCrudRepository<Player, Integer> {
|
||||
|
||||
@Query("select id, name, age from player where name = $1")
|
||||
Flux<Player> findAllByName(String name);
|
||||
|
||||
@Query("select * from player where age = $1")
|
||||
Flux<Player> findByAge(int age);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
spring.couchbase.bucket.name=default
|
||||
spring.couchbase.bootstrap-hosts=localhost
|
||||
spring.couchbase.port=8091
|
||||
spring.couchbase.bucket.password=123456
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import com.baeldung.r2dbc.R2dbcApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = R2dbcApplication.class)
|
||||
public class SpringContextTest {
|
||||
|
||||
@Test
|
||||
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.baeldung.couchbase.domain.repository;
|
||||
|
||||
import com.baeldung.couchbase.configuration.CouchbaseProperties;
|
||||
import com.couchbase.mock.Bucket;
|
||||
import com.couchbase.mock.BucketConfiguration;
|
||||
import com.couchbase.mock.CouchbaseMock;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
|
||||
@TestConfiguration
|
||||
public class CouchbaseMockConfiguration {
|
||||
|
||||
private CouchbaseMock couchbaseMock;
|
||||
|
||||
public CouchbaseMockConfiguration(final CouchbaseProperties couchbaseProperties) {
|
||||
final BucketConfiguration bucketConfiguration = new BucketConfiguration();
|
||||
bucketConfiguration.numNodes = 1;
|
||||
bucketConfiguration.numReplicas = 1;
|
||||
bucketConfiguration.numVBuckets = 1024;
|
||||
bucketConfiguration.name = couchbaseProperties.getBucketName();
|
||||
bucketConfiguration.type = Bucket.BucketType.COUCHBASE;
|
||||
bucketConfiguration.password = couchbaseProperties.getBucketPassword();
|
||||
|
||||
try {
|
||||
couchbaseMock = new CouchbaseMock(couchbaseProperties.getPort(), Collections.singletonList(bucketConfiguration));
|
||||
} catch (final IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
try {
|
||||
couchbaseMock.start();
|
||||
} catch (final IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
try {
|
||||
couchbaseMock.waitForStartup();
|
||||
} catch (final InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
couchbaseMock.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.baeldung.couchbase.domain.repository.n1ql;
|
||||
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = {"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration"})
|
||||
public class N1QLPersonRepositoryLiveTest {
|
||||
|
||||
@Autowired private N1QLPersonRepository personRepository;
|
||||
|
||||
@Test
|
||||
public void shouldFindAll_byLastName() {
|
||||
//Given
|
||||
final String firstName = "John";
|
||||
final Person matchingPerson = new Person(UUID.randomUUID(), firstName);
|
||||
final Person nonMatchingPerson = new Person(UUID.randomUUID(), "NotJohn");
|
||||
wrap(() -> {
|
||||
personRepository
|
||||
.save(matchingPerson)
|
||||
.subscribe();
|
||||
personRepository
|
||||
.save(nonMatchingPerson)
|
||||
.subscribe();
|
||||
//When
|
||||
final Flux<Person> allByFirstName = personRepository.findAllByFirstName(firstName);
|
||||
//Then
|
||||
StepVerifier
|
||||
.create(allByFirstName)
|
||||
.expectNext(matchingPerson)
|
||||
.verifyComplete();
|
||||
|
||||
}, matchingPerson, nonMatchingPerson);
|
||||
}
|
||||
|
||||
private void wrap(final Runnable runnable, final Person... people) {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
for (final Person person : people) {
|
||||
personRepository
|
||||
.delete(person)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.baeldung.couchbase.domain.repository.n1ql;
|
||||
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = {"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration"})
|
||||
public class N1QLSortingPersonRepositoryLiveTest {
|
||||
|
||||
@Autowired private N1QLSortingPersonRepository personRepository;
|
||||
|
||||
@Test
|
||||
public void shouldFindAll_sortedByFirstName() {
|
||||
//Given
|
||||
final Person firstPerson = new Person(UUID.randomUUID(), "John");
|
||||
final Person secondPerson = new Person(UUID.randomUUID(), "Mikki");
|
||||
wrap(() -> {
|
||||
personRepository
|
||||
.save(firstPerson)
|
||||
.subscribe();
|
||||
personRepository
|
||||
.save(secondPerson)
|
||||
.subscribe();
|
||||
//When
|
||||
final Flux<Person> allByFirstName = personRepository.findAll(Sort.by(Sort.Direction.DESC, "firstName"));
|
||||
//Then
|
||||
StepVerifier
|
||||
.create(allByFirstName)
|
||||
.expectNextMatches(person -> person
|
||||
.getFirstName()
|
||||
.equals(secondPerson.getFirstName()))
|
||||
.expectNextMatches(person -> person
|
||||
.getFirstName()
|
||||
.equals(firstPerson.getFirstName()))
|
||||
.verifyComplete();
|
||||
}, firstPerson, secondPerson);
|
||||
}
|
||||
|
||||
//workaround for deleteAll()
|
||||
private void wrap(final Runnable runnable, final Person... people) {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
for (final Person person : people) {
|
||||
personRepository
|
||||
.delete(person)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.baeldung.couchbase.domain.repository.view;
|
||||
|
||||
import com.baeldung.couchbase.configuration.CouchbaseProperties;
|
||||
import com.baeldung.couchbase.configuration.ViewReactiveCouchbaseConfiguration;
|
||||
import com.baeldung.couchbase.domain.Person;
|
||||
import com.baeldung.couchbase.domain.repository.CouchbaseMockConfiguration;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = { "spring.couchbase.port=10010", "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration" },
|
||||
classes = { CouchbaseMockConfiguration.class, ViewReactiveCouchbaseConfiguration.class, CouchbaseProperties.class })
|
||||
public class ViewPersonRepositoryIntegrationTest {
|
||||
|
||||
@Autowired private ViewPersonRepository personRepository;
|
||||
|
||||
@Test
|
||||
public void shouldSavePerson_findById_thenDeleteIt() {
|
||||
//Given
|
||||
final UUID id = UUID.randomUUID();
|
||||
final Person person = new Person(id, "John");
|
||||
wrap(() -> {
|
||||
personRepository
|
||||
.save(person)
|
||||
.subscribe();
|
||||
//When
|
||||
final Mono<Person> byId = personRepository.findById(id);
|
||||
//Then
|
||||
StepVerifier
|
||||
.create(byId)
|
||||
.expectNextMatches(result -> result
|
||||
.getId()
|
||||
.equals(id))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}, person);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAll_thenDeleteIt() {
|
||||
//Given
|
||||
final Person person = new Person(UUID.randomUUID(), "John");
|
||||
final Person secondPerson = new Person(UUID.randomUUID(), "Mikki");
|
||||
wrap(() -> {
|
||||
personRepository
|
||||
.save(person)
|
||||
.subscribe();
|
||||
personRepository
|
||||
.save(secondPerson)
|
||||
.subscribe();
|
||||
//When
|
||||
final Flux<Person> all = personRepository.findAll();
|
||||
//Then
|
||||
StepVerifier
|
||||
.create(all)
|
||||
.expectNextCount(2)
|
||||
.verifyComplete();
|
||||
}, person, secondPerson);
|
||||
}
|
||||
|
||||
private void wrap(final Runnable runnable, final Person... people) {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
for (final Person person : people) {
|
||||
personRepository
|
||||
.delete(person)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.baeldung.r2dbc;
|
||||
|
||||
|
||||
import com.baeldung.r2dbc.model.Player;
|
||||
import com.baeldung.r2dbc.repository.PlayerRepository;
|
||||
import io.r2dbc.h2.H2ConnectionFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class R2dbcApplicationIntegrationTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
PlayerRepository playerRepository;
|
||||
|
||||
@Autowired
|
||||
DatabaseClient client;
|
||||
|
||||
@Autowired
|
||||
H2ConnectionFactory factory;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
Hooks.onOperatorDebug();
|
||||
|
||||
List<String> statements = Arrays.asList(//
|
||||
"DROP TABLE IF EXISTS player;",
|
||||
"CREATE table player (id INT AUTO_INCREMENT NOT NULL, name VARCHAR2, age INT NOT NULL);");
|
||||
|
||||
statements.forEach(it -> client.execute(it) //
|
||||
.fetch() //
|
||||
.rowsUpdated() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenDeleteAll_then0IsExpected() {
|
||||
|
||||
|
||||
playerRepository.deleteAll()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenInsert6_then6AreExpected() {
|
||||
|
||||
insertPlayers();
|
||||
|
||||
playerRepository.findAll()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(6)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSearchForCR7_then1IsExpected() {
|
||||
|
||||
insertPlayers();
|
||||
|
||||
playerRepository.findAllByName("CR7")
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSearchFor32YearsOld_then2AreExpected() {
|
||||
insertPlayers();
|
||||
|
||||
playerRepository.findByAge(32)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(2)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenBatchHas2Operations_then2AreExpected() {
|
||||
Mono.from(factory.create())
|
||||
.flatMapMany(connection -> Flux.from(connection
|
||||
.createBatch()
|
||||
.add("select * from player")
|
||||
.add("select * from player")
|
||||
.execute()))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(2)
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
private void insertPlayers() {
|
||||
List<Player> players = Arrays.asList(
|
||||
new Player(null, "Kaka", 37),
|
||||
new Player(null, "Messi", 32),
|
||||
new Player(null, "Mbappé", 20),
|
||||
new Player(null, "CR7", 34),
|
||||
new Player(null, "Lewandowski", 30),
|
||||
new Player(null, "Cavani", 32)
|
||||
);
|
||||
|
||||
playerRepository.saveAll(players).subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
12
spring-reactive-modules/spring-5-reactive-2/.gitignore
vendored
Normal file
12
spring-reactive-modules/spring-5-reactive-2/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#folders#
|
||||
.idea
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
10
spring-reactive-modules/spring-5-reactive-2/README.md
Normal file
10
spring-reactive-modules/spring-5-reactive-2/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## Spring 5 Reactive Project
|
||||
|
||||
This module contains articles about reactive Spring 5.
|
||||
|
||||
- [Validation for Functional Endpoints in Spring 5](https://www.baeldung.com/spring-functional-endpoints-validation)
|
||||
- [Testing Reactive Streams Using StepVerifier and TestPublisher](https://www.baeldung.com/reactive-streams-step-verifier-test-publisher)
|
||||
- [Static Content in Spring WebFlux](https://www.baeldung.com/spring-webflux-static-content)
|
||||
- [Server-Sent Events in Spring](https://www.baeldung.com/spring-server-sent-events)
|
||||
- [Backpressure Mechanism in Spring WebFlux](https://www.baeldung.com/spring-webflux-backpressure)
|
||||
- More articles: [[<-- prev]](../spring-5-reactive) [[next -->]](../spring-5-reactive-3)
|
||||
117
spring-reactive-modules/spring-5-reactive-2/pom.xml
Normal file
117
spring-reactive-modules/spring-5-reactive-2/pom.xml
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-5-reactive-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-5-reactive-2</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>spring 5 sample project about new features</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectreactor</groupId>
|
||||
<artifactId>reactor-spring</artifactId>
|
||||
<version>${reactor-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>${wiremock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.baeldung.webclient.WebClientApplication</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>integration-lite-first</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<logback.configurationFile>${project.basedir}/src/test/resources/logback-test.xml</logback.configurationFile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>integration-lite-second</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<logback.configurationFile>${project.basedir}/src/test/resources/logback-test.xml</logback.configurationFile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<properties>
|
||||
<reactor-spring.version>1.0.1.RELEASE</reactor-spring.version>
|
||||
<wiremock.version>2.24.0</wiremock.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.baeldung.reactive.serversentevents.consumer;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication(exclude = { RedisReactiveAutoConfiguration.class })
|
||||
@EnableAsync
|
||||
public class ConsumerSSEApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(ConsumerSSEApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8082"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain sseConsumerSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange()
|
||||
.anyExchange()
|
||||
.permitAll();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.baeldung.reactive.serversentevents.consumer.controller;
|
||||
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/sse-consumer")
|
||||
public class ClientController {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ClientController.class);
|
||||
private WebClient client = WebClient.create("http://localhost:8081/sse-server");
|
||||
|
||||
@GetMapping("/launch-sse-client")
|
||||
public String launchSSEFromSSEWebClient() {
|
||||
consumeSSE();
|
||||
return "LAUNCHED EVENT CLIENT!!! Check the logs...";
|
||||
}
|
||||
|
||||
@GetMapping("/launch-flux-client")
|
||||
public String launchcFluxFromSSEWebClient() {
|
||||
consumeFlux();
|
||||
return "LAUNCHED EVENT CLIENT!!! Check the logs...";
|
||||
}
|
||||
|
||||
@GetMapping("/launch-sse-from-flux-endpoint-client")
|
||||
public String launchFluxFromFluxWebClient() {
|
||||
consumeSSEFromFluxEndpoint();
|
||||
return "LAUNCHED EVENT CLIENT!!! Check the logs...";
|
||||
}
|
||||
|
||||
@Async
|
||||
public void consumeSSE() {
|
||||
ParameterizedTypeReference<ServerSentEvent<String>> type = new ParameterizedTypeReference<ServerSentEvent<String>>() {
|
||||
};
|
||||
|
||||
Flux<ServerSentEvent<String>> eventStream = client.get()
|
||||
.uri("/stream-sse")
|
||||
.retrieve()
|
||||
.bodyToFlux(type);
|
||||
|
||||
eventStream.subscribe(content -> logger.info("Current time: {} - Received SSE: name[{}], id [{}], content[{}] ", LocalTime.now(), content.event(), content.id(), content.data()), error -> logger.error("Error receiving SSE: {}", error),
|
||||
() -> logger.info("Completed!!!"));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void consumeFlux() {
|
||||
Flux<String> stringStream = client.get()
|
||||
.uri("/stream-flux")
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class);
|
||||
|
||||
stringStream.subscribe(content -> logger.info("Current time: {} - Received content: {} ", LocalTime.now(), content), error -> logger.error("Error retrieving content: {}", error), () -> logger.info("Completed!!!"));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void consumeSSEFromFluxEndpoint() {
|
||||
ParameterizedTypeReference<ServerSentEvent<String>> type = new ParameterizedTypeReference<ServerSentEvent<String>>() {
|
||||
};
|
||||
|
||||
Flux<ServerSentEvent<String>> eventStream = client.get()
|
||||
.uri("/stream-flux")
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.retrieve()
|
||||
.bodyToFlux(type);
|
||||
|
||||
eventStream.subscribe(content -> logger.info("Current time: {} - Received SSE: name[{}], id [{}], content[{}] ", LocalTime.now(), content.event(), content.id(), content.data()), error -> logger.error("Error receiving SSE: {}", error),
|
||||
() -> logger.info("Completed!!!"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.reactive.serversentevents.server;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication(exclude = { RedisReactiveAutoConfiguration.class })
|
||||
public class ServerSSEApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(ServerSSEApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8081"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain sseServerSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange()
|
||||
.anyExchange()
|
||||
.permitAll();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baeldung.reactive.serversentevents.server.controllers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/sse-server")
|
||||
public class ServerController {
|
||||
|
||||
@GetMapping("/stream-sse")
|
||||
public Flux<ServerSentEvent<String>> streamEvents() {
|
||||
return Flux.interval(Duration.ofSeconds(1))
|
||||
.map(sequence -> ServerSentEvent.<String> builder()
|
||||
.id(String.valueOf(sequence))
|
||||
.event("periodic-event")
|
||||
.data("SSE - " + LocalTime.now()
|
||||
.toString())
|
||||
.build());
|
||||
}
|
||||
|
||||
@GetMapping(path = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> streamFlux() {
|
||||
return Flux.interval(Duration.ofSeconds(1))
|
||||
.map(sequence -> "Flux - " + LocalTime.now()
|
||||
.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.baeldung.staticcontent;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SpringBootApplication
|
||||
public class StaticContentApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(StaticContentApplication.class);
|
||||
app.setDefaultProperties(Collections.singletonMap("server.port", "8084"));
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain staticContentSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange()
|
||||
.anyExchange()
|
||||
.permitAll();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baeldung.staticcontent;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
@Configuration
|
||||
public class StaticContentConfig {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> htmlRouter(@Value("classpath:/public/index.html") Resource html) {
|
||||
return route(
|
||||
GET("/"),
|
||||
request -> ok()
|
||||
.contentType(MediaType.TEXT_HTML)
|
||||
.syncBody(html)
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> imgRouter() {
|
||||
return RouterFunctions.resources("/img/**", new ClassPathResource("img/"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.baeldung.validations.functional;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@SpringBootApplication
|
||||
public class FunctionalValidationsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FunctionalValidationsApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain functionalValidationsSpringSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange()
|
||||
.anyExchange()
|
||||
.permitAll();
|
||||
http.csrf().disable();
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.baeldung.validations.functional.handlers;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public abstract class AbstractValidationHandler<T, U extends Validator> {
|
||||
|
||||
private final Class<T> validationClass;
|
||||
|
||||
private final U validator;
|
||||
|
||||
protected AbstractValidationHandler(Class<T> clazz, U validator) {
|
||||
this.validationClass = clazz;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
abstract protected Mono<ServerResponse> processBody(T validBody, final ServerRequest originalRequest);
|
||||
|
||||
public final Mono<ServerResponse> handleRequest(final ServerRequest request) {
|
||||
return request.bodyToMono(this.validationClass)
|
||||
.flatMap(body -> {
|
||||
Errors errors = new BeanPropertyBindingResult(body, this.validationClass.getName());
|
||||
this.validator.validate(body, errors);
|
||||
|
||||
if (errors == null || errors.getAllErrors()
|
||||
.isEmpty()) {
|
||||
return processBody(body, request);
|
||||
} else {
|
||||
return onValidationErrors(errors, body, request);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<ServerResponse> onValidationErrors(Errors errors, T invalidBody, final ServerRequest request) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errors.getAllErrors()
|
||||
.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.baeldung.validations.functional.handlers;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.baeldung.validations.functional.model.CustomRequestEntity;
|
||||
import com.baeldung.validations.functional.validators.CustomRequestEntityValidator;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class FunctionalHandler {
|
||||
|
||||
public Mono<ServerResponse> handleRequest(final ServerRequest request) {
|
||||
Validator validator = new CustomRequestEntityValidator();
|
||||
Mono<String> responseBody = request.bodyToMono(CustomRequestEntity.class)
|
||||
.map(body -> {
|
||||
Errors errors = new BeanPropertyBindingResult(body, CustomRequestEntity.class.getName());
|
||||
validator.validate(body, errors);
|
||||
|
||||
if (errors == null || errors.getAllErrors()
|
||||
.isEmpty()) {
|
||||
return String.format("Hi, %s [%s]!", body.getName(), body.getCode());
|
||||
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errors.getAllErrors()
|
||||
.toString());
|
||||
}
|
||||
|
||||
});
|
||||
return ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(responseBody, String.class);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.validations.functional.handlers.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.validations.functional.handlers.AbstractValidationHandler;
|
||||
import com.baeldung.validations.functional.model.AnnotatedRequestEntity;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class AnnotatedRequestEntityValidationHandler extends AbstractValidationHandler<AnnotatedRequestEntity, Validator> {
|
||||
|
||||
private AnnotatedRequestEntityValidationHandler(@Autowired Validator validator) {
|
||||
super(AnnotatedRequestEntity.class, validator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<ServerResponse> processBody(AnnotatedRequestEntity validBody, ServerRequest originalRequest) {
|
||||
String responseBody = String.format("Hi, %s. Password: %s!", validBody.getUser(), validBody.getPassword());
|
||||
return ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(responseBody), String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.baeldung.validations.functional.handlers.impl;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.validations.functional.handlers.AbstractValidationHandler;
|
||||
import com.baeldung.validations.functional.model.CustomRequestEntity;
|
||||
import com.baeldung.validations.functional.validators.CustomRequestEntityValidator;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class CustomRequestEntityValidationHandler extends AbstractValidationHandler<CustomRequestEntity, CustomRequestEntityValidator> {
|
||||
|
||||
private CustomRequestEntityValidationHandler() {
|
||||
super(CustomRequestEntity.class, new CustomRequestEntityValidator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<ServerResponse> processBody(CustomRequestEntity validBody, ServerRequest originalRequest) {
|
||||
String responseBody = String.format("Hi, %s [%s]!", validBody.getName(), validBody.getCode());
|
||||
return ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(responseBody), String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<ServerResponse> onValidationErrors(Errors errors, CustomRequestEntity invalidBody, final ServerRequest request) {
|
||||
return ServerResponse.badRequest()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(String.format("Custom message showing the errors: %s", errors.getAllErrors()
|
||||
.toString())), String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.baeldung.validations.functional.handlers.impl;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.validations.functional.handlers.AbstractValidationHandler;
|
||||
import com.baeldung.validations.functional.model.OtherEntity;
|
||||
import com.baeldung.validations.functional.validators.OtherEntityValidator;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class OtherEntityValidationHandler extends AbstractValidationHandler<OtherEntity, OtherEntityValidator> {
|
||||
|
||||
private OtherEntityValidationHandler() {
|
||||
super(OtherEntity.class, new OtherEntityValidator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<ServerResponse> processBody(OtherEntity validBody, ServerRequest originalRequest) {
|
||||
String responseBody = String.format("Other object with item %s and quantity %s!", validBody.getItem(), validBody.getQuantity());
|
||||
return ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(responseBody), String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.baeldung.validations.functional.model;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class AnnotatedRequestEntity {
|
||||
@NotNull
|
||||
private String user;
|
||||
|
||||
@NotNull
|
||||
@Size(min = 4, max = 7)
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.validations.functional.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class CustomRequestEntity {
|
||||
|
||||
private String name;
|
||||
private String code;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.validations.functional.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class OtherEntity {
|
||||
|
||||
private String item;
|
||||
private Integer quantity;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.validations.functional.routers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import com.baeldung.validations.functional.handlers.FunctionalHandler;
|
||||
import com.baeldung.validations.functional.handlers.impl.AnnotatedRequestEntityValidationHandler;
|
||||
import com.baeldung.validations.functional.handlers.impl.CustomRequestEntityValidationHandler;
|
||||
import com.baeldung.validations.functional.handlers.impl.OtherEntityValidationHandler;
|
||||
|
||||
@Configuration
|
||||
public class ValidationsRouters {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> validationsRouter(@Autowired CustomRequestEntityValidationHandler dryHandler,
|
||||
@Autowired FunctionalHandler complexHandler,
|
||||
@Autowired OtherEntityValidationHandler otherHandler,
|
||||
@Autowired AnnotatedRequestEntityValidationHandler annotatedEntityHandler) {
|
||||
return RouterFunctions.route(RequestPredicates.POST("/complex-handler-functional-validation"), complexHandler::handleRequest)
|
||||
.andRoute(RequestPredicates.POST("/dry-functional-validation"), dryHandler::handleRequest)
|
||||
.andRoute(RequestPredicates.POST("/other-dry-functional-validation"), otherHandler::handleRequest)
|
||||
.andRoute(RequestPredicates.POST("/annotated-functional-validation"), annotatedEntityHandler::handleRequest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.validations.functional.validators;
|
||||
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import com.baeldung.validations.functional.model.CustomRequestEntity;
|
||||
|
||||
public class CustomRequestEntityValidator implements Validator {
|
||||
|
||||
private static final int MINIMUM_CODE_LENGTH = 6;
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return CustomRequestEntity.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "field.required");
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "code", "field.required");
|
||||
CustomRequestEntity request = (CustomRequestEntity) target;
|
||||
if (request.getCode() != null && request.getCode()
|
||||
.trim()
|
||||
.length() < MINIMUM_CODE_LENGTH) {
|
||||
errors.rejectValue("code", "field.min.length", new Object[] { Integer.valueOf(MINIMUM_CODE_LENGTH) }, "The code must be at least [" + MINIMUM_CODE_LENGTH + "] characters in length.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.baeldung.validations.functional.validators;
|
||||
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import com.baeldung.validations.functional.model.OtherEntity;
|
||||
|
||||
public class OtherEntityValidator implements Validator {
|
||||
|
||||
private static final int MIN_ITEM_QUANTITY = 1;
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return OtherEntity.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "item", "field.required");
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "quantity", "field.required");
|
||||
OtherEntity request = (OtherEntity) target;
|
||||
if (request.getQuantity() != null && request.getQuantity() < MIN_ITEM_QUANTITY) {
|
||||
errors.rejectValue("quantity", "field.min.length", new Object[] { Integer.valueOf(MIN_ITEM_QUANTITY) }, "There must be at least one item");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Use in Static content Example
|
||||
spring.webflux.static-path-pattern = /assets/**
|
||||
spring.resources.static-locations = classpath:/assets/
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.baeldung.backpressure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
public class BackpressureUnitTest {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BackpressureUnitTest.class);
|
||||
|
||||
@Test
|
||||
public void whenLimitRateSet_thenSplitIntoChunks() throws InterruptedException {
|
||||
Flux<Integer> limit = Flux.range(1, 25);
|
||||
|
||||
limit.limitRate(10);
|
||||
limit.subscribe(
|
||||
value -> LOGGER.debug(String.valueOf(value)),
|
||||
err -> err.printStackTrace(),
|
||||
() -> LOGGER.debug("Finished!!"),
|
||||
subscription -> subscription.request(15)
|
||||
);
|
||||
|
||||
StepVerifier.create(limit)
|
||||
.expectSubscription()
|
||||
.thenRequest(15)
|
||||
.expectNext(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
.expectNext(11, 12, 13, 14, 15)
|
||||
.thenRequest(10)
|
||||
.expectNext(16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingChunks10_thenMessagesAreReceived() {
|
||||
Flux<Integer> request = Flux.range(1, 50);
|
||||
|
||||
request.subscribe(
|
||||
integer -> LOGGER.debug(String.valueOf(integer)),
|
||||
err -> err.printStackTrace(),
|
||||
() -> LOGGER.debug("All 50 items have been successfully processed!!!"),
|
||||
subscription -> {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
LOGGER.debug("Requesting the next 10 elements!!!");
|
||||
subscription.request(10);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
StepVerifier.create(request)
|
||||
.expectSubscription()
|
||||
.thenRequest(10)
|
||||
.expectNext(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
.thenRequest(10)
|
||||
.expectNext(11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
|
||||
.thenRequest(10)
|
||||
.expectNext(21, 22, 23, 24, 25, 26, 27 , 28, 29 ,30)
|
||||
.thenRequest(10)
|
||||
.expectNext(31, 32, 33, 34, 35, 36, 37 , 38, 39 ,40)
|
||||
.thenRequest(10)
|
||||
.expectNext(41, 42, 43, 44, 45, 46, 47 , 48, 49 ,50)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenCancel_thenSubscriptionFinished() {
|
||||
Flux<Integer> cancel = Flux.range(1, 10).log();
|
||||
|
||||
cancel.subscribe(new BaseSubscriber<Integer>() {
|
||||
@Override
|
||||
protected void hookOnNext(Integer value) {
|
||||
request(3);
|
||||
LOGGER.debug(String.valueOf(value));
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
|
||||
StepVerifier.create(cancel)
|
||||
.expectNext(1, 2, 3)
|
||||
.thenCancel()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.baeldung.reactive.serversentsevents;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import com.baeldung.reactive.serversentevents.server.ServerSSEApplication;
|
||||
|
||||
@SpringBootTest(classes = ServerSSEApplication.class)
|
||||
@WithMockUser
|
||||
public class ServiceSentEventLiveTest {
|
||||
|
||||
private WebTestClient client = WebTestClient.bindToServer()
|
||||
.baseUrl("http://localhost:8081/sse-server")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void whenSSEEndpointIsCalled_thenEventStreamingBegins() {
|
||||
|
||||
Executable sseStreamingCall = () -> client.get()
|
||||
.uri("/stream-sse")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)
|
||||
.expectBody(String.class);
|
||||
|
||||
Assertions.assertThrows(IllegalStateException.class, sseStreamingCall, "Expected test to timeout and throw IllegalStateException, but it didn't");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFluxEndpointIsCalled_thenEventStreamingBegins() {
|
||||
|
||||
Executable sseStreamingCall = () -> client.get()
|
||||
.uri("/stream-flux")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)
|
||||
.expectBody(String.class);
|
||||
|
||||
Assertions.assertThrows(IllegalStateException.class, sseStreamingCall, "Expected test to timeout and throw IllegalStateException, but it didn't");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.baeldung.staticcontent;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("assets-custom-location")
|
||||
public class StaticContentCustomLocationIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient client;
|
||||
|
||||
@Test
|
||||
public void whenRequestingHtmlFileFromCustomLocation_thenReturnThisFileAndTextHtmlContentType() throws Exception {
|
||||
client.get()
|
||||
.uri("/assets/index.html")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().valueEquals(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingMissingStaticResource_thenReturnNotFoundStatus() throws Exception {
|
||||
client.get()
|
||||
.uri("/assets/unknown.png")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.baeldung.staticcontent;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class StaticContentDefaultLocationIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient client;
|
||||
|
||||
@Test
|
||||
public void whenRequestingHtmlFileFromDefaultLocation_thenReturnThisFileAndTextHtmlContentType() throws Exception {
|
||||
client.get()
|
||||
.uri("/index.html")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().valueEquals(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingPngImageFromImgLocation_thenReturnThisFileAndImagePngContentType() throws Exception {
|
||||
client.get()
|
||||
.uri("/img/example-image.png")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().valueEquals(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingMissingStaticResource_thenReturnNotFoundStatus() throws Exception {
|
||||
client.get()
|
||||
.uri("/unknown.png")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.baeldung.stepverifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class PostExecutionUnitTest {
|
||||
|
||||
Flux<Integer> source = Flux.<Integer>create(emitter -> {
|
||||
emitter.next(1);
|
||||
emitter.next(2);
|
||||
emitter.next(3);
|
||||
emitter.complete();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
emitter.next(4);
|
||||
}).filter(number -> number % 2 == 0);
|
||||
|
||||
@Test
|
||||
public void droppedElements() {
|
||||
StepVerifier.create(source)
|
||||
.expectNext(2)
|
||||
.expectComplete()
|
||||
.verifyThenAssertThat()
|
||||
.hasDropped(4)
|
||||
.tookLessThan(Duration.ofMillis(1500));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.baeldung.stepverifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
public class StepByStepUnitTest {
|
||||
|
||||
Flux<String> source = Flux.just("John", "Monica", "Mark", "Cloe", "Frank", "Casper", "Olivia", "Emily", "Cate")
|
||||
.filter(name -> name.length() == 4)
|
||||
.map(String::toUpperCase);
|
||||
|
||||
@Test
|
||||
public void shouldReturnForLettersUpperCaseStrings() {
|
||||
StepVerifier
|
||||
.create(source)
|
||||
.expectNext("JOHN")
|
||||
.expectNextMatches(name -> name.startsWith("MA"))
|
||||
.expectNext("CLOE", "CATE")
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldThrowExceptionAfterFourElements() {
|
||||
Flux<String> error = source.concatWith(
|
||||
Mono.error(new IllegalArgumentException("Our message"))
|
||||
);
|
||||
|
||||
StepVerifier
|
||||
.create(error)
|
||||
.expectNextCount(4)
|
||||
.expectErrorMatches(throwable -> throwable instanceof IllegalArgumentException &&
|
||||
throwable.getMessage().equals("Our message")
|
||||
).verify();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.baeldung.stepverifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.TestPublisher;
|
||||
|
||||
public class TestingTestPublisherUnitTest {
|
||||
|
||||
@Test
|
||||
public void testPublisher() {
|
||||
TestPublisher
|
||||
.<String>create()
|
||||
.next("First", "Second", "Third")
|
||||
.error(new RuntimeException("Message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonCompliant() {
|
||||
TestPublisher
|
||||
.createNoncompliant(TestPublisher.Violation.ALLOW_NULL)
|
||||
.emit("1", "2", null, "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPublisherInAction() {
|
||||
final TestPublisher<String> testPublisher = TestPublisher.create();
|
||||
|
||||
UppercaseConverter uppercaseConverter = new UppercaseConverter(testPublisher.flux());
|
||||
|
||||
StepVerifier.create(uppercaseConverter.getUpperCase())
|
||||
.then(() -> testPublisher.emit("aA", "bb", "ccc"))
|
||||
.expectNext("AA", "BB", "CCC")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UppercaseConverter {
|
||||
private final Flux<String> source;
|
||||
|
||||
UppercaseConverter(Flux<String> source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
Flux<String> getUpperCase() {
|
||||
return source
|
||||
.map(String::toUpperCase);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baeldung.stepverifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class TimeBasedUnitTest {
|
||||
|
||||
@Test
|
||||
public void simpleExample() {
|
||||
StepVerifier
|
||||
.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(1)).take(2))
|
||||
.expectSubscription()
|
||||
.expectNoEvent(Duration.ofSeconds(1))
|
||||
.expectNext(0L)
|
||||
.thenAwait(Duration.ofSeconds(1))
|
||||
.expectNext(1L)
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.baeldung.validations.functional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
|
||||
|
||||
import com.baeldung.validations.functional.model.AnnotatedRequestEntity;
|
||||
import com.baeldung.validations.functional.model.CustomRequestEntity;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
|
||||
public class FunctionalEndpointValidationsLiveTest {
|
||||
|
||||
private static final String BASE_URL = "http://localhost:8080";
|
||||
private static final String COMPLEX_EP_URL = BASE_URL + "/complex-handler-functional-validation";
|
||||
private static final String DRY_EP_URL = BASE_URL + "/dry-functional-validation";
|
||||
private static final String ANNOTATIONS_EP_URL = BASE_URL + "/annotated-functional-validation";
|
||||
|
||||
private static WebTestClient client;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
client = WebTestClient.bindToServer()
|
||||
.baseUrl(BASE_URL)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingDryEPWithInvalidBody_thenObtainBadRequest() {
|
||||
CustomRequestEntity body = new CustomRequestEntity("name", "123");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(DRY_EP_URL)
|
||||
.body(Mono.just(body), CustomRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingComplexEPWithInvalidBody_thenObtainBadRequest() {
|
||||
CustomRequestEntity body = new CustomRequestEntity("name", "123");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(COMPLEX_EP_URL)
|
||||
.body(Mono.just(body), CustomRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingAnnotatedEPWithInvalidBody_thenObtainBadRequest() {
|
||||
AnnotatedRequestEntity body = new AnnotatedRequestEntity("user", "passwordlongerthan7digits");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(ANNOTATIONS_EP_URL)
|
||||
.body(Mono.just(body), AnnotatedRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingDryEPWithValidBody_thenObtainBadRequest() {
|
||||
CustomRequestEntity body = new CustomRequestEntity("name", "1234567");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(DRY_EP_URL)
|
||||
.body(Mono.just(body), CustomRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingComplexEPWithValidBody_thenObtainBadRequest() {
|
||||
CustomRequestEntity body = new CustomRequestEntity("name", "1234567");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(COMPLEX_EP_URL)
|
||||
.body(Mono.just(body), CustomRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRequestingAnnotatedEPWithValidBody_thenObtainBadRequest() {
|
||||
AnnotatedRequestEntity body = new AnnotatedRequestEntity("user", "12345");
|
||||
|
||||
ResponseSpec response = client.post()
|
||||
.uri(ANNOTATIONS_EP_URL)
|
||||
.body(Mono.just(body), AnnotatedRequestEntity.class)
|
||||
.exchange();
|
||||
|
||||
response.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Baeldung: Static Content in Spring WebFlux</title>
|
||||
</head>
|
||||
<body>
|
||||
Example HTML file
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="15 seconds" debug="false">
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Baeldung: Static Content in Spring WebFlux</title>
|
||||
</head>
|
||||
<body>
|
||||
Example HTML file
|
||||
</body>
|
||||
</html>
|
||||
12
spring-reactive-modules/spring-5-reactive-3/.gitignore
vendored
Normal file
12
spring-reactive-modules/spring-5-reactive-3/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#folders#
|
||||
.idea
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
7
spring-reactive-modules/spring-5-reactive-3/README.md
Normal file
7
spring-reactive-modules/spring-5-reactive-3/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Spring 5 Reactive Project
|
||||
|
||||
This module contains articles about reactive Spring 5.
|
||||
|
||||
- [Logging a Reactive Sequence](https://www.baeldung.com/spring-reactive-sequence-logging)
|
||||
- [Reading Flux Into a Single InputStream Using Spring Reactive WebClient](https://www.baeldung.com/spring-reactive-read-flux-into-inputstream)
|
||||
- More articles: [[<-- prev]](../spring-5-reactive-2)
|
||||
44
spring-reactive-modules/spring-5-reactive-3/pom.xml
Normal file
44
spring-reactive-modules/spring-5-reactive-3/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-5-reactive-3</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-5-reactive-3</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>spring 5 sample project about new features</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectreactor</groupId>
|
||||
<artifactId>reactor-spring</artifactId>
|
||||
<version>${reactor-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<reactor-spring.version>1.0.1.RELEASE</reactor-spring.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.baeldung.databuffer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
|
||||
public class DataBufferToInputStream {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DataBufferToInputStream.class);
|
||||
private static final String REQUEST_ENDPOINT = "https://gorest.co.in/public/v2/users";
|
||||
|
||||
private static WebClient getWebClient() {
|
||||
WebClient.Builder webClientBuilder = WebClient.builder();
|
||||
return webClientBuilder.build();
|
||||
}
|
||||
|
||||
public static InputStream getResponseAsInputStream(WebClient client, String url) throws IOException, InterruptedException {
|
||||
|
||||
PipedOutputStream pipedOutputStream = new PipedOutputStream();
|
||||
PipedInputStream pipedInputStream = new PipedInputStream(1024 * 10);
|
||||
pipedInputStream.connect(pipedOutputStream);
|
||||
|
||||
Flux<DataBuffer> body = client.get()
|
||||
.uri(url)
|
||||
.exchangeToFlux(clientResponse -> {
|
||||
return clientResponse.body(BodyExtractors.toDataBuffers());
|
||||
})
|
||||
.doOnError(error -> {
|
||||
logger.error("error occurred while reading body", error);
|
||||
})
|
||||
.doFinally(s -> {
|
||||
try {
|
||||
pipedOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.doOnCancel(() -> {
|
||||
logger.error("Get request is cancelled");
|
||||
});
|
||||
|
||||
DataBufferUtils.write(body, pipedOutputStream)
|
||||
.log("Writing to output buffer")
|
||||
.subscribe();
|
||||
return pipedInputStream;
|
||||
}
|
||||
|
||||
private static String readContentFromPipedInputStream(PipedInputStream stream) throws IOException {
|
||||
StringBuffer contentStringBuffer = new StringBuffer();
|
||||
try {
|
||||
Thread pipeReader = new Thread(() -> {
|
||||
try {
|
||||
contentStringBuffer.append(readContent(stream));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
pipeReader.start();
|
||||
pipeReader.join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
return String.valueOf(contentStringBuffer);
|
||||
}
|
||||
|
||||
private static String readContent(InputStream stream) throws IOException {
|
||||
StringBuffer contentStringBuffer = new StringBuffer();
|
||||
byte[] tmp = new byte[stream.available()];
|
||||
int byteCount = stream.read(tmp, 0, tmp.length);
|
||||
logger.info(String.format("read %d bytes from the stream\n", byteCount));
|
||||
contentStringBuffer.append(new String(tmp));
|
||||
return String.valueOf(contentStringBuffer);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
WebClient webClient = getWebClient();
|
||||
InputStream inputStream = getResponseAsInputStream(webClient, REQUEST_ENDPOINT);
|
||||
Thread.sleep(3000);
|
||||
String content = readContentFromPipedInputStream((PipedInputStream) inputStream);
|
||||
logger.info("response content: \n{}", content.replace("}", "}\n"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.baeldung.webflux.logging;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public class WebFluxLoggingExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Flux<Integer> reactiveStream = Flux.range(1, 5).log();
|
||||
|
||||
reactiveStream.subscribe();
|
||||
|
||||
reactiveStream = Flux.range(1, 5).log().take(3);
|
||||
|
||||
reactiveStream.subscribe();
|
||||
|
||||
reactiveStream = Flux.range(1, 5).take(3).log();
|
||||
|
||||
reactiveStream.subscribe();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# application properties
|
||||
@@ -0,0 +1,77 @@
|
||||
package databuffer;
|
||||
|
||||
import com.baeldung.databuffer.DataBufferToInputStream;
|
||||
|
||||
import io.restassured.internal.util.IOUtils;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
class DataBufferToInputStreamUnitTest {
|
||||
private String getResponseStub() throws IOException {
|
||||
InputStream inputStream = null;
|
||||
BufferedReader reader = null;
|
||||
String content = null;
|
||||
try {
|
||||
inputStream = this.getClass()
|
||||
.getClassLoader()
|
||||
.getResourceAsStream("user-response.json");
|
||||
if (inputStream != null) {
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
content = reader.lines()
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("exception caught while getting response stub");
|
||||
} finally {
|
||||
reader.close();
|
||||
inputStream.close();
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private InputStream getResponseStubAsInputStream() {
|
||||
return this.getClass()
|
||||
.getClassLoader()
|
||||
.getResourceAsStream("user-response.json");
|
||||
}
|
||||
|
||||
private WebClient getMockWebClient() throws IOException {
|
||||
String content = getResponseStub();
|
||||
ClientResponse clientResponse = ClientResponse.create(HttpStatus.OK)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(content)
|
||||
.build();
|
||||
|
||||
ExchangeFunction exchangeFunction = clientRequest -> Mono.just(clientResponse);
|
||||
|
||||
WebClient.Builder webClientBuilder = WebClient.builder()
|
||||
.exchangeFunction(exchangeFunction);
|
||||
WebClient webClient = webClientBuilder.build();
|
||||
return webClient;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseAsInputStream() throws IOException, InterruptedException {
|
||||
String mockUrl = Mockito.anyString();
|
||||
WebClient mockWebClient = getMockWebClient();
|
||||
InputStream inputStream = DataBufferToInputStream.getResponseAsInputStream(mockWebClient, mockUrl);
|
||||
byte[] expectedBytes = IOUtils.toByteArray(getResponseStubAsInputStream());
|
||||
byte[] actualBytes = IOUtils.toByteArray(inputStream);
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml" />
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,72 @@
|
||||
[
|
||||
{
|
||||
"id": 2683,
|
||||
"name": "Maheswar Kocchar",
|
||||
"email": "maheswar_kocchar@kihn.info",
|
||||
"gender": "male",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2680,
|
||||
"name": "Lakshminath Khan",
|
||||
"email": "lakshminath_khan@barrows-cormier.biz",
|
||||
"gender": "female",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 2679,
|
||||
"name": "Tarun Arora",
|
||||
"email": "tarun_arora@rolfson.net",
|
||||
"gender": "female",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 2678,
|
||||
"name": "Agnivesh Dubashi",
|
||||
"email": "dubashi_agnivesh@senger.name",
|
||||
"gender": "male",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 2677,
|
||||
"name": "Dhanu Gowda",
|
||||
"email": "gowda_dhanu@hayes.org",
|
||||
"gender": "male",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2675,
|
||||
"name": "Harinakshi Pilla Jr.",
|
||||
"email": "pilla_jr_harinakshi@rutherford-monahan.com",
|
||||
"gender": "female",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 2673,
|
||||
"name": "Kalpana Prajapat",
|
||||
"email": "prajapat_kalpana@wilkinson-schaefer.net",
|
||||
"gender": "female",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2672,
|
||||
"name": "Chakradhar Jha",
|
||||
"email": "jha_chakradhar@baumbach.info",
|
||||
"gender": "male",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2670,
|
||||
"name": "Divaakar Deshpande Jr.",
|
||||
"email": "deshpande_jr_divaakar@mertz.info",
|
||||
"gender": "female",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 2669,
|
||||
"name": "Prasanna Mehra",
|
||||
"email": "prasanna_mehra@ruecker-larkin.name",
|
||||
"gender": "female",
|
||||
"status": "active"
|
||||
}
|
||||
]
|
||||
12
spring-reactive-modules/spring-5-reactive-client/.gitignore
vendored
Normal file
12
spring-reactive-modules/spring-5-reactive-client/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#folders#
|
||||
.idea
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
15
spring-reactive-modules/spring-5-reactive-client/README.md
Normal file
15
spring-reactive-modules/spring-5-reactive-client/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Spring REST Example Project
|
||||
|
||||
This module contains articles about reactive Spring 5 WebClient
|
||||
|
||||
### The Course
|
||||
The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||
|
||||
### Relevant Articles
|
||||
- [Logging Spring WebClient Calls](https://www.baeldung.com/spring-log-webclient-calls)
|
||||
- [Simultaneous Spring WebClient Calls](https://www.baeldung.com/spring-webclient-simultaneous-calls)
|
||||
- [Mocking a WebClient in Spring](https://www.baeldung.com/spring-mocking-webclient)
|
||||
- [Spring WebClient Filters](https://www.baeldung.com/spring-webclient-filters)
|
||||
- [Get List of JSON Objects with WebClient](https://www.baeldung.com/spring-webclient-json-list)
|
||||
- [Upload a File with WebClient](https://www.baeldung.com/spring-webclient-upload-file)
|
||||
- [How to Get Response Body When Testing the Status Code in WebFlux WebClient](https://www.baeldung.com/spring-webclient-get-response-body)
|
||||
196
spring-reactive-modules/spring-5-reactive-client/pom.xml
Normal file
196
spring-reactive-modules/spring-5-reactive-client/pom.xml
Normal file
@@ -0,0 +1,196 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-5-reactive-client</artifactId>
|
||||
<name>spring-5-reactive-client</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>spring 5 sample project about new features</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectreactor</groupId>
|
||||
<artifactId>reactor-spring</artifactId>
|
||||
<version>${reactor-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.json.bind</groupId>
|
||||
<artifactId>javax.json.bind-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-json_1.1_spec</artifactId>
|
||||
<version>${geronimo-json_1.1_spec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.johnzon</groupId>
|
||||
<artifactId>johnzon-jsonb</artifactId>
|
||||
</dependency>
|
||||
<!-- utils -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<!-- okhttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>${okhttp.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>${okhttp.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- runtime and test scoped -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-standalone</artifactId>
|
||||
<version>${wiremock-standalone.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons-collections4.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<version>${reactor-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-reactive-httpclient</artifactId>
|
||||
<version>${jetty-reactive-httpclient.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.baeldung.Spring5Application</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>integration-lite-first</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<logback.configurationFile>${project.basedir}/src/test/resources/logback-test.xml</logback.configurationFile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>integration-lite-second</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<logback.configurationFile>${project.basedir}/src/test/resources/logback-test.xml</logback.configurationFile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<properties>
|
||||
<reactor-spring.version>1.0.1.RELEASE</reactor-spring.version>
|
||||
<johnzon.version>1.1.3</johnzon.version>
|
||||
<jsonb-api.version>1.0</jsonb-api.version>
|
||||
<geronimo-json_1.1_spec.version>1.0</geronimo-json_1.1_spec.version>
|
||||
<jetty-reactive-httpclient.version>1.1.6</jetty-reactive-httpclient.version>
|
||||
<okhttp.version>4.0.1</okhttp.version>
|
||||
<reactor-test.version>3.2.10.RELEASE</reactor-test.version>
|
||||
<wiremock-standalone.version>2.26.0</wiremock-standalone.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.baeldung.reactive.controller;
|
||||
|
||||
|
||||
import com.baeldung.reactive.service.ReactiveUploadService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
@RestController
|
||||
public class UploadController {
|
||||
final ReactiveUploadService uploadService;
|
||||
|
||||
public UploadController(ReactiveUploadService uploadService) {
|
||||
this.uploadService = uploadService;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/upload")
|
||||
@ResponseBody
|
||||
public Mono<HttpStatus> uploadPdf(@RequestParam("file") final MultipartFile multipartFile) {
|
||||
return uploadService.uploadPdf(multipartFile.getResource());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/upload/multipart")
|
||||
@ResponseBody
|
||||
public Mono<HttpStatus> uploadMultipart(@RequestParam("file") final MultipartFile multipartFile) {
|
||||
return uploadService.uploadMultipart(multipartFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.baeldung.reactive.enums;
|
||||
|
||||
public enum Role {
|
||||
ENGINEER, SENIOR_ENGINEER, LEAD_ENGINEER
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.baeldung.reactive.exception;
|
||||
|
||||
public class ServiceException extends RuntimeException{
|
||||
|
||||
public ServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.baeldung.reactive.model;
|
||||
|
||||
|
||||
import com.baeldung.reactive.enums.Role;
|
||||
|
||||
public class Employee {
|
||||
private Integer employeeId;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private Integer age;
|
||||
private Role role;
|
||||
|
||||
public Employee() {
|
||||
}
|
||||
|
||||
public Employee(Integer employeeId, String firstName, String lastName, Integer age, Role role) {
|
||||
this.employeeId = employeeId;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Integer getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public void setEmployeeId(Integer employeeId) {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.baeldung.reactive.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class Foo {
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.baeldung.reactive.service;
|
||||
import com.baeldung.reactive.model.Employee;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class EmployeeService {
|
||||
|
||||
private WebClient webClient;
|
||||
public static String PATH_PARAM_BY_ID = "/employee/{id}";
|
||||
public static String ADD_EMPLOYEE = "/employee";
|
||||
|
||||
public EmployeeService(WebClient webClient) {
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
public EmployeeService(String baseUrl) {
|
||||
this.webClient = WebClient.create(baseUrl);
|
||||
}
|
||||
|
||||
public Mono<Employee> getEmployeeById(Integer employeeId) {
|
||||
return webClient
|
||||
.get()
|
||||
.uri(PATH_PARAM_BY_ID, employeeId)
|
||||
.retrieve()
|
||||
.bodyToMono(Employee.class);
|
||||
}
|
||||
|
||||
public Mono<Employee> addNewEmployee(Employee newEmployee) {
|
||||
|
||||
return webClient
|
||||
.post()
|
||||
.uri(ADD_EMPLOYEE)
|
||||
.syncBody(newEmployee)
|
||||
.retrieve().
|
||||
bodyToMono(Employee.class);
|
||||
}
|
||||
|
||||
public Mono<Employee> updateEmployee(Integer employeeId, Employee updateEmployee) {
|
||||
|
||||
return webClient
|
||||
.put()
|
||||
.uri(PATH_PARAM_BY_ID,employeeId)
|
||||
.syncBody(updateEmployee)
|
||||
.retrieve()
|
||||
.bodyToMono(Employee.class);
|
||||
}
|
||||
|
||||
public Mono<String> deleteEmployeeById(Integer employeeId) {
|
||||
return webClient
|
||||
.delete()
|
||||
.uri(PATH_PARAM_BY_ID,employeeId)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.baeldung.reactive.service;
|
||||
|
||||
|
||||
import com.baeldung.reactive.exception.ServiceException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@Service
|
||||
public class ReactiveUploadService {
|
||||
|
||||
private final WebClient webClient;
|
||||
private static final String EXTERNAL_UPLOAD_URL = "http://localhost:8080/external/upload";
|
||||
|
||||
public ReactiveUploadService(final WebClient webClient) {
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
|
||||
public Mono<HttpStatus> uploadPdf(final Resource resource) {
|
||||
|
||||
final URI url = UriComponentsBuilder.fromHttpUrl(EXTERNAL_UPLOAD_URL).build().toUri();
|
||||
Mono<HttpStatus> httpStatusMono = webClient.post()
|
||||
.uri(url)
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(BodyInserters.fromResource(resource))
|
||||
.exchangeToMono(response -> {
|
||||
if (response.statusCode().equals(HttpStatus.OK)) {
|
||||
return response.bodyToMono(HttpStatus.class).thenReturn(response.statusCode());
|
||||
} else {
|
||||
throw new ServiceException("Error uploading file");
|
||||
}
|
||||
});
|
||||
return httpStatusMono;
|
||||
}
|
||||
|
||||
|
||||
public Mono<HttpStatus> uploadMultipart(final MultipartFile multipartFile) {
|
||||
final URI url = UriComponentsBuilder.fromHttpUrl(EXTERNAL_UPLOAD_URL).build().toUri();
|
||||
|
||||
final MultipartBodyBuilder builder = new MultipartBodyBuilder();
|
||||
builder.part("file", multipartFile.getResource());
|
||||
|
||||
Mono<HttpStatus> httpStatusMono = webClient.post()
|
||||
.uri(url)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.body(BodyInserters.fromMultipartData(builder.build()))
|
||||
.exchangeToMono(response -> {
|
||||
if (response.statusCode().equals(HttpStatus.OK)) {
|
||||
return response.bodyToMono(HttpStatus.class).thenReturn(response.statusCode());
|
||||
} else {
|
||||
throw new ServiceException("Error uploading file");
|
||||
}
|
||||
});
|
||||
return httpStatusMono;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.baeldung.reactive.webclient.simultaneous;
|
||||
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class Client {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(Client.class.getName());
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
public Client(String uri) {
|
||||
this.webClient = WebClient.create(uri);
|
||||
}
|
||||
|
||||
public Mono<User> getUser(int id) {
|
||||
return webClient.get()
|
||||
.uri("/user/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(User.class);
|
||||
}
|
||||
|
||||
public Mono<Item> getItem(int id) {
|
||||
return webClient.get()
|
||||
.uri("/item/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(Item.class);
|
||||
}
|
||||
|
||||
public Mono<User> getOtherUser(int id) {
|
||||
return webClient.get()
|
||||
.uri("/otheruser/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(User.class);
|
||||
}
|
||||
|
||||
public Flux<User> fetchUsers(List<Integer> userIds) {
|
||||
return Flux.fromIterable(userIds)
|
||||
.flatMap(this::getUser);
|
||||
}
|
||||
|
||||
public Flux<User> fetchUserAndOtherUser(int id) {
|
||||
return Flux.merge(getUser(id), getOtherUser(id));
|
||||
}
|
||||
|
||||
public Mono<UserWithItem> fetchUserAndItem(int userId, int itemId) {
|
||||
Mono<User> user = getUser(userId);
|
||||
Mono<Item> item = getItem(itemId);
|
||||
|
||||
return Mono.zip(user, item, UserWithItem::new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.reactive.webclient.simultaneous;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class Item {
|
||||
private int id;
|
||||
|
||||
@JsonCreator
|
||||
public Item(@JsonProperty("id") int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.reactive.webclient.simultaneous;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class User {
|
||||
private int id;
|
||||
|
||||
@JsonCreator
|
||||
public User(@JsonProperty("id") int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baeldung.reactive.webclient.simultaneous;
|
||||
|
||||
public class UserWithItem {
|
||||
private User user;
|
||||
private Item item;
|
||||
|
||||
public UserWithItem(User user, Item item) {
|
||||
this.user = user;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public User user() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public Item item() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.baeldung.webclient.filter;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
|
||||
public class WebClientFilters {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebClientFilters.class);
|
||||
|
||||
public static ExchangeFilterFunction demoFilter() {
|
||||
ExchangeFilterFunction filterFunction = (clientRequest, nextFilter) -> {
|
||||
LOG.info("WebClient fitler executed");
|
||||
return nextFilter.exchange(clientRequest);
|
||||
};
|
||||
return filterFunction;
|
||||
}
|
||||
|
||||
public static ExchangeFilterFunction countingFilter(AtomicInteger getCounter) {
|
||||
ExchangeFilterFunction countingFilter = (clientRequest, nextFilter) -> {
|
||||
HttpMethod httpMethod = clientRequest.method();
|
||||
if (httpMethod == HttpMethod.GET) {
|
||||
getCounter.incrementAndGet();
|
||||
}
|
||||
return nextFilter.exchange(clientRequest);
|
||||
};
|
||||
return countingFilter;
|
||||
}
|
||||
|
||||
public static ExchangeFilterFunction urlModifyingFilter(String version) {
|
||||
ExchangeFilterFunction urlModifyingFilter = (clientRequest, nextFilter) -> {
|
||||
String oldUrl = clientRequest.url()
|
||||
.toString();
|
||||
URI newUrl = URI.create(oldUrl + "/" + version);
|
||||
ClientRequest filteredRequest = ClientRequest.from(clientRequest)
|
||||
.url(newUrl)
|
||||
.build();
|
||||
return nextFilter.exchange(filteredRequest);
|
||||
};
|
||||
return urlModifyingFilter;
|
||||
}
|
||||
|
||||
public static ExchangeFilterFunction loggingFilter(PrintStream printStream) {
|
||||
ExchangeFilterFunction loggingFilter = (clientRequest, nextFilter) -> {
|
||||
printStream.print("Sending request " + clientRequest.method() + " " + clientRequest.url());
|
||||
return nextFilter.exchange(clientRequest);
|
||||
};
|
||||
return loggingFilter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.baeldung.webclient.json;
|
||||
|
||||
import com.baeldung.webclient.json.model.Book;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ReaderConsumerService {
|
||||
|
||||
List<Book> processReaderDataFromObjectArray();
|
||||
|
||||
List<Book> processReaderDataFromReaderArray();
|
||||
|
||||
List<Book> processReaderDataFromReaderList();
|
||||
|
||||
List<String> processNestedReaderDataFromReaderArray();
|
||||
|
||||
List<String> processNestedReaderDataFromReaderList();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.baeldung.webclient.json;
|
||||
|
||||
import com.baeldung.webclient.json.model.Book;
|
||||
import com.baeldung.webclient.json.model.Reader;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ReaderConsumerServiceImpl implements ReaderConsumerService {
|
||||
|
||||
private final WebClient webClient;
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public ReaderConsumerServiceImpl(WebClient webClient) {
|
||||
this.webClient = webClient;
|
||||
}
|
||||
@Override
|
||||
public List<Book> processReaderDataFromObjectArray() {
|
||||
Mono<Object[]> response = webClient.get()
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(Object[].class).log();
|
||||
Object[] objects = response.block();
|
||||
return Arrays.stream(objects)
|
||||
.map(object -> mapper.convertValue(object, Reader.class))
|
||||
.map(Reader::getFavouriteBook)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> processReaderDataFromReaderArray() {
|
||||
Mono<Reader[]> response =
|
||||
webClient.get()
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(Reader[].class).log();
|
||||
|
||||
Reader[] readers = response.block();
|
||||
return Arrays.stream(readers)
|
||||
.map(Reader::getFavouriteBook)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> processReaderDataFromReaderList() {
|
||||
Mono<List<Reader>> response = webClient.get()
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<Reader>>() {});
|
||||
List<Reader> readers = response.block();
|
||||
|
||||
return readers.stream()
|
||||
.map(Reader::getFavouriteBook)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> processNestedReaderDataFromReaderArray() {
|
||||
Mono<Reader[]> response = webClient.get()
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(Reader[].class).log();
|
||||
Reader[] readers = response.block();
|
||||
|
||||
return Arrays.stream(readers)
|
||||
.flatMap(reader -> reader.getBooksRead().stream())
|
||||
.map(Book::getAuthor)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> processNestedReaderDataFromReaderList() {
|
||||
Mono<List<Reader>> response = webClient.get()
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<Reader>>() {});
|
||||
|
||||
List<Reader> readers = response.block();
|
||||
return readers.stream()
|
||||
.flatMap(reader -> reader.getBooksRead().stream())
|
||||
.map(Book::getAuthor)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baeldung.webclient.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class Book {
|
||||
private final String author;
|
||||
private final String title;
|
||||
|
||||
@JsonCreator
|
||||
public Book(
|
||||
@JsonProperty("author") String author,
|
||||
@JsonProperty("title") String title) {
|
||||
this.author = author;
|
||||
this.title = title;
|
||||
}
|
||||
public String getAuthor() {
|
||||
return this.author;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baeldung.webclient.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class Reader {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final Book favouriteBook;
|
||||
private final List<Book> booksRead;
|
||||
|
||||
@JsonCreator
|
||||
public Reader(
|
||||
@JsonProperty("id") int id,
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("favouriteBook") Book favouriteBook,
|
||||
@JsonProperty("booksRead") List<Book> booksRead) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.favouriteBook = favouriteBook;
|
||||
this.booksRead =booksRead;
|
||||
}
|
||||
|
||||
public Book getFavouriteBook() {
|
||||
return favouriteBook;
|
||||
}
|
||||
|
||||
public List<Book> getBooksRead() { return booksRead; }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.baeldung.webclient.status;
|
||||
|
||||
import com.baeldung.webclient.status.exception.CustomBadRequestException;
|
||||
import com.baeldung.webclient.status.exception.CustomServerErrorException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class WebClientStatusCodeHandler {
|
||||
|
||||
public static Mono<String> getResponseBodyUsingExchangeFilterFunction(String uri) {
|
||||
ExchangeFilterFunction errorResponseFilter = ExchangeFilterFunction
|
||||
.ofResponseProcessor(WebClientStatusCodeHandler::exchangeFilterResponseProcessor);
|
||||
return WebClient
|
||||
.builder()
|
||||
.filter(errorResponseFilter)
|
||||
.build()
|
||||
.post()
|
||||
.uri(uri)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
}
|
||||
|
||||
public static Mono<String> getResponseBodyUsingOnStatus(String uri) {
|
||||
return WebClient
|
||||
.builder()
|
||||
.build()
|
||||
.post()
|
||||
.uri(uri)
|
||||
.retrieve()
|
||||
.onStatus(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR::equals,
|
||||
response -> response.bodyToMono(String.class).map(CustomServerErrorException::new))
|
||||
.onStatus(
|
||||
HttpStatus.BAD_REQUEST::equals,
|
||||
response -> response.bodyToMono(String.class).map(CustomBadRequestException::new))
|
||||
.bodyToMono(String.class);
|
||||
}
|
||||
|
||||
private static Mono<ClientResponse> exchangeFilterResponseProcessor(ClientResponse response) {
|
||||
HttpStatus status = response.statusCode();
|
||||
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
|
||||
return response.bodyToMono(String.class)
|
||||
.flatMap(body -> Mono.error(new CustomServerErrorException(body)));
|
||||
}
|
||||
if (HttpStatus.BAD_REQUEST.equals(status)) {
|
||||
return response.bodyToMono(String.class)
|
||||
.flatMap(body -> Mono.error(new CustomBadRequestException(body)));
|
||||
}
|
||||
return Mono.just(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.webclient.status.exception;
|
||||
|
||||
public class CustomBadRequestException extends Exception {
|
||||
public CustomBadRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.webclient.status.exception;
|
||||
|
||||
public class CustomServerErrorException extends Exception {
|
||||
public CustomServerErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.baeldung.webclient.timeout;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.epoll.EpollChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.tcp.SslProvider;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@UtilityClass
|
||||
public class WebClientTimeoutProvider {
|
||||
|
||||
public static WebClient defaultWebClient() {
|
||||
HttpClient httpClient = HttpClient.create();
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient responseTimeoutClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofSeconds(1));
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient connectionTimeoutClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000);
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient connectionTimeoutWithKeepAliveClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
.option(EpollChannelOption.TCP_KEEPIDLE, 300)
|
||||
.option(EpollChannelOption.TCP_KEEPINTVL, 60)
|
||||
.option(EpollChannelOption.TCP_KEEPCNT, 8);
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient readWriteTimeoutClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.doOnConnected(conn -> conn
|
||||
.addHandler(new ReadTimeoutHandler(5, TimeUnit.SECONDS))
|
||||
.addHandler(new WriteTimeoutHandler(5)));
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient sslTimeoutClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.secure(spec -> spec
|
||||
.sslContext(SslContextBuilder.forClient())
|
||||
.defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
|
||||
.handshakeTimeout(Duration.ofSeconds(30))
|
||||
.closeNotifyFlushTimeout(Duration.ofSeconds(10))
|
||||
.closeNotifyReadTimeout(Duration.ofSeconds(10)));
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
public WebClient proxyTimeoutClient() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.proxy(spec -> spec
|
||||
.type(ProxyProvider.Proxy.HTTP)
|
||||
.host("http://proxy")
|
||||
.port(8080)
|
||||
.connectTimeoutMillis(3000));
|
||||
|
||||
return buildWebClient(httpClient);
|
||||
}
|
||||
|
||||
private WebClient buildWebClient(HttpClient httpClient) {
|
||||
return WebClient.builder()
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
logging.level.root=INFO
|
||||
|
||||
server.port=8081
|
||||
|
||||
logging.level.reactor.netty.http.client.HttpClient=DEBUG
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
# Pattern of log message for console appender
|
||||
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework" level="INFO" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
|
||||
<display-name>Spring Functional Application</display-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>functional</servlet-name>
|
||||
<servlet-class>com.baeldung.functional.RootServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
<async-supported>true</async-supported>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>functional</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import com.baeldung.reactive.Spring5ReactiveTestApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Spring5ReactiveTestApplication.class)
|
||||
public class SpringContextTest {
|
||||
|
||||
@Test
|
||||
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.baeldung.reactive;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import com.baeldung.reactive.model.Foo;
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class ReactiveIntegrationTest {
|
||||
|
||||
private WebClient client;
|
||||
private int singleRequestTime = 1000;
|
||||
private WireMockServer wireMockServer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
|
||||
wireMockServer.start();
|
||||
configureFor("localhost", wireMockServer.port());
|
||||
client = WebClient.create("http://localhost:" + wireMockServer.port());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
wireMockServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenMonoReactiveEndpointIsConsumed_thenCorrectOutput() {
|
||||
stubFor(get(urlEqualTo("/foo/123")).willReturn(aResponse().withFixedDelay(singleRequestTime)
|
||||
.withStatus(200)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"id\":123, \"name\":\"foo\"}")));
|
||||
|
||||
final Mono<ClientResponse> fooMono = client.get().uri("/foo/123").exchange().log();
|
||||
|
||||
System.out.println(fooMono.subscribe());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFluxReactiveEndpointIsConsumed_thenCorrectOutput() throws InterruptedException {
|
||||
stubFor(get(urlEqualTo("/foo")).willReturn(aResponse().withFixedDelay(singleRequestTime)
|
||||
.withStatus(200)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"id\":1, \"name\":\"foo\"}")));
|
||||
|
||||
client.get().uri("/foo")
|
||||
.retrieve()
|
||||
.bodyToFlux(Foo.class).log()
|
||||
.subscribe(System.out::println);
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baeldung.reactive;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import com.baeldung.reactive.model.Foo;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Spring5ReactiveTestApplication {
|
||||
|
||||
@Bean
|
||||
public WebClient client() {
|
||||
return WebClient.create("http://localhost:8080");
|
||||
}
|
||||
|
||||
@Bean
|
||||
CommandLineRunner cmd(WebClient client) {
|
||||
return args -> {
|
||||
client.get().uri("/foos2")
|
||||
.retrieve()
|
||||
.bodyToFlux(Foo.class).log()
|
||||
.subscribe(System.out::println);
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Spring5ReactiveTestApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.baeldung.reactive.logging;
|
||||
|
||||
import static com.baeldung.reactive.logging.jetty.RequestLogEnhancer.enhance;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import com.baeldung.reactive.logging.filters.LogFilters;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import ch.qos.logback.classic.spi.LoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.transport.logging.AdvancedByteBufFormat;
|
||||
|
||||
|
||||
public class WebClientLoggingIntegrationTest {
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
class Post {
|
||||
private String title;
|
||||
private String body;
|
||||
private int userId;
|
||||
}
|
||||
|
||||
private Appender jettyAppender;
|
||||
private Appender nettyAppender;
|
||||
private Appender mockAppender;
|
||||
private String sampleUrl = "https://jsonplaceholder.typicode.com/posts";
|
||||
|
||||
private Post post;
|
||||
private String sampleResponseBody;
|
||||
|
||||
@BeforeEach
|
||||
private void setup() throws Exception {
|
||||
|
||||
post = new Post("Learn WebClient logging with Baeldung!", "", 1);
|
||||
sampleResponseBody = new ObjectMapper().writeValueAsString(post);
|
||||
|
||||
ch.qos.logback.classic.Logger jetty = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.reactive.logging.jetty");
|
||||
jettyAppender = mock(Appender.class);
|
||||
when(jettyAppender.getName()).thenReturn("com.baeldung.reactive.logging.jetty");
|
||||
jetty.addAppender(jettyAppender);
|
||||
|
||||
ch.qos.logback.classic.Logger netty = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("reactor.netty.http.client");
|
||||
nettyAppender = mock(Appender.class);
|
||||
when(nettyAppender.getName()).thenReturn("reactor.netty.http.client");
|
||||
netty.addAppender(nettyAppender);
|
||||
|
||||
ch.qos.logback.classic.Logger test = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.reactive");
|
||||
mockAppender = mock(Appender.class);
|
||||
when(mockAppender.getName()).thenReturn("com.baeldung.reactive");
|
||||
test.addAppender(mockAppender);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenJettyHttpClient_whenEndpointIsConsumed_thenRequestAndResponseBodyLogged() {
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
org.eclipse.jetty.client.HttpClient httpClient = new org.eclipse.jetty.client.HttpClient(sslContextFactory) {
|
||||
@Override
|
||||
public Request newRequest(URI uri) {
|
||||
Request request = super.newRequest(uri);
|
||||
return enhance(request);
|
||||
}
|
||||
};
|
||||
|
||||
WebClient
|
||||
.builder()
|
||||
.clientConnector(new JettyClientHttpConnector(httpClient))
|
||||
.build()
|
||||
.post()
|
||||
.uri(sampleUrl)
|
||||
.body(BodyInserters.fromObject(post))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
|
||||
verify(jettyAppender).doAppend(argThat(argument -> (((LoggingEvent) argument).getFormattedMessage()).contains(sampleResponseBody)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNettyHttpClientWithWiretap_whenEndpointIsConsumed_thenRequestAndResponseBodyLogged() {
|
||||
|
||||
reactor.netty.http.client.HttpClient httpClient = HttpClient
|
||||
.create()
|
||||
.wiretap(true);
|
||||
WebClient
|
||||
.builder()
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build()
|
||||
.post()
|
||||
.uri(sampleUrl)
|
||||
.body(BodyInserters.fromObject(post))
|
||||
.exchange()
|
||||
.block();
|
||||
|
||||
verify(nettyAppender).doAppend(argThat(argument -> (((LoggingEvent) argument).getFormattedMessage()).contains("00000300")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNettyHttpClientWithCustomLogger_whenEndpointIsConsumed_thenRequestAndResponseBodyLogged() {
|
||||
reactor.netty.http.client.HttpClient httpClient = HttpClient.create()
|
||||
.wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL);
|
||||
|
||||
WebClient.builder()
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build()
|
||||
.post()
|
||||
.uri(sampleUrl)
|
||||
.body(BodyInserters.fromObject(post))
|
||||
.exchange()
|
||||
.block();
|
||||
|
||||
verify(nettyAppender).doAppend(argThat(argument -> (((LoggingEvent) argument).getFormattedMessage()).contains(sampleResponseBody)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenDefaultHttpClientWithFilter_whenEndpointIsConsumed_thenRequestAndResponseLogged() {
|
||||
WebClient
|
||||
.builder()
|
||||
.filters(exchangeFilterFunctions -> {
|
||||
exchangeFilterFunctions.addAll(LogFilters.prepareFilters());
|
||||
})
|
||||
.build()
|
||||
.post()
|
||||
.uri(sampleUrl)
|
||||
.body(BodyInserters.fromObject(post))
|
||||
.exchange()
|
||||
.block();
|
||||
|
||||
verify(mockAppender).doAppend(argThat(argument -> (((LoggingEvent) argument).getFormattedMessage()).contains(sampleUrl)));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.baeldung.reactive.logging.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Slf4j
|
||||
public class LogFilters {
|
||||
public static List<ExchangeFilterFunction> prepareFilters() {
|
||||
return Arrays.asList(logRequest(), logResponse());
|
||||
}
|
||||
|
||||
private static ExchangeFilterFunction logRequest() {
|
||||
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder("Request: \n")
|
||||
.append(clientRequest.method())
|
||||
.append(" ")
|
||||
.append(clientRequest.url());
|
||||
clientRequest
|
||||
.headers()
|
||||
.forEach((name, values) -> values.forEach(value -> sb
|
||||
.append("\n")
|
||||
.append(name)
|
||||
.append(":")
|
||||
.append(value)));
|
||||
log.debug(sb.toString());
|
||||
}
|
||||
return Mono.just(clientRequest);
|
||||
});
|
||||
}
|
||||
|
||||
private static ExchangeFilterFunction logResponse() {
|
||||
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
StringBuilder sb = new StringBuilder("Response: \n")
|
||||
.append("Status: ")
|
||||
.append(clientResponse.rawStatusCode());
|
||||
clientResponse
|
||||
.headers()
|
||||
.asHttpHeaders()
|
||||
.forEach((key, value1) -> value1.forEach(value -> sb
|
||||
.append("\n")
|
||||
.append(key)
|
||||
.append(":")
|
||||
.append(value)));
|
||||
log.debug(sb.toString());
|
||||
}
|
||||
return Mono.just(clientResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user