diff --git a/core-java-concurrency/src/main/java/com/baeldung/concurrent/stopping/ControlSubThread.java b/core-java-concurrency/src/main/java/com/baeldung/concurrent/stopping/ControlSubThread.java index 0e72821a88..e6522168bb 100644 --- a/core-java-concurrency/src/main/java/com/baeldung/concurrent/stopping/ControlSubThread.java +++ b/core-java-concurrency/src/main/java/com/baeldung/concurrent/stopping/ControlSubThread.java @@ -43,8 +43,9 @@ public class ControlSubThread implements Runnable { try { Thread.sleep(interval); } catch (InterruptedException e) { - // no-op, just loop again - } + Thread.currentThread().interrupt(); + System.out.println("Thread was interrupted, Failed to complete operation"); + } // do something } stopped.set(true); diff --git a/core-kotlin/pom.xml b/core-kotlin/pom.xml index b511f0dd7b..2cd5275eeb 100644 --- a/core-kotlin/pom.xml +++ b/core-kotlin/pom.xml @@ -44,6 +44,11 @@ kotlin-stdlib ${kotlin-stdlib.version} + + org.jetbrains.kotlin + kotlin-stdlib-jre8 + ${kotlin-stdlib.version} + org.jetbrains.kotlin kotlin-test-junit diff --git a/core-kotlin/src/test/kotlin/com/baeldung/kotlin/UseTest.kt b/core-kotlin/src/test/kotlin/com/baeldung/kotlin/UseTest.kt new file mode 100644 index 0000000000..15bdfcafd8 --- /dev/null +++ b/core-kotlin/src/test/kotlin/com/baeldung/kotlin/UseTest.kt @@ -0,0 +1,67 @@ +package com.baeldung.kotlin + +import org.junit.Test +import java.beans.ExceptionListener +import java.beans.XMLEncoder +import java.io.* +import java.lang.Exception +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +class UseTest { + + @Test + fun givenCloseable_whenUseIsCalled_thenItIsClosed() { + val stringWriter = StringWriter() + val writer = BufferedWriter(stringWriter) //Using a BufferedWriter because after close() it throws. + writer.use { + assertEquals(writer, it) + + it.write("something") + } + try { + writer.write("something else") + + fail("write() should have thrown an exception because the writer is closed.") + } catch (e: IOException) { + //Ok + } + + assertEquals("something", stringWriter.toString()) + } + + @Test + fun givenAutoCloseable_whenUseIsCalled_thenItIsClosed() { + val baos = ByteArrayOutputStream() + val encoder = XMLEncoder(PrintStream(baos)) //XMLEncoder is AutoCloseable but not Closeable. + //Here, we use a PrintStream because after close() it throws. + encoder.exceptionListener = ThrowingExceptionListener() + encoder.use { + assertEquals(encoder, it) + + it.writeObject("something") + } + try { + encoder.writeObject("something else") + encoder.flush() + + fail("write() should have thrown an exception because the encoder is closed.") + } catch (e: IOException) { + //Ok + } + } + + @Test + fun whenSimpleFormIsUsed_thenItWorks() { + StringWriter().use { it.write("something") } + } +} + +class ThrowingExceptionListener : ExceptionListener { + override fun exceptionThrown(e: Exception?) { + if(e != null) { + throw e + } + } +} \ No newline at end of file diff --git a/influxdb/src/test/java/com/baeldung/influxdb/InfluxDBConnectionLiveTest.java b/influxdb/src/test/java/com/baeldung/influxdb/InfluxDBConnectionLiveTest.java index 50d35b9b1c..6c7a03cb70 100644 --- a/influxdb/src/test/java/com/baeldung/influxdb/InfluxDBConnectionLiveTest.java +++ b/influxdb/src/test/java/com/baeldung/influxdb/InfluxDBConnectionLiveTest.java @@ -55,7 +55,7 @@ public class InfluxDBConnectionLiveTest { InfluxDB connection = connectDatabase(); - // Create "baeldung and check for it + // Create "baeldung" and check for it connection.createDatabase("baeldung"); assertTrue(connection.databaseExists("baeldung")); diff --git a/persistence-modules/redis/README.md b/persistence-modules/redis/README.md index d179b80c33..dd655ca7aa 100644 --- a/persistence-modules/redis/README.md +++ b/persistence-modules/redis/README.md @@ -1,3 +1,5 @@ ### Relevant Articles: - [Intro to Jedis – the Java Redis Client Library](http://www.baeldung.com/jedis-java-redis-client-library) - [A Guide to Redis with Redisson](http://www.baeldung.com/redis-redisson) +- [Intro to Lettuce – the Java Redis Client Library](http://www.baeldung.com/lettuce-java-redis-client-library) + diff --git a/persistence-modules/redis/pom.xml b/persistence-modules/redis/pom.xml index 4321b491eb..1f27faa09a 100644 --- a/persistence-modules/redis/pom.xml +++ b/persistence-modules/redis/pom.xml @@ -36,6 +36,13 @@ redisson 3.3.0 + + + io.lettuce + lettuce-core + 5.0.1.RELEASE + + diff --git a/persistence-modules/redis/src/test/java/com/baeldung/LettuceIntegrationLiveTest.java b/persistence-modules/redis/src/test/java/com/baeldung/LettuceIntegrationLiveTest.java new file mode 100644 index 0000000000..eb879d1d21 --- /dev/null +++ b/persistence-modules/redis/src/test/java/com/baeldung/LettuceIntegrationLiveTest.java @@ -0,0 +1,312 @@ +package com.baeldung; + +import io.lettuce.core.LettuceFutures; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.TransactionResult; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.pubsub.RedisPubSubListener; +import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; +import io.lettuce.core.pubsub.api.async.RedisPubSubAsyncCommands; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertTrue; + +public class LettuceIntegrationLiveTest { + + private static Logger log = LoggerFactory.getLogger(LettuceIntegrationLiveTest.class); + + private static StatefulRedisConnection redisConnection; + + private static RedisClient redisClient; + + @BeforeClass + public static void setUp() { + // Docker defaults to mapping redis port to 32768 + redisClient = RedisClient.create("redis://localhost:32768/"); + redisConnection = redisClient.connect(); + } + + @AfterClass + public static void destroy() { + redisConnection.close(); + } + + @Test + public void givenAString_thenSaveItAsRedisStringsSync() { + + RedisCommands syncCommands = redisConnection.sync(); + + String key = "key"; + String value = "value"; + + syncCommands.set(key, value); + String response = syncCommands.get(key); + + Assert.assertEquals(value, response); + } + + @Test + public void givenValues_thenSaveAsRedisHashSync() { + + RedisCommands syncCommands = redisConnection.sync(); + + String recordName = "record1"; + String name = "FirstName"; + String value = "John"; + String surname = "LastName"; + String value1 = "Smith"; + + syncCommands.hset(recordName, name, value); + syncCommands.hset(recordName, surname, value1); + Map record = syncCommands.hgetall(recordName); + + Assert.assertEquals(record.get(name), value); + Assert.assertEquals(record.get(surname), value1); + } + + @Test + public void givenAString_thenSaveItAsRedisStringsAsync() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + String key = "key"; + String value = "value"; + + asyncCommands.set(key, value); + RedisFuture redisFuture = asyncCommands.get(key); + + String response = redisFuture.get(); + + Assert.assertEquals(value, response); + } + + @Test + public void givenValues_thenSaveAsRedisHashAsync() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + String recordName = "record1"; + String name = "FirstName"; + String value = "John"; + String surname = "LastName"; + String value1 = "Smith"; + + asyncCommands.hset(recordName, name, value); + asyncCommands.hset(recordName, surname, value1); + RedisFuture> redisFuture = asyncCommands.hgetall(recordName); + + Map record = redisFuture.get(); + + Assert.assertEquals(record.get(name), value); + Assert.assertEquals(record.get(surname), value1); + } + + @Test + public void givenValues_thenSaveAsRedisListAsync() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + String listName = "tasks"; + String firstTask = "firstTask"; + String secondTask = "secondTask"; + + asyncCommands.del(listName); + + asyncCommands.lpush(listName, firstTask); + asyncCommands.lpush(listName, secondTask); + RedisFuture redisFuture = asyncCommands.rpop(listName); + + String nextTask = redisFuture.get(); + + Assert.assertEquals(firstTask, nextTask); + + asyncCommands.del(listName); + + asyncCommands.lpush(listName, firstTask); + asyncCommands.lpush(listName, secondTask); + + redisFuture = asyncCommands.lpop(listName); + + nextTask = redisFuture.get(); + + Assert.assertEquals(secondTask, nextTask); + + } + + @Test + public void givenSetElements_thenSaveThemInRedisSetAsync() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + String countries = "countries"; + + String countryOne = "Spain"; + String countryTwo = "Ireland"; + String countryThree = "Ireland"; + + asyncCommands.sadd(countries, countryOne); + + RedisFuture> countriesSetFuture = asyncCommands.smembers(countries); + Assert.assertEquals(2, countriesSetFuture.get().size()); + + asyncCommands.sadd(countries, countryTwo); + countriesSetFuture = asyncCommands.smembers(countries); + Assert.assertEquals(2, countriesSetFuture.get().size()); + + asyncCommands.sadd(countries, countryThree); + countriesSetFuture = asyncCommands.smembers(countries); + Assert.assertEquals(2, countriesSetFuture.get().size()); + + RedisFuture exists = asyncCommands.sismember(countries, countryThree); + assertTrue(exists.get()); + } + + @Test + public void givenARanking_thenSaveItInRedisSortedSetAsync() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + String key = "sortedset"; + + asyncCommands.zadd(key, 1, "one"); + asyncCommands.zadd(key, 4, "zero"); + asyncCommands.zadd(key, 2, "two"); + + RedisFuture> values = asyncCommands.zrevrange(key, 0, 3); + Assert.assertEquals("zero", values.get().get(0)); + + values = asyncCommands.zrange(key, 0, 3); + Assert.assertEquals("one", values.get().get(0)); + } + + @Test + public void givenMultipleOperationsThatNeedToBeExecutedAtomically_thenWrapThemInATransaction() throws Exception { + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + // Start a transaction + asyncCommands.multi(); + + // Add three sets to it, and save the future responses + RedisFuture result1 = asyncCommands.set("key1", "value1"); + RedisFuture result2 = asyncCommands.set("key2", "value2"); + RedisFuture result3 = asyncCommands.set("key3", "value3"); + + // Execute it + RedisFuture execResult = asyncCommands.exec(); + + TransactionResult transactionResult = execResult.get(); + + // Get the three results in the transaction return + String firstResult = transactionResult.get(0); + String secondResult = transactionResult.get(0); + String thirdResult = transactionResult.get(0); + + // Our results are in both! + assertTrue(firstResult.equals("OK")); + assertTrue(secondResult.equals("OK")); + assertTrue(thirdResult.equals("OK")); + + assertTrue(result1.get().equals("OK")); + assertTrue(result2.get().equals("OK")); + assertTrue(result3.get().equals("OK")); + } + + @Test + public void givenMultipleIndependentOperations_whenNetworkOptimizationIsImportant_thenFlushManually() throws Exception { + + int iterations = 50; + + RedisAsyncCommands asyncCommands = redisConnection.async(); + + asyncCommands.setAutoFlushCommands(false); + + List> futures = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + futures.add(asyncCommands.set("key" + i, "value" + i)); + } + + asyncCommands.flushCommands(); + + // Wait until all futures complete + boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS, futures.toArray(new RedisFuture[futures.size()])); + + asyncCommands.setAutoFlushCommands(true); + + } + + @Test + public void givenPubSubChannel_whenMessage_thenMessageReceived() throws Exception { + + Listener listener = new Listener(); + StatefulRedisPubSubConnection connection = redisClient.connectPubSub(); + StatefulRedisPubSubConnection pubconnection = redisClient.connectPubSub(); + connection.addListener(listener); + + RedisPubSubAsyncCommands async = connection.async(); + async.subscribe("channel"); + + RedisPubSubAsyncCommands pubasync = pubconnection.async(); + RedisFuture result = pubasync.publish("channel", "hithere"); + + // Need a long wait for publish to complete, depending on system. + result.get(15, TimeUnit.SECONDS); + assertTrue(listener.getMessage().equals("hithere")); + + } + + private static class Listener implements RedisPubSubListener { + + private String message; + + String getMessage() { + return message; + } + + @Override + public void message(String channel, String message) { + log.debug("Got {} on {}", message, channel); + this.message = message; + } + + @Override + public void message(String pattern, String channel, String message) { + + } + + @Override + public void subscribed(String channel, long count) { + log.debug("Subscribed to {}", channel); + } + + @Override + public void psubscribed(String pattern, long count) { + + } + + @Override + public void unsubscribed(String channel, long count) { + + } + + @Override + public void punsubscribed(String pattern, long count) { + + } + } + +} diff --git a/spring-5-security/README.md b/spring-5-security/README.md new file mode 100644 index 0000000000..1c9fad65e4 --- /dev/null +++ b/spring-5-security/README.md @@ -0,0 +1,3 @@ +## Relevant articles: + +- [Spring Security 5 -OAuth2 Login](http://www.baeldung.com/spring-security-5-oauth2-login) diff --git a/spring-mvc-xml/src/main/webapp/WEB-INF/web.xml b/spring-mvc-xml/src/main/webapp/WEB-INF/web.xml index 1ea3051426..6ff435b84b 100644 --- a/spring-mvc-xml/src/main/webapp/WEB-INF/web.xml +++ b/spring-mvc-xml/src/main/webapp/WEB-INF/web.xml @@ -1,10 +1,9 @@ - - Spring MVC XML Application @@ -65,4 +64,4 @@ /errors - \ No newline at end of file + diff --git a/spring-reactive-websocket/pom.xml b/spring-reactive-websocket/pom.xml new file mode 100644 index 0000000000..846cece177 --- /dev/null +++ b/spring-reactive-websocket/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + spring-reactive-websocket + 0.0.1-SNAPSHOT + jar + + spring-reactive-websocket + Reactive WebSockets with Spring 5 + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-integration + 2.0.0.M7 + + + org.springframework.boot + spring-boot-starter-webflux + 2.0.0.M7 + + + org.projectlombok + lombok + compile + RELEASE + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/spring-reactive-websocket/src/main/java/com/baeldung/Event.java b/spring-reactive-websocket/src/main/java/com/baeldung/Event.java new file mode 100644 index 0000000000..20d678c214 --- /dev/null +++ b/spring-reactive-websocket/src/main/java/com/baeldung/Event.java @@ -0,0 +1,11 @@ +package com.baeldung; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Event { + private String eventId; + private String eventDt; +} diff --git a/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketApplication.java b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketApplication.java new file mode 100644 index 0000000000..f8952d750d --- /dev/null +++ b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketApplication.java @@ -0,0 +1,38 @@ +package com.baeldung; + +import java.net.URI; +import java.time.Duration; + +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.socket.WebSocketMessage; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +import reactor.core.publisher.Mono; + +@SpringBootApplication +public class ReactiveWebSocketApplication { + public static void main(String[] args) { + SpringApplication.run(ReactiveWebSocketApplication.class, args); + } + + /** + * Spring Reactive WebSocket Client + * **/ + @Bean + CommandLineRunner runner() { + return run -> { + WebSocketClient client = new ReactorNettyWebSocketClient(); + client.execute(URI.create("ws://localhost:8080/event-emitter"), session -> session.send(Mono.just(session.textMessage("event-me-from-spring-reactive-client"))) + .thenMany(session.receive() + .map(WebSocketMessage::getPayloadAsText) + .log()) + .then()) + .block(); +// .block(Duration.ofSeconds(10L));//force timeout after given duration + }; + } +} diff --git a/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketConfiguration.java b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketConfiguration.java new file mode 100644 index 0000000000..6729e09273 --- /dev/null +++ b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketConfiguration.java @@ -0,0 +1,34 @@ +package com.baeldung; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; +import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class ReactiveWebSocketConfiguration { + + @Autowired + private WebSocketHandler webSocketHandler; + + @Bean + public HandlerMapping webSocketHandlerMapping() { + Map map = new HashMap<>(); + map.put("/event-emitter", webSocketHandler); + + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setOrder(1); + handlerMapping.setUrlMap(map); + return handlerMapping; + } + + @Bean + public WebSocketHandlerAdapter handlerAdapter() { + return new WebSocketHandlerAdapter(); + } +} \ No newline at end of file diff --git a/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketHandler.java b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketHandler.java new file mode 100644 index 0000000000..4a548322b3 --- /dev/null +++ b/spring-reactive-websocket/src/main/java/com/baeldung/ReactiveWebSocketHandler.java @@ -0,0 +1,71 @@ +package com.baeldung; + +import org.springframework.web.reactive.socket.WebSocketSession; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.WebSocketMessage; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.PostConstruct; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.UUID; + +@Component +public class ReactiveWebSocketHandler implements WebSocketHandler { + + private Flux eventFlux; + private Flux intervalFlux; + + /** + * Here we prepare a Flux that will emit a message every second + */ + @PostConstruct + private void init() throws InterruptedException { + + eventFlux = Flux.generate(e -> { + Event event = new Event(UUID.randomUUID() + .toString(), + LocalDateTime.now() + .toString()); + e.next(event); + }); + + intervalFlux = Flux.interval(Duration.ofMillis(1000L)) + .zipWith(eventFlux, (time, event) -> event); + + } + + /** + * On each new client session, send the message flux to the client. + * Spring subscribes to the flux and send every new flux event to the WebSocketSession object + * @param session + * @return Mono + */ + @Override + public Mono handle(WebSocketSession webSocketSession) { + ObjectMapper json = new ObjectMapper(); + return webSocketSession.send(intervalFlux.map(event -> { + try { + String jsonEvent = json.writeValueAsString(event); + System.out.println(jsonEvent); + return jsonEvent; + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + }) + .map(webSocketSession::textMessage)) + + .and(webSocketSession.receive() + .map(WebSocketMessage::getPayloadAsText) + .log()); + } + +} diff --git a/spring-reactive-websocket/src/main/resources/static/client-websocket.html b/spring-reactive-websocket/src/main/resources/static/client-websocket.html new file mode 100644 index 0000000000..3f840e8bd4 --- /dev/null +++ b/spring-reactive-websocket/src/main/resources/static/client-websocket.html @@ -0,0 +1,34 @@ + + + + +Baeldung: Spring 5 Reactive Client WebSocket (Browser) + + + +
+ + + \ No newline at end of file