diff --git a/aws-lambda/.gitignore b/aws-lambda/.gitignore new file mode 100644 index 0000000000..9984c2e554 --- /dev/null +++ b/aws-lambda/.gitignore @@ -0,0 +1 @@ +.aws-sam/ diff --git a/aws-lambda/README.md b/aws-lambda/README.md index 759c9dd506..0ae188fc97 100644 --- a/aws-lambda/README.md +++ b/aws-lambda/README.md @@ -6,3 +6,4 @@ This module contains articles about AWS Lambda - [Using AWS Lambda with API Gateway](https://www.baeldung.com/aws-lambda-api-gateway) - [Introduction to AWS Serverless Application Model](https://www.baeldung.com/aws-serverless) - [How to Implement Hibernate in an AWS Lambda Function in Java](https://www.baeldung.com/java-aws-lambda-hibernate) +- [Writing an Enterprise-Grade AWS Lambda in Java](https://www.baeldung.com/java-enterprise-aws-lambda) diff --git a/aws-lambda/pom.xml b/aws-lambda/pom.xml index 5dc275141d..8014a87126 100644 --- a/aws-lambda/pom.xml +++ b/aws-lambda/pom.xml @@ -17,6 +17,7 @@ lambda shipping-tracker/ShippingFunction + todo-reminder/ToDoFunction diff --git a/aws-lambda/todo-reminder/ToDoFunction/pom.xml b/aws-lambda/todo-reminder/ToDoFunction/pom.xml new file mode 100644 index 0000000000..f80cbdf22f --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/pom.xml @@ -0,0 +1,105 @@ + + 4.0.0 + helloworld + HelloWorld + 1.0 + jar + To Do Application Example. + + 1.8 + 1.8 + + + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 3.6.0 + + + uk.org.webcompere + lightweight-config + 1.1.0 + + + com.amazonaws + aws-lambda-java-log4j2 + 1.2.0 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.2 + + + io.github.openfeign + feign-core + 11.2 + + + io.github.openfeign + feign-slf4j + 11.2 + + + io.github.openfeign + feign-gson + 11.2 + + + com.google.inject + guice + 5.0.1 + + + junit + junit + 4.13.1 + test + + + uk.org.webcompere + system-stubs-junit4 + 1.2.0 + test + + + org.mockito + mockito-core + 3.3.0 + test + + + org.assertj + assertj-core + 3.19.0 + test + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + + package + + shade + + + + + + + diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java new file mode 100644 index 0000000000..b322dabca8 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/App.java @@ -0,0 +1,43 @@ +package com.baeldung.lambda.todo; + +import java.io.*; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.baeldung.lambda.todo.config.ExecutionContext; +import com.baeldung.lambda.todo.service.PostService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestStreamHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private String environmentName = System.getenv("ENV_NAME"); + private ExecutionContext executionContext = new ExecutionContext(); + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + context.getLogger().log("App starting\n"); + context.getLogger().log("Environment: " + + environmentName + "\n"); + + try { + PostService postService = executionContext.getPostService(); + executionContext.getToDoReaderService() + .getOldestToDo() + .ifPresent(postService::makePost); + } catch (Exception e) { + LOGGER.error("Failed: {}", e.getMessage(), e); + } + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java new file mode 100644 index 0000000000..0521dfe05c --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostApi.java @@ -0,0 +1,8 @@ +package com.baeldung.lambda.todo.api; + +import feign.RequestLine; + +public interface PostApi { + @RequestLine("POST /posts") + void makePost(PostItem item); +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java new file mode 100644 index 0000000000..96049e37d4 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/PostItem.java @@ -0,0 +1,40 @@ +package com.baeldung.lambda.todo.api; + +public class PostItem { + private String title; + private String body; + private int userId; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + @Override + public String toString() { + return "PostItem{" + + "title='" + title + '\'' + + ", body='" + body + '\'' + + ", userId=" + userId + + '}'; + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java new file mode 100644 index 0000000000..bf75cd566d --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoApi.java @@ -0,0 +1,10 @@ +package com.baeldung.lambda.todo.api; + +import feign.RequestLine; + +import java.util.List; + +public interface ToDoApi { + @RequestLine("GET /todos") + List getAllTodos(); +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java new file mode 100644 index 0000000000..d43b7a947c --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/api/ToDoItem.java @@ -0,0 +1,50 @@ +package com.baeldung.lambda.todo.api; + +public class ToDoItem { + private int userId; + private int id; + private String title; + private boolean completed; + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + @Override + public String toString() { + return "ToDoItem{" + + "userId=" + userId + + ", id=" + id + + ", title='" + title + '\'' + + ", completed=" + completed + + '}'; + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java new file mode 100644 index 0000000000..f919ac4006 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Config.java @@ -0,0 +1,50 @@ +package com.baeldung.lambda.todo.config; + +public class Config { + private String toDoEndpoint; + private String postEndpoint; + private String environmentName; + + private Credentials toDoCredentials; + private Credentials postCredentials; + + public String getToDoEndpoint() { + return toDoEndpoint; + } + + public void setToDoEndpoint(String toDoEndpoint) { + this.toDoEndpoint = toDoEndpoint; + } + + public String getPostEndpoint() { + return postEndpoint; + } + + public void setPostEndpoint(String postEndpoint) { + this.postEndpoint = postEndpoint; + } + + public String getEnvironmentName() { + return environmentName; + } + + public void setEnvironmentName(String environmentName) { + this.environmentName = environmentName; + } + + public Credentials getToDoCredentials() { + return toDoCredentials; + } + + public void setToDoCredentials(Credentials toDoCredentials) { + this.toDoCredentials = toDoCredentials; + } + + public Credentials getPostCredentials() { + return postCredentials; + } + + public void setPostCredentials(Credentials postCredentials) { + this.postCredentials = postCredentials; + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java new file mode 100644 index 0000000000..a11399381c --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Credentials.java @@ -0,0 +1,22 @@ +package com.baeldung.lambda.todo.config; + +public class Credentials { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java new file mode 100644 index 0000000000..b588187abd --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/ExecutionContext.java @@ -0,0 +1,36 @@ +package com.baeldung.lambda.todo.config; + +import com.baeldung.lambda.todo.service.PostService; +import com.baeldung.lambda.todo.service.ToDoReaderService; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExecutionContext { + private static final Logger LOGGER = + LoggerFactory.getLogger(ExecutionContext.class); + + private ToDoReaderService toDoReaderService; + private PostService postService; + + public ExecutionContext() { + LOGGER.info("Loading configuration"); + + try { + Injector injector = Guice.createInjector(new Services()); + this.toDoReaderService = injector.getInstance(ToDoReaderService.class); + this.postService = injector.getInstance(PostService.class); + } catch (Exception e) { + LOGGER.error("Could not start", e); + } + } + + public ToDoReaderService getToDoReaderService() { + return toDoReaderService; + } + + public PostService getPostService() { + return postService; + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java new file mode 100644 index 0000000000..c1d85012eb --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/config/Services.java @@ -0,0 +1,38 @@ +package com.baeldung.lambda.todo.config; + +import com.baeldung.lambda.todo.api.PostApi; +import com.baeldung.lambda.todo.api.ToDoApi; +import com.google.inject.AbstractModule; +import feign.Feign; +import feign.auth.BasicAuthRequestInterceptor; +import feign.gson.GsonDecoder; +import feign.gson.GsonEncoder; +import feign.slf4j.Slf4jLogger; +import uk.org.webcompere.lightweightconfig.ConfigLoader; + +import static feign.Logger.Level.FULL; + +public class Services extends AbstractModule { + @Override + protected void configure() { + Config config = ConfigLoader.loadYmlConfigFromResource("configuration.yml", Config.class); + + ToDoApi toDoApi = Feign.builder() + .decoder(new GsonDecoder()) + .logger(new Slf4jLogger()) + .logLevel(FULL) + .requestInterceptor(new BasicAuthRequestInterceptor(config.getToDoCredentials().getUsername(), config.getToDoCredentials().getPassword())) + .target(ToDoApi.class, config.getToDoEndpoint()); + + PostApi postApi = Feign.builder() + .encoder(new GsonEncoder()) + .logger(new Slf4jLogger()) + .logLevel(FULL) + .requestInterceptor(new BasicAuthRequestInterceptor(config.getPostCredentials().getUsername(), config.getPostCredentials().getPassword())) + .target(PostApi.class, config.getPostEndpoint()); + + bind(Config.class).toInstance(config); + bind(ToDoApi.class).toInstance(toDoApi); + bind(PostApi.class).toInstance(postApi); + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java new file mode 100644 index 0000000000..bb0a67dbd1 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/PostService.java @@ -0,0 +1,30 @@ +package com.baeldung.lambda.todo.service; + +import com.baeldung.lambda.todo.api.PostApi; +import com.baeldung.lambda.todo.api.PostItem; +import com.baeldung.lambda.todo.api.ToDoItem; +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +public class PostService { + private static final Logger LOGGER = LogManager.getLogger(PostService.class); + private PostApi postApi; + + @Inject + public PostService(PostApi postApi) { + this.postApi = postApi; + } + + public void makePost(ToDoItem toDoItem) { + LOGGER.info("Posting about: {}", toDoItem); + PostItem item = new PostItem(); + item.setTitle("To Do is Out Of Date: " + toDoItem.getId()); + item.setUserId(toDoItem.getUserId()); + item.setBody("Not done: " + toDoItem.getTitle()); + + LOGGER.info("Post: {}", item); + postApi.makePost(item); + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java new file mode 100644 index 0000000000..5cd18ff46a --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/java/com/baeldung/lambda/todo/service/ToDoReaderService.java @@ -0,0 +1,30 @@ +package com.baeldung.lambda.todo.service; + +import com.baeldung.lambda.todo.api.ToDoApi; +import com.baeldung.lambda.todo.api.ToDoItem; +import com.baeldung.lambda.todo.config.Config; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.util.Optional; + +public class ToDoReaderService { + // Log4j logger + private static final Logger LOGGER = LogManager.getLogger(ToDoReaderService.class); + + private ToDoApi toDoApi; + + @Inject + public ToDoReaderService(Config configuration, ToDoApi toDoApi) { + LOGGER.info("ToDo Endpoint on: {}", configuration.getToDoEndpoint()); + + this.toDoApi = toDoApi; + } + + public Optional getOldestToDo() { + return toDoApi.getAllTodos().stream() + .filter(item -> !item.isCompleted()) + .findFirst(); + } +} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml new file mode 100644 index 0000000000..f67239bc23 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/configuration.yml @@ -0,0 +1,9 @@ +toDoEndpoint: https://jsonplaceholder.typicode.com +postEndpoint: https://jsonplaceholder.typicode.com +environmentName: ${ENV_NAME} +toDoCredentials: + username: baeldung + password: ${TODO_PASSWORD:-password} +postCredentials: + username: baeldung + password: ${POST_PASSWORD:-password} diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b241e5d167 --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1} - %m%n + + + + + + + + + diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java new file mode 100644 index 0000000000..cbdc8c22cb --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/AppTest.java @@ -0,0 +1,58 @@ +package com.baeldung.lambda.todo; + +import com.amazonaws.services.lambda.runtime.Context; +import com.baeldung.lambda.todo.config.Config; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import uk.org.webcompere.lightweightconfig.ConfigLoader; +import uk.org.webcompere.systemstubs.rules.EnvironmentVariablesRule; +import uk.org.webcompere.systemstubs.stream.input.LinesAltStream; +import uk.org.webcompere.systemstubs.stream.output.NoopStream; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class AppTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mockContext; + + @Rule + public EnvironmentVariablesRule environmentVariablesRule = new EnvironmentVariablesRule(); + + private InputStream fakeInputStream = new LinesAltStream(); + private OutputStream fakeOutputStream = new NoopStream(); + + @Test + public void whenTheEnvironmentVariableIsSet_thenItIsLogged() throws Exception { + environmentVariablesRule.set("ENV_NAME", "unitTest"); + new App().handleRequest(fakeInputStream, fakeOutputStream, mockContext); + + verify(mockContext.getLogger()) + .log("Environment: unitTest\n"); + } + + @Test + public void givenEnvironmentVariableIsNotSet_thenUseDefault() { + String setting = Optional.ofNullable(System.getenv("SETTING")) + .orElse("default"); + + assertThat(setting).isEqualTo("default"); + } + + @Test + public void givenConfiguration_canLoadIntoPojo() { + environmentVariablesRule.set("ENV_NAME", "unitTest"); + Config config = ConfigLoader.loadYmlConfigFromResource("configuration.yml", Config.class); + assertThat(config.getEnvironmentName()).isEqualTo("unitTest"); + } +} \ No newline at end of file diff --git a/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java new file mode 100644 index 0000000000..634c5257ff --- /dev/null +++ b/aws-lambda/todo-reminder/ToDoFunction/src/test/java/com/baeldung/lambda/todo/service/ToDoReaderServiceTest.java @@ -0,0 +1,24 @@ +package com.baeldung.lambda.todo.service; + +import com.baeldung.lambda.todo.config.Config; +import org.junit.Rule; +import org.junit.Test; +import uk.org.webcompere.systemstubs.rules.SystemOutRule; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class ToDoReaderServiceTest { + + @Rule + public SystemOutRule systemOutRule = new SystemOutRule(); + + @Test + public void whenTheServiceStarts_thenItOutputsEndpoint() { + Config config = new Config(); + config.setToDoEndpoint("https://todo-endpoint.com"); + ToDoReaderService service = new ToDoReaderService(config, null); + + assertThat(systemOutRule.getLinesNormalized()) + .contains("ToDo Endpoint on: https://todo-endpoint.com"); + } +} \ No newline at end of file diff --git a/aws-lambda/todo-reminder/template.yaml b/aws-lambda/todo-reminder/template.yaml new file mode 100644 index 0000000000..f32ee392c5 --- /dev/null +++ b/aws-lambda/todo-reminder/template.yaml @@ -0,0 +1,22 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: todo-reminder application + +Parameters: + EnvironmentName: + Type: String + Default: dev + +Resources: + ToDoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + Timeout: 20 + CodeUri: ToDoFunction + Handler: com.baeldung.lambda.todo.App::handleRequest + Runtime: java8 + MemorySize: 512 + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + PARAM1: VALUE + ENV_NAME: !Ref EnvironmentName \ No newline at end of file diff --git a/core-java-modules/core-java-arrays-guides/README.md b/core-java-modules/core-java-arrays-guides/README.md index 7338ff9523..00bb6b53c8 100644 --- a/core-java-modules/core-java-arrays-guides/README.md +++ b/core-java-modules/core-java-arrays-guides/README.md @@ -8,3 +8,4 @@ This module contains complete guides about arrays in Java - [What is \[Ljava.lang.Object;?](https://www.baeldung.com/java-tostring-array) - [Guide to ArrayStoreException](https://www.baeldung.com/java-arraystoreexception) - [Creating a Generic Array in Java](https://www.baeldung.com/java-generic-array) +- [Maximum Size of Java Arrays](https://www.baeldung.com/java-arrays-max-size) diff --git a/core-java-modules/core-java-exceptions-3/README.md b/core-java-modules/core-java-exceptions-3/README.md index e1372381a8..f79eb41a8b 100644 --- a/core-java-modules/core-java-exceptions-3/README.md +++ b/core-java-modules/core-java-exceptions-3/README.md @@ -8,3 +8,4 @@ - [Localizing Exception Messages in Java](https://www.baeldung.com/java-localize-exception-messages) - [Explanation of ClassCastException in Java](https://www.baeldung.com/java-classcastexception) - [NoSuchFieldError in Java](https://www.baeldung.com/java-nosuchfielderror) +- [IllegalAccessError in Java](https://www.baeldung.com/java-illegalaccesserror) diff --git a/core-java-modules/core-java-networking-3/README.md b/core-java-modules/core-java-networking-3/README.md index 09470fe88c..730231525f 100644 --- a/core-java-modules/core-java-networking-3/README.md +++ b/core-java-modules/core-java-networking-3/README.md @@ -5,4 +5,5 @@ This module contains articles about networking in Java ### Relevant Articles - [Finding a Free Port in Java](https://www.baeldung.com/java-free-port) +- [Downloading Email Attachments in Java](https://www.baeldung.com/java-download-email-attachments) - [[<-- Prev]](/core-java-modules/core-java-networking-2) diff --git a/core-java-modules/core-java-reflection-2/README.md b/core-java-modules/core-java-reflection-2/README.md index 3195cddc42..4c888bdf58 100644 --- a/core-java-modules/core-java-reflection-2/README.md +++ b/core-java-modules/core-java-reflection-2/README.md @@ -5,3 +5,4 @@ - [Checking If a Method is Static Using Reflection in Java](https://www.baeldung.com/java-check-method-is-static) - [Checking if a Java Class is ‘abstract’ Using Reflection](https://www.baeldung.com/java-reflection-is-class-abstract) - [Invoking a Private Method in Java](https://www.baeldung.com/java-call-private-method) +- [Finding All Classes in a Java Package](https://www.baeldung.com/java-find-all-classes-in-package) diff --git a/core-java-modules/core-java-regex/README.md b/core-java-modules/core-java-regex/README.md index 92321fa656..bc28f4b732 100644 --- a/core-java-modules/core-java-regex/README.md +++ b/core-java-modules/core-java-regex/README.md @@ -13,3 +13,4 @@ - [Regular Expressions \s and \s+ in Java](https://www.baeldung.com/java-regex-s-splus) - [Validate Phone Numbers With Java Regex](https://www.baeldung.com/java-regex-validate-phone-numbers) - [How to Count the Number of Matches for a Regex?](https://www.baeldung.com/java-count-regex-matches) +- [Find All Numbers in a String in Java](https://www.baeldung.com/java-find-numbers-in-string) diff --git a/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java new file mode 100644 index 0000000000..e90d2e9f26 --- /dev/null +++ b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/countdigits/CountDigitsUnitTest.java @@ -0,0 +1,77 @@ +package com.baeldung.regex.countdigits; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; + +import com.google.common.base.CharMatcher; + +/** + * Unit Test to count the number of digits in a String + */ +class CountDigitsUnitTest { + + // Guava CharMatcher to match digits + private static final CharMatcher DIGIT_CHAR_MATCHER = CharMatcher.inRange('0', '9'); + + private static final String STR_WITH_ALL_DIGITS = "970987678607608"; + private static final String STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS = "9kjl()4f*(&6~3dfd8&5dfd8a"; + private static final String STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS + = "64.6lk.l~453lkdsf9wg038.68*()(k;95786fsd7986"; + + private static int countDigits(String stringToSearch) { + Matcher countEmailMatcher = Pattern.compile("\\d").matcher(stringToSearch); + + int count = 0; + while (countEmailMatcher.find()) { + count++; + } + + return count; + } + + @Test + void givenStrOfAllDigits_whenRegexMatchByDigit_thenFifteenDigitsCounted() { + int count = countDigits(STR_WITH_ALL_DIGITS); + + assertThat(count).isEqualTo(15); + } + + @Test + void givenStrWithSingleDigitsSepByNonDigits_whenRegexMatchByDigit_thenSevenDigitsCounted() { + int count = countDigits(STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS); + + assertThat(count).isEqualTo(7); + } + + @Test + void givenStrWithOneOrMoreDigitsSepByNonDigits_whenRegexMatchByDigit_thenTwentyOneDigitsCounted() { + int count = countDigits(STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS); + + assertThat(count).isEqualTo(21); + } + + @Test + void givenStrOfAllDigits_whenGuavaCharMatchByDigit_thenFifteenDigitsCounted() { + int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_ALL_DIGITS); + + assertThat(count).isEqualTo(15); + } + + @Test + void givenStrWithSingleDigitsSepByNonDigits_whenGuavaCharMatchByDigit_thenSevenDigitsCounted() { + int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_SINGLE_DIGITS_SEP_BY_NON_DIGITS); + + assertThat(count).isEqualTo(7); + } + + @Test + void givenStrWithOneOrMoreDigitsSepByNonDigits_whenGuavaCharMatchByDigit_thenTwentyOneDigitsCounted() { + int count = DIGIT_CHAR_MATCHER.countIn(STR_WITH_SEQUENCES_OF_1_OR_MORE_DIGITS_SEP_BY_NON_DIGITS); + + assertThat(count).isEqualTo(21); + } +} diff --git a/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java new file mode 100644 index 0000000000..128a326ae2 --- /dev/null +++ b/core-java-modules/core-java-regex/src/test/java/com/baeldung/regex/findnumbers/FindNumbersUnitTest.java @@ -0,0 +1,128 @@ +package com.baeldung.regex.findnumbers; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.DoubleStream; +import java.util.stream.LongStream; + +import org.junit.jupiter.api.Test; + +/** + * Unit Test to find Integers, Decimal Numbers, and Scientific Notation and Hexadecimal Numbers in a String + */ +class FindNumbersUnitTest { + + private static List findIntegers(String stringToSearch) { + Pattern integerPattern = Pattern.compile("-?\\d+"); + Matcher matcher = integerPattern.matcher(stringToSearch); + + List integerList = new ArrayList<>(); + while (matcher.find()) { + integerList.add(matcher.group()); + } + + return integerList; + } + + private static List findDecimalNums(String stringToSearch) { + Pattern decimalNumPattern = Pattern.compile("-?\\d+(\\.\\d+)?"); + Matcher matcher = decimalNumPattern.matcher(stringToSearch); + + List decimalNumList = new ArrayList<>(); + while (matcher.find()) { + decimalNumList.add(matcher.group()); + } + + return decimalNumList; + } + + @Test + void givenStrOfAllDigits_whenRegexMatchByInt_thenWholeStrMatchedAsOneInt() { + List integersFound = findIntegers("970987678607608"); + + assertThat(integersFound).containsExactly("970987678607608"); + } + + @Test + void givenStrWithIntegersSepByPeriods_whenRegexMatchByInt_thenExpectedIntsFound() { + List integersFound = findIntegers("3453..5.-23532...32432.-2363.3454......345.-34."); + + assertThat(integersFound).containsExactly("3453", "5", "-23532", "32432", "-2363", "3454", "345", "-34"); + } + + @Test + void givenStrWithIntegersSepByNonDigits_whenRegexMatchByInt_thenExpectedIntsFound() { + List integersFound = findIntegers("646lkl~4-53l-k34.fdsf.-ds-35.45f9wg3868*()(k;-95786fsd79-86"); + + assertThat(integersFound).containsExactly("646", "4", "-53", "34", "-35", "45", "9", "3868", "-95786", "79", "-86"); + } + + @Test + void givenStrOfAllDigits_whenRegexMatchByDecNum_thenWholeStrMatchedAsOneDecimalNumber() { + List decimalNumsFound = findDecimalNums("970987678607608"); + + assertThat(decimalNumsFound).containsExactly("970987678607608"); + } + + @Test + void givenStrOfDecNumsSepByNonDigits_whenRegexMatchByDecNum_thenExpectedNumsFound() { + List decimalNumsFound = findDecimalNums(".7854.455wo.rdy(do.g)-3.-553.00.53;good^night%o3456sdcardR%3567.4%£cat"); + + assertThat(decimalNumsFound).containsExactly("7854.455", "-3", "-553.00", "53", "3456", "3567.4"); + } + + @Test + void givenStrWithRandomDigitsDashesAndPeriods_whenRegexMatchByDecNum_thenExpectedNumsFound() { + List decimalNumsFound = findDecimalNums(".-..90834.345.--493-..-85.-875.345-.-.-355.345...345.-.636-5.6-3."); + + assertThat(decimalNumsFound).containsExactly("90834.345", "-493", "-85", "-875.345", "-355.345", "345", "636", "-5.6", "-3"); + } + + @Test + void givenStrOfIntsSepByNonDigits_whenRegexMatchByInt_thenExpectedValuesFound() { + LongStream integerValuesFound = findIntegers(".7854.455wo.rdy(do.g)-3.ght%o34.56") + .stream().mapToLong(Long::valueOf); + + assertThat(integerValuesFound).containsExactly(7854L, 455L, -3L, 34L, 56L); + } + + @Test + void givenStrOfDecNumsSepByNonDigits_whenRegexMatchByDecNum_thenExpectedValuesFound() { + DoubleStream decimalNumValuesFound = findDecimalNums(".7854.455wo.rdy(do.g)-3.ght%o34.56") + .stream().mapToDouble(Double::valueOf); + + assertThat(decimalNumValuesFound).containsExactly(7854.455, -3.0, 34.56); + } + + @Test + void givenStrOfSciNotationNumsSepByNonDigits_whenRegexMatchBySciNotNum_thenExpectedNumsFound() { + String strToSearch = "}s1.25E-3>,/@l2e109he-70.96E+105d£d_-8.7312E-102=#;,.d919.3822e+31e]"; + + Matcher matcher = Pattern.compile("-?\\d+(\\.\\d+)?[eE][+-]?\\d+") + .matcher(strToSearch); + List sciNotationNums = new ArrayList<>(); + while (matcher.find()) { + sciNotationNums.add(matcher.group()); + } + + assertThat(sciNotationNums).containsExactly("1.25E-3", "2e109", "-70.96E+105", "-8.7312E-102", "919.3822e+31"); + } + + @Test + void givenStrOfHexNumsSepByNonDigits_whenRegexMatchByHexNum_thenExpectedNumsFound() { + String strToSearch = "}saF851Bq-3f6Cm>,/@j-2Ad9eE>70ae19.>"; + + Matcher matcher = Pattern.compile("-?[0-9a-fA-F]+") + .matcher(strToSearch); + List hexNums = new ArrayList<>(); + while (matcher.find()) { + hexNums.add(matcher.group()); + } + + assertThat(hexNums).containsExactly("aF851B", "-3f6C", "-2Ad9eE", "70ae19"); + } +} diff --git a/core-java-modules/core-java-security-3/README.md b/core-java-modules/core-java-security-3/README.md index 4585b6cc86..970faaac88 100644 --- a/core-java-modules/core-java-security-3/README.md +++ b/core-java-modules/core-java-security-3/README.md @@ -4,5 +4,5 @@ This module contains articles about core Java Security ### Relevant Articles: -- [Secret Key and String Conversion in Java](https://www.baeldung.com/secret-key-and-string-conversion-in-java/) +- [Secret Key and String Conversion in Java](https://www.baeldung.com/java-secret-key-to-string) - More articles: [[<-- prev]](/core-java-modules/core-java-security-2) diff --git a/gradle/gradle-cucumber/README.md b/gradle/gradle-cucumber/README.md new file mode 100644 index 0000000000..a92593e959 --- /dev/null +++ b/gradle/gradle-cucumber/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Using Cucumber with Gradle](https://www.baeldung.com/java-cucumber-gradle) diff --git a/kubernetes/k8s-intro/pom.xml b/kubernetes/k8s-intro/pom.xml index 61722cb2c8..5da137ebb6 100644 --- a/kubernetes/k8s-intro/pom.xml +++ b/kubernetes/k8s-intro/pom.xml @@ -17,6 +17,7 @@ client-java 11.0.0 + ch.qos.logback logback-classic diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java new file mode 100644 index 0000000000..0c73dfb203 --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/RunJob.java @@ -0,0 +1,152 @@ +/** + * + */ +package com.baeldung.kubernetes.intro; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.kubernetes.client.custom.V1Patch; +import io.kubernetes.client.custom.V1Patch.V1PatchAdapter; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.BatchV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1DeleteOptions; +import io.kubernetes.client.openapi.models.V1DeleteOptionsBuilder; +import io.kubernetes.client.openapi.models.V1Job; +import io.kubernetes.client.openapi.models.V1JobBuilder; +import io.kubernetes.client.openapi.models.V1JobSpec; +import io.kubernetes.client.openapi.models.V1JobSpecBuilder; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Status; +import io.kubernetes.client.util.Config; +import io.kubernetes.client.util.PatchUtils; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +/** + * @author Philippe + * + */ +public class RunJob { + + private static Logger log = LoggerFactory.getLogger(RunJob.class); + + public static void main(String[] args) throws Exception { + + // Create client with logginginterceptor + ApiClient client = Config.defaultClient(); + HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> log.info(message)); + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + OkHttpClient newClient = client.getHttpClient() + .newBuilder() + .addInterceptor(interceptor) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + client.setHttpClient(newClient); + + // Create Job Spec + BatchV1Api api = new BatchV1Api(client); + String ns = "report-jobs"; + V1Job body = new V1JobBuilder() + .withNewMetadata() + .withNamespace(ns) + .withName("payroll-report-job") + .endMetadata() + .withNewSpec() + .withCompletions(2) + .withParallelism(1) + .withNewTemplate() + .withNewMetadata() + .addToLabels("name", "payroll-report") + .endMetadata() + .editOrNewSpec() + .addNewContainer() + .withName("main") + .withImage("alpine") + .addNewCommand("/bin/sh") + .addNewArg("-c") + .addNewArg("sleep 10") + .endContainer() + .withRestartPolicy("Never") + .endSpec() + .endTemplate() + .endSpec() + .build(); + + // Send to K8S + V1Job createdJob = api.createNamespacedJob(ns, body, null, null, null); + log.info("job: uid={}", createdJob.getMetadata().getUid()); + + // Let's change its parallelism value + V1Job patchedJob = new V1JobBuilder(createdJob) + .withNewMetadata() + .withName(createdJob.getMetadata().getName()) + .withNamespace(createdJob.getMetadata().getNamespace()) + .endMetadata() + .editSpec() + .withParallelism(2) + .endSpec() + .build(); + + String patchedJobJSON = client.getJSON().serialize(patchedJob); + V1Patch patch = new V1Patch(patchedJobJSON); + + PatchUtils.patch( + V1Job.class, + () -> api.patchNamespacedJobCall( + createdJob.getMetadata().getName(), + createdJob.getMetadata().getNamespace(), + patch, + null, + null, + "acme", + true, + null), + V1Patch.PATCH_FORMAT_APPLY_YAML, + api.getApiClient()); + + while(!jobCompleted(api,createdJob)) { + log.info("[I75] still running..."); + Thread.sleep(1000); + } + + V1Status response = api.deleteNamespacedJob( + createdJob.getMetadata().getName(), + createdJob.getMetadata().getNamespace(), + null, + null, + null, + null, + null, + null ) ; + + log.info("[I122] response={}", response); + } + + private static boolean jobCompleted(BatchV1Api api, V1Job createdJob) throws Exception { + + V1Job job = api.readNamespacedJob( + createdJob.getMetadata().getName(), + createdJob.getMetadata().getNamespace(), + null,null,null); + + if ( job.getStatus() == null ) { + return false; + } + + log.info("[I88] Status: active={}, succeeded={}, failed={}", + job.getStatus().getActive(), + job.getStatus().getSucceeded(), + job.getStatus().getFailed() + ); + Integer active = job.getStatus().getActive(); + + return active == null || active == 0 ; + } + +} diff --git a/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java new file mode 100644 index 0000000000..d6621db2d4 --- /dev/null +++ b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/RunJobLiveTest.java @@ -0,0 +1,10 @@ +package com.baeldung.kubernetes.intro; + +import org.junit.jupiter.api.Test; + +class RunJobLiveTest { + @Test + void whenWatchPods_thenSuccess() throws Exception { + RunJob.main(new String[] {}); + } +} diff --git a/libraries-data-3/README.md b/libraries-data-3/README.md new file mode 100644 index 0000000000..fffdf65252 --- /dev/null +++ b/libraries-data-3/README.md @@ -0,0 +1,10 @@ +## Data Libraries + +This module contains articles about libraries for data processing in Java. + +### Relevant articles +- [Kafka Streams vs Kafka Consumer]() +- More articles: [[<-- prev]](/../libraries-data-2) + +##### Building the project +You can build the project from the command line using: *mvn clean install*, or in an IDE. If you have issues with the derive4j imports in your IDE, you have to add the folder: *target/generated-sources/annotations* to the project build path in your IDE. diff --git a/libraries-data-3/log4j.properties b/libraries-data-3/log4j.properties new file mode 100644 index 0000000000..2173c5d96f --- /dev/null +++ b/libraries-data-3/log4j.properties @@ -0,0 +1 @@ +log4j.rootLogger=INFO, stdout diff --git a/libraries-data-3/pom.xml b/libraries-data-3/pom.xml new file mode 100644 index 0000000000..bfc39e537e --- /dev/null +++ b/libraries-data-3/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + libraries-data-3 + libraries-data-3 + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + + org.apache.kafka + kafka-clients + ${kafka.version} + test + test + + + org.apache.kafka + kafka-streams + ${kafka.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + org.assertj + assertj-core + ${assertj.version} + test + + + org.testcontainers + kafka + ${testcontainers-kafka.version} + test + + + + + 3.6.2 + 1.7.25 + 2.8.0 + 1.15.3 + + + \ No newline at end of file diff --git a/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java b/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java new file mode 100644 index 0000000000..0d4c0606e3 --- /dev/null +++ b/libraries-data-3/src/test/java/com/baeldung/kafka/streams/KafkaStreamsLiveTest.java @@ -0,0 +1,279 @@ +package com.baeldung.kafka.streams; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.serialization.Serde; +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.common.utils.Bytes; +import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.StoreQueryParameters; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.kstream.Consumed; +import org.apache.kafka.streams.kstream.Grouped; +import org.apache.kafka.streams.kstream.JoinWindows; +import org.apache.kafka.streams.kstream.KGroupedStream; +import org.apache.kafka.streams.kstream.KGroupedTable; +import org.apache.kafka.streams.kstream.KStream; +import org.apache.kafka.streams.kstream.KTable; +import org.apache.kafka.streams.kstream.Materialized; +import org.apache.kafka.streams.kstream.Produced; +import org.apache.kafka.streams.kstream.TimeWindows; +import org.apache.kafka.streams.state.KeyValueIterator; +import org.apache.kafka.streams.state.KeyValueStore; +import org.apache.kafka.streams.state.QueryableStoreTypes; +import org.apache.kafka.streams.state.ReadOnlyKeyValueStore; +import org.apache.kafka.streams.state.StoreBuilder; +import org.apache.kafka.streams.state.Stores; +import org.apache.kafka.streams.state.WindowStore; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Locale; +import java.util.Properties; + +import static org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG; +import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; +import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; + +public class KafkaStreamsLiveTest { + private final String LEFT_TOPIC = "left-stream-topic"; + private final String RIGHT_TOPIC = "right-stream-topic"; + private final String LEFT_RIGHT_TOPIC = "left-right-stream-topic"; + + private KafkaProducer producer = createKafkaProducer(); + private Properties streamsConfiguration = new Properties(); + + static final String TEXT_LINES_TOPIC = "TextLinesTopic"; + + private final String TEXT_EXAMPLE_1 = "test test and test"; + private final String TEXT_EXAMPLE_2 = "test filter filter this sentence"; + + @ClassRule + public static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.4.3")); + + @Before + public void setUp() { + streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); + streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); + streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName()); + streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 1000); + streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + } + + @Test + public void shouldTestKafkaTableLatestWord() throws InterruptedException { + String inputTopic = "topicTable"; + + final StreamsBuilder builder = new StreamsBuilder(); + + KTable textLinesTable = builder.table(inputTopic, + Consumed.with(Serdes.String(), Serdes.String())); + + textLinesTable.toStream().foreach((word, count) -> System.out.println("Latest word: " + word + " -> " + count)); + + final Topology topology = builder.build(); + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "latest-word-id"); + KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration); + + streams.cleanUp(); + streams.start(); + producer.send(new ProducerRecord(inputTopic, "1", TEXT_EXAMPLE_1)); + producer.send(new ProducerRecord(inputTopic, "2", TEXT_EXAMPLE_2)); + + Thread.sleep(2000); + streams.close(); + } + + @Test + public void shouldTestWordCountKafkaStreams() throws InterruptedException { + String wordCountTopic = "wordCountTopic"; + + final StreamsBuilder builder = new StreamsBuilder(); + KStream textLines = builder.stream(wordCountTopic, + Consumed.with(Serdes.String(), Serdes.String())); + + KTable wordCounts = textLines + .flatMapValues(value -> Arrays.asList(value.toLowerCase(Locale.ROOT) + .split("\\W+"))) + .groupBy((key, word) -> word) + .count(Materialized.> as("counts-store")); + + wordCounts.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count)); + + wordCounts.toStream().to("outputTopic", + Produced.with(Serdes.String(), Serdes.Long())); + + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-stream-table-id"); + final Topology topology = builder.build(); + KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration); + + streams.cleanUp(); + streams.start(); + + producer.send(new ProducerRecord(wordCountTopic, "1", TEXT_EXAMPLE_1)); + producer.send(new ProducerRecord(wordCountTopic, "2", TEXT_EXAMPLE_2)); + + Thread.sleep(2000); + streams.close(); + } + + // Filter, map + @Test + public void shouldTestStatelessTransformations() throws InterruptedException { + String wordCountTopic = "wordCountTopic"; + + //when + final StreamsBuilder builder = new StreamsBuilder(); + KStream textLines = builder.stream(wordCountTopic, + Consumed.with(Serdes.String(), Serdes.String())); + + final KStream textLinesUpperCase = + textLines + .map((key, value) -> KeyValue.pair(value, value.toUpperCase())) + .filter((key, value) -> value.contains("FILTER")); + + KTable wordCounts = textLinesUpperCase + .flatMapValues(value -> Arrays.asList(value.split("\\W+"))) + .groupBy((key, word) -> word) + .count(Materialized.> as("counts-store")); + + wordCounts.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count)); + + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-filter-map-id"); + final Topology topology = builder.build(); + KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration); + + streams.cleanUp(); + streams.start(); + + producer.send(new ProducerRecord(wordCountTopic, "1", TEXT_EXAMPLE_1)); + producer.send(new ProducerRecord(wordCountTopic, "2", TEXT_EXAMPLE_2)); + + Thread.sleep(2000); + streams.close(); + + } + + @Test + public void shouldTestAggregationStatefulTransformations() throws InterruptedException { + String aggregationTopic = "aggregationTopic"; + + final StreamsBuilder builder = new StreamsBuilder(); + final KStream input = builder.stream(aggregationTopic, + Consumed.with(Serdes.ByteArray(), Serdes.String())); + final KTable aggregated = input + .groupBy((key, value) -> (value != null && value.length() > 0) ? value.substring(0, 2).toLowerCase() : "", + Grouped.with(Serdes.String(), Serdes.String())) + .aggregate(() -> 0L, (aggKey, newValue, aggValue) -> aggValue + newValue.length(), + Materialized.with(Serdes.String(), Serdes.Long())); + + aggregated.toStream().foreach((word, count) -> System.out.println("Word: " + word + " -> " + count)); + + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "aggregation-id"); + final Topology topology = builder.build(); + KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration); + + streams.cleanUp(); + streams.start(); + + producer.send(new ProducerRecord(aggregationTopic, "1", "one")); + producer.send(new ProducerRecord(aggregationTopic, "2", "two")); + producer.send(new ProducerRecord(aggregationTopic, "3", "three")); + producer.send(new ProducerRecord(aggregationTopic, "4", "four")); + producer.send(new ProducerRecord(aggregationTopic, "5", "five")); + + Thread.sleep(5000); + streams.close(); + + } + + @Test + public void shouldTestWindowingJoinStatefulTransformations() throws InterruptedException { + final StreamsBuilder builder = new StreamsBuilder(); + + KStream leftSource = builder.stream(LEFT_TOPIC); + KStream rightSource = builder.stream(RIGHT_TOPIC); + + KStream leftRightSource = leftSource.outerJoin(rightSource, + (leftValue, rightValue) -> "left=" + leftValue + ", right=" + rightValue, + JoinWindows.of(Duration.ofSeconds(5))) + .groupByKey() + .reduce(((key, lastValue) -> lastValue)) + .toStream(); + + leftRightSource.foreach((key, value) -> System.out.println("(key= " + key + ") -> (" + value + ")")); + + final Topology topology = builder.build(); + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "windowing-join-id"); + KafkaStreams streams = new KafkaStreams(topology, streamsConfiguration); + + streams.cleanUp(); + streams.start(); + + producer.send(new ProducerRecord(LEFT_TOPIC, "1", "left")); + producer.send(new ProducerRecord(RIGHT_TOPIC, "2", "right")); + + Thread.sleep(2000); + streams.close(); + } + + @Test + public void shouldTestWordCountWithInteractiveQueries() throws InterruptedException { + + final Serde stringSerde = Serdes.String(); + final StreamsBuilder builder = new StreamsBuilder(); + final KStream + textLines = builder.stream(TEXT_LINES_TOPIC, Consumed.with(Serdes.String(), Serdes.String())); + + final KGroupedStream groupedByWord = textLines + .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+"))) + .groupBy((key, word) -> word, Grouped.with(stringSerde, stringSerde)); + + groupedByWord.count(Materialized.>as("WordCountsStore") + .withValueSerde(Serdes.Long())); + + streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-interactive-queries"); + + final KafkaStreams streams = new KafkaStreams(builder.build(), streamsConfiguration); + streams.cleanUp(); + streams.start(); + + producer.send(new ProducerRecord(TEXT_LINES_TOPIC, "1", TEXT_EXAMPLE_1)); + producer.send(new ProducerRecord(TEXT_LINES_TOPIC, "2", TEXT_EXAMPLE_2)); + + Thread.sleep(2000); + ReadOnlyKeyValueStore keyValueStore = + streams.store(StoreQueryParameters.fromNameAndType( + "WordCountsStore", QueryableStoreTypes.keyValueStore())); + + KeyValueIterator range = keyValueStore.all(); + while (range.hasNext()) { + KeyValue next = range.next(); + System.out.println("Count for " + next.key + ": " + next.value); + } + + streams.close(); + } + + private static KafkaProducer createKafkaProducer() { + + Properties props = new Properties(); + props.put(BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); + props.put(KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + props.put(VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + + return new KafkaProducer(props); + + } +} + + diff --git a/persistence-modules/java-jpa-3/README.md b/persistence-modules/java-jpa-3/README.md index e607043880..c024d7c540 100644 --- a/persistence-modules/java-jpa-3/README.md +++ b/persistence-modules/java-jpa-3/README.md @@ -11,3 +11,5 @@ This module contains articles about the Java Persistence API (JPA) in Java. - [A Guide to MultipleBagFetchException in Hibernate](https://www.baeldung.com/java-hibernate-multiplebagfetchexception) - [How to Convert a Hibernate Proxy to a Real Entity Object](https://www.baeldung.com/hibernate-proxy-to-real-entity-object) - [Returning an Auto-Generated Id with JPA](https://www.baeldung.com/jpa-get-auto-generated-id) +- [How to Return Multiple Entities In JPA Query](https://www.baeldung.com/jpa-return-multiple-entities) +- [Defining Unique Constraints in JPA](https://www.baeldung.com/jpa-unique-constraints) diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java new file mode 100644 index 0000000000..b20de6a471 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Address.java @@ -0,0 +1,37 @@ +package com.baeldung.jpa.uniqueconstraints; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table +public class Address implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue + private Long id; + + private String streetAddress; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(String streetAddress) { + this.streetAddress = streetAddress; + } +} \ No newline at end of file diff --git a/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java new file mode 100644 index 0000000000..c5df90df73 --- /dev/null +++ b/persistence-modules/java-jpa-3/src/main/java/com/baeldung/jpa/uniqueconstraints/Person.java @@ -0,0 +1,116 @@ +package com.baeldung.jpa.uniqueconstraints; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Entity +@Table(uniqueConstraints = { @UniqueConstraint(name = "UniqueNumberAndStatus", columnNames = { "personNumber", "isActive" }), + @UniqueConstraint(name = "UniqueSecurityAndDepartment", columnNames = { "securityNumber", "departmentCode" }), + @UniqueConstraint(name = "UniqueNumberAndAddress", columnNames = { "personNumber", "address" }) }) +public class Person implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue + private Long id; + + private String name; + + private String password; + + @Column(unique = true) + private String email; + + @Column(unique = true) + private Long personNumber; + + private Boolean isActive; + + private String securityNumber; + + private String departmentCode; + + @Column(unique = true) + @JoinColumn(name = "addressId", referencedColumnName = "id") + private Address address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Long getPersonNumber() { + return personNumber; + } + + public void setPersonNumber(Long personNumber) { + this.personNumber = personNumber; + } + + public Boolean getIsActive() { + return isActive; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } + + public String getScode() { + return securityNumber; + } + + public void setScode(String scode) { + this.securityNumber = scode; + } + + public String getDcode() { + return departmentCode; + } + + public void setDcode(String dcode) { + this.departmentCode = dcode; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } +} \ No newline at end of file diff --git a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml index 666fc1500a..1166aaca71 100644 --- a/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml +++ b/persistence-modules/java-jpa-3/src/main/resources/META-INF/persistence.xml @@ -113,6 +113,22 @@ + + org.hibernate.jpa.HibernatePersistenceProvider + com.baeldung.jpa.uniqueconstraints.Person + com.baeldung.jpa.uniqueconstraints.Address + true + + + + + + + + + + + org.hibernate.jpa.HibernatePersistenceProvider com.baeldung.jpa.returnmultipleentities.Channel diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java new file mode 100644 index 0000000000..ca776ba00b --- /dev/null +++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueColumnIntegrationTest.java @@ -0,0 +1,119 @@ +package com.baeldung.jpa.uniqueconstraints; + +import java.util.Optional; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.hibernate.exception.ConstraintViolationException; +import org.junit.Assert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class UniqueColumnIntegrationTest { + + private static EntityManagerFactory factory; + private static EntityManager entityManager; + + @BeforeAll + public static void setup() { + factory = Persistence.createEntityManagerFactory("jpa-unique-constraints"); + entityManager = factory.createEntityManager(); + } + + @Test + public void whenPersistPersonWithSameNumber_thenConstraintViolationException() { + Person person1 = new Person(); + person1.setPersonNumber(2000L); + person1.setEmail("john.beth@gmail.com"); + + Person person2 = new Person(); + person2.setPersonNumber(2000L); + person2.setEmail("anthony.green@gmail.com"); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } + + @Test + public void whenPersistPersonWithSameEmail_thenConstraintViolationException() { + Person person1 = new Person(); + person1.setPersonNumber(4000L); + person1.setEmail("timm.beth@gmail.com"); + + Person person2 = new Person(); + person2.setPersonNumber(3000L); + person2.setEmail("timm.beth@gmail.com"); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } + + @Test + public void whenPersistPersonWithSameAddress_thenConstraintViolationException() { + Person person1 = new Person(); + person1.setPersonNumber(5000L); + person1.setEmail("chris.beck@gmail.com"); + + Address address1 = new Address(); + address1.setStreetAddress("20 Street"); + person1.setAddress(address1); + + Person person2 = new Person(); + person2.setPersonNumber(6000L); + person2.setEmail("mark.jonson@gmail.com"); + person2.setAddress(address1); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } +} \ No newline at end of file diff --git a/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java new file mode 100644 index 0000000000..f12313724e --- /dev/null +++ b/persistence-modules/java-jpa-3/src/test/java/com/baeldung/jpa/uniqueconstraints/UniqueConstraintIntegrationTest.java @@ -0,0 +1,116 @@ +package com.baeldung.jpa.uniqueconstraints; + +import java.util.Optional; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +import org.hibernate.exception.ConstraintViolationException; +import org.junit.Assert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class UniqueConstraintIntegrationTest { + private static EntityManagerFactory factory; + private static EntityManager entityManager; + + @BeforeAll + public static void setup() { + factory = Persistence.createEntityManagerFactory("jpa-unique-constraints"); + entityManager = factory.createEntityManager(); + } + + @Test + public void whenPersistPersonWithSameNumberAndStatus_thenConstraintViolationException() { + Person person1 = new Person(); + person1.setPersonNumber(12345L); + person1.setIsActive(Boolean.TRUE); + + Person person2 = new Person(); + person2.setPersonNumber(12345L); + person2.setIsActive(Boolean.TRUE); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } + + @Test + public void whenPersistPersonWithSameSCodeAndDecode_thenConstraintViolationException() { + Person person1 = new Person(); + person1.setDcode("Sec1"); + person1.setScode("Axybg356"); + + Person person2 = new Person(); + person2.setDcode("Sec1"); + person2.setScode("Axybg356"); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } + + @Test + public void whenPersistPersonWithSameNumberAndAddress_thenConstraintViolationException() { + Address address1 = new Address(); + address1.setStreetAddress("40 Street"); + + Person person1 = new Person(); + person1.setPersonNumber(54321L); + person1.setAddress(address1); + + Person person2 = new Person(); + person2.setPersonNumber(99999L); + person2.setAddress(address1); + + entityManager.getTransaction().begin(); + entityManager.persist(person1); + entityManager.getTransaction().commit(); + + entityManager.getTransaction().begin(); + try { + entityManager.persist(person2); + entityManager.getTransaction().commit(); + Assert.fail("Should raise an exception - unique key violation"); + } catch (Exception ex) { + Assert.assertTrue(Optional.of(ex) + .map(Throwable::getCause) + .map(Throwable::getCause) + .filter(x -> x instanceof ConstraintViolationException) + .isPresent()); + } finally { + entityManager.getTransaction().rollback(); + } + } +} \ No newline at end of file diff --git a/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java b/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java new file mode 100644 index 0000000000..137bcbe021 --- /dev/null +++ b/reactor-core/src/test/java/com/baeldung/reactor/mapping/MappingUnitTest.java @@ -0,0 +1,37 @@ +package com.baeldung.reactor.mapping; + +import org.junit.Test; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MappingUnitTest { + @Test + public void givenInputStream_whenCallingTheMapOperator_thenItemsAreTransformed() { + Function mapper = String::toUpperCase; + Flux inFlux = Flux.just("baeldung", ".", "com"); + Flux outFlux = inFlux.map(mapper); + + StepVerifier.create(outFlux) + .expectNext("BAELDUNG", ".", "COM") + .expectComplete() + .verify(); + } + + @Test + public void givenInputStream_whenCallingTheFlatMapOperator_thenItemsAreFlatten() { + Function> mapper = s -> Flux.just(s.toUpperCase().split("")); + Flux inFlux = Flux.just("baeldung", ".", "com"); + Flux outFlux = inFlux.flatMap(mapper); + + List output = new ArrayList<>(); + outFlux.subscribe(output::add); + assertThat(output).containsExactlyInAnyOrder("B", "A", "E", "L", "D", "U", "N", "G", ".", "C", "O", "M"); + } +}