Merge branch 'eugenp:master' into master

This commit is contained in:
Ulisses Lima 2022-05-20 12:06:51 -03:00 committed by GitHub
commit 124c0bd354
93 changed files with 1937 additions and 39 deletions

View File

@ -3,12 +3,17 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<artifactId>apache-tapestry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>apache-tapestry</name>
<packaging>war</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- To set up an application with a database, change the artifactId below to tapestry-hibernate,
and add a dependency on your JDBC driver. You'll also need to add Hibernate configuration files, such

View File

@ -8,6 +8,13 @@
<name>ShippingFunction</name>
<packaging>jar</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>aws-lambda</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>

View File

@ -3,4 +3,4 @@
This module contains articles about Java HttpClient
### Relevant articles
- TODO
- [Posting with Java HttpClient](https://www.baeldung.com/java-httpclient-post)

View File

@ -0,0 +1,12 @@
package com.baeldung.http;
import java.net.http.HttpClient;
import java.time.Duration;
public class JavaHttpClientTimeout {
static HttpClient getHttpClientWithTimeout(int seconds) {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(seconds))
.build();
}
}

View File

@ -0,0 +1,54 @@
package com.baeldung.http;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static com.baeldung.http.JavaHttpClientTimeout.getHttpClientWithTimeout;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JavaHttpClientTimeoutIntegrationTest {
private HttpClient httpClient;
private HttpRequest httpRequest;
@BeforeEach
public void setUp() {
httpClient = getHttpClientWithTimeout(3);
httpClient.connectTimeout().map(Duration::toSeconds)
.ifPresent(sec -> System.out.println("Timeout in seconds: " + sec));
httpRequest = HttpRequest.newBuilder().uri(URI.create("http://10.255.255.1")).GET().build();
}
@Test
void shouldThrowExceptionWhenMakingSyncCall() {
HttpConnectTimeoutException thrown = assertThrows(
HttpConnectTimeoutException.class,
() -> httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()),
"Expected doThing() to throw, but it didn't"
);
assertTrue(thrown.getMessage().contains("timed out"));
}
@Test
void shouldThrowExceptionWhenMakingASyncCall() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<String> completableFuture =
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.exceptionally(Throwable::getMessage);
String response = completableFuture.get(5, TimeUnit.SECONDS);
assertTrue(response.contains("timed out"));
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.searchfilesbywildcards;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
public class SearchFileByWildcard {
public static List<String> matchesList = new ArrayList<String>();
public List<String> searchWithWc(Path rootDir, String pattern) throws IOException {
matchesList.clear();
FileVisitor<Path> matcherVisitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) throws IOException {
FileSystem fs = FileSystems.getDefault();
PathMatcher matcher = fs.getPathMatcher(pattern);
Path name = file.getFileName();
if (matcher.matches(name)) {
matchesList.add(name.toString());
}
return FileVisitResult.CONTINUE;
}
};
Files.walkFileTree(rootDir, matcherVisitor);
return matchesList;
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.searchfilesbywildcards;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.junit.jupiter.api.Test;
public class SearchFileByWildcardUnitTest {
@Test
public void whenFourFilenameMatch_thenListOfFour() throws IOException {
SearchFileByWildcard sfbw = new SearchFileByWildcard();
List<String> actual = sfbw.searchWithWc(Paths.get("src/test/resources/sfbw"), "glob:*.{txt,docx}");
assertEquals(new HashSet<>(Arrays.asList("six.txt", "three.txt", "two.docx", "one.txt")), new HashSet<>(actual));
}
@Test
public void whenOneFilenameMatch_thenListOfOne() throws IOException {
SearchFileByWildcard sfbw = new SearchFileByWildcard();
List<String> actual = sfbw.searchWithWc(Paths.get("src/test/resources/sfbw"), "glob:????.{csv}");
assertEquals(new HashSet<>(Arrays.asList("five.csv")), new HashSet<>(actual));
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.reflection.proxy;
public interface AdvancedOperation {
int multiply(int a, int b);
int divide(int a, int b);
}

View File

@ -0,0 +1,7 @@
package com.baeldung.reflection.proxy;
public interface BasicOperation {
int add(int a, int b);
int subtract(int a, int b);
}

View File

@ -0,0 +1,35 @@
package com.baeldung.reflection.proxy;
import org.junit.Test;
import java.lang.reflect.Proxy;
import java.util.function.Consumer;
import static org.junit.Assert.assertTrue;
public class DollarProxyUnitTest {
@Test
public void givenProxy_whenInvokingGetProxyClass_thenGeneratingProxyClass() {
// Java 8: -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
// Java 9 or later: -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
// Note: System.setProperty() doesn't work here
// because ProxyGenerator.saveGeneratedFiles read its property only once.
// The @Test annotation in this method will generate a $Proxy class.
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?>[] interfaces = {BasicOperation.class, AdvancedOperation.class};
Class<?> proxyClass = Proxy.getProxyClass(classLoader, interfaces);
boolean isProxyClass = Proxy.isProxyClass(proxyClass);
assertTrue(isProxyClass);
}
@Test
public void givenReflection_whenReadingAnnotation_thenGeneratingProxyClass() {
FunctionalInterface instance = Consumer.class.getDeclaredAnnotation(FunctionalInterface.class);
Class<?> clazz = instance.getClass();
boolean isProxyClass = Proxy.isProxyClass(clazz);
assertTrue(isProxyClass);
}
}

View File

@ -30,6 +30,12 @@
<artifactId>commons-lang3</artifactId>
<version>${apache-commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,38 @@
package com.baeldung.checkvowels;
import java.util.regex.Pattern;
public class CheckVowels {
private static final String VOWELS = "aeiouAEIOU";
private static final Pattern VOWELS_PATTERN = Pattern.compile("[aeiou]", Pattern.CASE_INSENSITIVE);
public static boolean isInVowelsString(char c) {
return VOWELS.indexOf(c) != -1;
}
public static boolean isInVowelsString(String c) {
return VOWELS.contains(c);
}
public static boolean isVowelBySwitch(char c) {
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
return true;
default:
return false;
}
}
public static boolean isVowelByRegex(String c) {
return VOWELS_PATTERN.matcher(c).matches();
}
}

View File

@ -0,0 +1,51 @@
package com.baeldung.checkvowels;
import org.junit.jupiter.api.Test;
import static com.baeldung.checkvowels.CheckVowels.*;
import static org.assertj.core.api.Assertions.*;
class CheckVowelsUnitTest {
@Test
void givenAVowelCharacter_thenInVowelString() {
assertThat(isInVowelsString('e')).isTrue();
}
@Test
void givenAConsonantCharacter_thenNotInVowelString() {
assertThat(isInVowelsString('z')).isFalse();
}
@Test
void givenAVowelString_thenInVowelString() {
assertThat(isInVowelsString("e")).isTrue();
}
@Test
void givenAConsonantString_thenNotInVowelString() {
assertThat(isInVowelsString("z")).isFalse();
}
@Test
void givenAVowelCharacter_thenInVowelSwitch() {
assertThat(isVowelBySwitch('e')).isTrue();
}
@Test
void givenAConsonantCharacter_thenNotInVowelSwitch() {
assertThat(isVowelBySwitch('z')).isFalse();
}
@Test
void givenAVowelString_thenInVowelPattern() {
assertThat(isVowelByRegex("e")).isTrue();
assertThat(isVowelByRegex("E")).isTrue();
}
@Test
void givenAVowelCharacter_thenInVowelPattern() {
assertThat(isVowelByRegex(Character.toString('e'))).isTrue();
assertThat(isVowelByRegex("E")).isTrue();
}
}

View File

@ -1,19 +1,19 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>docker-push-to-private-repo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>docker-push-to-private-repo</name>
<description>Example application to showcase how to push a docker image to a private repository</description>
<parent>
<groupId>com.baeldung.docker</groupId>
<artifactId>docker</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>push-to-private-repo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>push-to-private-repo</name>
<description>Example application to showcase how to push a docker image to a private repository</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -40,4 +40,8 @@
</plugins>
</build>
<properties>
<java.version>11</java.version>
</properties>
</project>

View File

@ -8,3 +8,4 @@
- [Convert Byte Size Into a Human-Readable Format in Java](https://www.baeldung.com/java-human-readable-byte-size)
- [Convert boolean to int in Java](https://www.baeldung.com/java-boolean-to-int)
- [Generate a Random Value From an Enum](https://www.baeldung.com/java-enum-random-value)
- [Reverse a Number in Java](https://www.baeldung.com/java-reverse-number)

View File

@ -1090,6 +1090,66 @@
</plugins>
</build>
</profile>
<profile>
<id>integration-lite-first</id>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<executions>
<execution>
<id>install node and npm</id>
<phase>none</phase>
</execution>
<execution>
<id>npm install</id>
<phase>none</phase>
</execution>
<execution>
<id>webpack build dev</id>
<phase>none</phase>
</execution>
<execution>
<id>webpack build test</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>integration-lite-second</id>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<executions>
<execution>
<id>install node and npm</id>
<phase>none</phase>
</execution>
<execution>
<id>npm install</id>
<phase>none</phase>
</execution>
<execution>
<id>webpack build dev</id>
<phase>none</phase>
</execution>
<execution>
<id>webpack build test</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<properties>

View File

@ -174,7 +174,7 @@
<gson.version>2.8.2</gson.version>
<cache.version>1.1.1</cache.version>
<flink.version>1.5.0</flink.version>
<hazelcast.version>5.1.1</hazelcast.version>
<hazelcast.version>3.8.4</hazelcast.version>
<org.apache.crunch.crunch-core.version>0.15.0</org.apache.crunch.crunch-core.version>
<org.apache.hadoop.hadoop-client>2.2.0</org.apache.hadoop.hadoop-client>
<jmapper.version>1.6.0.1</jmapper.version>

View File

@ -8,6 +8,12 @@
<version>1.0-SNAPSHOT</version>
<name>libraries-primitive</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/it.unimi.dsi/fastutil -->
<dependency>

36
lightrun/README.md Normal file
View File

@ -0,0 +1,36 @@
# Lightrun Example Application - Tasks Management
This application exists as an example for the Lightrun series of articles.
## Building
This application requires [Apache Maven](https://maven.apache.org/) and [Java 17+](https://www.oracle.com/java/technologies/downloads/).
Building the code is done by executing:
```
$ mvn install
```
from the top level.
## Running
The application consists of three services:
* Tasks
* Users
* API
These are all Spring Boot applications.
The Tasks and Users services exist as microservices for managing one facet of data. Each uses a database, and utilise a JMS queue between them as well. For convenience this infrastructure is all embedded in the applications.
This does mean that the startup order is important. The JMS queue exists within the Tasks service and is connected to from the Users service. As such, the Tasks service must be started before the others.
Each service can be started either by executing `mvn spring-boot:run` from within the appropriate directory. Alternatively, as Spring Boot applications, the build will produce an executable JAR file within the `target` directory that can be executed as, for
example:
```
$ java -jar ./target/tasks-service-0.0.1-SNAPSHOT.jar
```

33
lightrun/api-service/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,45 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.baeldung</groupId>
<artifactId>api-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-service</name>
<description>Aggregator Service for LightRun Article</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.baeldung.apiservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ApiServiceApplication.class, args);
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.apiservice;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class RequestIdGenerator implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestId = UUID.randomUUID()
.toString();
MDC.put(RequestIdGenerator.class.getCanonicalName(), requestId);
response.addHeader("X-Request-Id", requestId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.remove(RequestIdGenerator.class.getCanonicalName());
}
public static String getRequestId() {
return MDC.get(RequestIdGenerator.class.getCanonicalName());
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.apiservice;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.additionalInterceptors((request, body, execution) -> {
request.getHeaders()
.add("X-Request-Id", RequestIdGenerator.getRequestId());
return execution.execute(request, body);
})
.build();
}
}

View File

@ -0,0 +1,17 @@
package com.baeldung.apiservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestIdGenerator requestIdGenerator;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestIdGenerator);
}
}

View File

@ -0,0 +1,6 @@
package com.baeldung.apiservice.adapters.http;
import java.time.Instant;
public record TaskResponse(String id, String title, Instant created, UserResponse createdBy, UserResponse assignedTo, String status) {
}

View File

@ -0,0 +1,56 @@
package com.baeldung.apiservice.adapters.http;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.apiservice.adapters.tasks.Task;
import com.baeldung.apiservice.adapters.tasks.TaskRepository;
import com.baeldung.apiservice.adapters.users.UserRepository;
@RequestMapping("/")
@RestController
public class TasksController {
@Autowired
private TaskRepository taskRepository;
@Autowired
private UserRepository userRepository;
@GetMapping("/{id}")
public TaskResponse getTaskById(@PathVariable("id") String id) {
Task task = taskRepository.getTaskById(id);
if (task == null) {
throw new UnknownTaskException();
}
return buildResponse(task);
}
private TaskResponse buildResponse(Task task) {
return new TaskResponse(task.id(), task.title(), task.created(), getUser(task.createdBy()), getUser(task.assignedTo()), task.status());
}
private UserResponse getUser(String userId) {
if (userId == null) {
return null;
}
var user = userRepository.getUserById(userId);
if (user == null) {
return null;
}
return new UserResponse(user.id(), user.name());
}
@ExceptionHandler(UnknownTaskException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleUnknownTask() {
}
}

View File

@ -0,0 +1,4 @@
package com.baeldung.apiservice.adapters.http;
public class UnknownTaskException extends RuntimeException {
}

View File

@ -0,0 +1,4 @@
package com.baeldung.apiservice.adapters.http;
public record UserResponse(String id, String name) {
}

View File

@ -0,0 +1,6 @@
package com.baeldung.apiservice.adapters.tasks;
import java.time.Instant;
public record Task(String id, String title, Instant created, String createdBy, String assignedTo, String status) {
}

View File

@ -0,0 +1,30 @@
package com.baeldung.apiservice.adapters.tasks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Repository
public class TaskRepository {
@Autowired
private RestTemplate restTemplate;
@Value("${tasks-service.url}")
private String tasksService;
public Task getTaskById(String id) {
var uri = UriComponentsBuilder.fromUriString(tasksService)
.path(id)
.build()
.toUri();
try {
return restTemplate.getForObject(uri, Task.class);
} catch (HttpClientErrorException.NotFound e) {
return null;
}
}
}

View File

@ -0,0 +1,4 @@
package com.baeldung.apiservice.adapters.users;
public record User(String id, String name) {
}

View File

@ -0,0 +1,30 @@
package com.baeldung.apiservice.adapters.users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Repository
public class UserRepository {
@Autowired
private RestTemplate restTemplate;
@Value("${users-service.url}")
private String usersService;
public User getUserById(String id) {
var uri = UriComponentsBuilder.fromUriString(usersService)
.path(id)
.build()
.toUri();
try {
return restTemplate.getForObject(uri, User.class);
} catch (HttpClientErrorException.NotFound e) {
return null;
}
}
}

View File

@ -0,0 +1,2 @@
users-service.url=http://localhost:8081
tasks-service.url=http://localhost:8082

17
lightrun/pom.xml Normal file
View File

@ -0,0 +1,17 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baelduung</groupId>
<artifactId>lightrun</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>lightrun</name>
<description>Services for LightRun Article</description>
<modules>
<module>tasks-service</module>
<module>users-service</module>
<module>api-service</module>
</modules>
</project>

33
lightrun/tasks-service/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,67 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.baeldung</groupId>
<artifactId>tasks-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tasks-service</name>
<description>Tasks Service for LightRun Article</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,23 @@
package com.baeldung.tasksservice;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ArtemisConfig implements ArtemisConfigurationCustomizer {
@Value("${spring.artemis.host}")
private String hostname;
@Value("${spring.artemis.port}")
private int port;
@Override
public void customize(org.apache.activemq.artemis.core.config.Configuration configuration) {
try {
configuration.addAcceptorConfiguration("remote", "tcp://" + hostname + ":" + port);
} catch (Exception e) {
throw new RuntimeException("Failed to configure Artemis listener", e);
}
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.tasksservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TasksServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TasksServiceApplication.class, args);
}
}

View File

@ -0,0 +1,15 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.http;
public record CreateTaskRequest(String title, String createdBy) {
}

View File

@ -0,0 +1,17 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.http;
import java.util.Optional;
public record PatchTaskRequest(Optional<String> status, Optional<String> assignedTo) {
}

View File

@ -0,0 +1,17 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.http;
import java.time.Instant;
public record TaskResponse(String id, String title, Instant created, String createdBy, String assignedTo, String status) {
}

View File

@ -0,0 +1,84 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.http;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.tasksservice.adapters.repository.TaskRecord;
import com.baeldung.tasksservice.service.TasksService;
import com.baeldung.tasksservice.service.UnknownTaskException;
@RestController
@RequestMapping("/")
class TasksController {
@Autowired
private TasksService tasksService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public TaskResponse createTask(@RequestBody CreateTaskRequest body) {
var task = tasksService.createTask(body.title(), body.createdBy());
return buildResponse(task);
}
@GetMapping
public List<TaskResponse> searchTasks(@RequestParam("status") Optional<String> status, @RequestParam("createdBy") Optional<String> createdBy) {
var tasks = tasksService.search(status, createdBy);
return tasks.stream()
.map(this::buildResponse)
.collect(Collectors.toList());
}
@GetMapping("/{id}")
public TaskResponse getTask(@PathVariable("id") String id) {
var task = tasksService.getTaskById(id);
return buildResponse(task);
}
@DeleteMapping("/{id}")
public void deleteTask(@PathVariable("id") String id) {
tasksService.deleteTaskById(id);
}
@PatchMapping("/{id}")
public TaskResponse patchTask(@PathVariable("id") String id, @RequestBody PatchTaskRequest body) {
var task = tasksService.updateTask(id, body.status(), body.assignedTo());
return buildResponse(task);
}
private TaskResponse buildResponse(final TaskRecord task) {
return new TaskResponse(task.getId(), task.getTitle(), task.getCreated(), task.getCreatedBy(), task.getAssignedTo(), task.getStatus());
}
@ExceptionHandler(UnknownTaskException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleUnknownTask() {
}
}

View File

@ -0,0 +1,5 @@
{
"local-tasks": {
"host": "localhost:8082"
}
}

View File

@ -0,0 +1,35 @@
GET http://{{host}}/createdemoapplication1 HTTP/1.1
###
GET http://{{host}}/unknown HTTP/1.1
###
GET http://{{host}}?status=PENDING
###
GET http://{{host}}?createdBy=baeldung
###
GET http://{{host}}?createdBy=baeldung&status=COMPLETE
###
DELETE http://{{host}}/createdemoapplication1 HTTP/1.1
###
DELETE http://{{host}}/unknown HTTP/1.1
###
POST http://{{host}} HTTP/1.1
Content-Type: application/json
{
"title": "My Task",
"createdBy": "graham"
}
###
PATCH http://{{host}}/createdemoapplication1 HTTP/1.1
Content-Type: application/json
{
"status": "COMPLETE"
}

View File

@ -0,0 +1,18 @@
package com.baeldung.tasksservice.adapters.jms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;
import com.baeldung.tasksservice.service.DeletedUserService;
@Service
public class JmsConsumer {
@Autowired
private DeletedUserService deletedUserService;
@JmsListener(destination = "deleted_user")
public void receive(String user) {
deletedUserService.handleDeletedUser(user);
}
}

View File

@ -0,0 +1,80 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.repository;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "tasks")
public class TaskRecord {
@Id
@Column(name = "task_id")
private String id;
private String title;
@Column(name = "created_at")
private Instant created;
@Column(name = "created_by")
private String createdBy;
@Column(name = "assigned_to")
private String assignedTo;
private String status;
public TaskRecord(final String id, final String title, final Instant created, final String createdBy, final String assignedTo, final String status) {
this.id = id;
this.title = title;
this.created = created;
this.createdBy = createdBy;
this.assignedTo = assignedTo;
this.status = status;
}
private TaskRecord() {
// Needed for JPA
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public Instant getCreated() {
return created;
}
public String getCreatedBy() {
return createdBy;
}
public String getAssignedTo() {
return assignedTo;
}
public String getStatus() {
return status;
}
public void setAssignedTo(final String assignedTo) {
this.assignedTo = assignedTo;
}
public void setStatus(final String status) {
this.status = status;
}
}

View File

@ -0,0 +1,28 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.adapters.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TasksRepository extends JpaRepository<TaskRecord, String> {
List<TaskRecord> findByStatus(String status);
List<TaskRecord> findByCreatedBy(String createdBy);
List<TaskRecord> findByStatusAndCreatedBy(String status, String createdBy);
List<TaskRecord> findByAssignedTo(String assignedTo);
}

View File

@ -0,0 +1,27 @@
package com.baeldung.tasksservice.service;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baeldung.tasksservice.adapters.repository.TaskRecord;
import com.baeldung.tasksservice.adapters.repository.TasksRepository;
@Service
public class DeletedUserService {
@Autowired
private TasksRepository tasksRepository;
@Transactional
public void handleDeletedUser(String user) {
var ownedByUser = tasksRepository.findByCreatedBy(user);
tasksRepository.deleteAll(ownedByUser);
var assignedToUser = tasksRepository.findByAssignedTo(user);
for (TaskRecord record : assignedToUser) {
record.setAssignedTo(null);
record.setStatus("PENDING");
}
}
}

View File

@ -0,0 +1,73 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.service;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baeldung.tasksservice.adapters.repository.TaskRecord;
import com.baeldung.tasksservice.adapters.repository.TasksRepository;
@Service
public class TasksService {
@Autowired
private TasksRepository tasksRepository;
public TaskRecord getTaskById(String id) {
return tasksRepository.findById(id)
.orElseThrow(() -> new UnknownTaskException(id));
}
@Transactional
public void deleteTaskById(String id) {
var task = tasksRepository.findById(id)
.orElseThrow(() -> new UnknownTaskException(id));
tasksRepository.delete(task);
}
public List<TaskRecord> search(Optional<String> createdBy, Optional<String> status) {
if (createdBy.isPresent() && status.isPresent()) {
return tasksRepository.findByStatusAndCreatedBy(status.get(), createdBy.get());
} else if (createdBy.isPresent()) {
return tasksRepository.findByCreatedBy(createdBy.get());
} else if (status.isPresent()) {
return tasksRepository.findByStatus(status.get());
} else {
return tasksRepository.findAll();
}
}
@Transactional
public TaskRecord updateTask(String id, Optional<String> newStatus, Optional<String> newAssignedTo) {
var task = tasksRepository.findById(id)
.orElseThrow(() -> new UnknownTaskException(id));
newStatus.ifPresent(task::setStatus);
newAssignedTo.ifPresent(task::setAssignedTo);
return task;
}
public TaskRecord createTask(String title, String createdBy) {
var task = new TaskRecord(UUID.randomUUID()
.toString(), title, Instant.now(), createdBy, null, "PENDING");
tasksRepository.save(task);
return task;
}
}

View File

@ -0,0 +1,24 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.tasksservice.service;
public class UnknownTaskException extends RuntimeException {
private final String id;
public UnknownTaskException(final String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,9 @@
server.port=8082
spring.artemis.mode=EMBEDDED
spring.artemis.host=localhost
spring.artemis.port=61616
spring.artemis.embedded.enabled=true
spring.jms.template.default-destination=my-queue-1
logging.level.org.apache.activemq.audit.base=WARN
logging.level.org.apache.activemq.audit.message=WARN

View File

@ -0,0 +1,13 @@
CREATE TABLE tasks (
task_id VARCHAR(36) PRIMARY KEY,
title VARCHAR(100) NOT NULL,
created_at DATETIME NOT NULL,
created_by VARCHAR(36) NOT NULL,
assigned_to VARCHAR(36) NULL,
status VARCHAR(20) NOT NULL
);
INSERT INTO tasks(task_id, title, created_at, created_by, assigned_to, status) VALUES
('createdemoapplication1', 'Create demo applications - Tasks', '2022-05-05 12:34:56', 'baeldung', 'coxg', 'IN_PROGRESS'),
('createdemoapplication2', 'Create demo applications - Users', '2022-05-05 12:34:56', 'baeldung', NULL, 'PENDING'),
('createdemoapplication3', 'Create demo applications - API', '2022-05-05 12:34:56', 'baeldung', NULL, 'PENDING');

33
lightrun/users-service/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,36 @@
# Lightrun Example Application - Tasks Management
This application exists as an example for the Lightrun series of articles.
## Building
This application requires [Apache Maven](https://maven.apache.org/) and [Java 17+](https://www.oracle.com/java/technologies/downloads/). It does use the Maven Wrapper, so it can be built with only Java available on the path.
As such, building the code is done by executing:
```
$ ./mvnw install
```
from the top level.
## Running
The application consists of three services:
* Tasks
* Users
* API
These are all Spring Boot applications.
The Tasks and Users services exist as microservices for managing one facet of data. Each uses a database, and utilise a JMS queue between them as well. For convenience this infrastructure is all embedded in the applications.
This does mean that the startup order is important. The JMS queue exists within the Tasks service and is connected to from the Users service. As such, the Tasks service must be started before the others.
Each service can be started either by executing `mvn spring-boot:run` from within the appropriate directory. Alternatively, as Spring Boot applications, the build will produce an executable JAR file within the `target` directory that can be executed as, for
example:
```
$ java -jar ./target/tasks-service-0.0.1-SNAPSHOT.jar
```

View 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.baeldung</groupId>
<artifactId>users-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>users-service</name>
<description>Users Service for LightRun Article</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,29 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
@Configuration
public class JmsConfig {
@Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.usersservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UsersServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UsersServiceApplication.class, args);
}
}

View File

@ -0,0 +1,15 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.http;
public record CreateUserRequest(String name) {
}

View File

@ -0,0 +1,17 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.http;
import java.util.Optional;
public record PatchUserRequest(Optional<String> name) {
}

View File

@ -0,0 +1,15 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.http;
public record UserResponse(String id, String name) {
}

View File

@ -0,0 +1,70 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.http;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.usersservice.adapters.repository.UserRecord;
import com.baeldung.usersservice.service.UnknownUserException;
import com.baeldung.usersservice.service.UsersService;
@RestController
@RequestMapping("/")
class UsersController {
@Autowired
private UsersService usersService;
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable("id") String id) {
var user = usersService.getUserById(id);
return buildResponse(user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable("id") String id) {
usersService.deleteUserById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(@RequestBody CreateUserRequest body) {
var user = usersService.createUser(body.name());
return buildResponse(user);
}
@PatchMapping("/{id}")
public UserResponse patchUser(@PathVariable("id") String id, @RequestBody PatchUserRequest body) {
var user = usersService.updateUser(id, body.name());
return buildResponse(user);
}
private UserResponse buildResponse(final UserRecord user) {
return new UserResponse(user.getId(), user.getName());
}
@ExceptionHandler(UnknownUserException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleUnknownUser() {
}
}

View File

@ -0,0 +1,5 @@
{
"local-users": {
"host": "localhost:8081"
}
}

View File

@ -0,0 +1,25 @@
GET http://{{host}}/baeldung HTTP/1.1
###
GET http://{{host}}/unknown HTTP/1.1
###
DELETE http://{{host}}/baeldung HTTP/1.1
###
DELETE http://{{host}}/unknown HTTP/1.1
###
POST http://{{host}} HTTP/1.1
Content-Type: application/json
{
"name": "Testing"
}
###
PATCH http://{{host}}/coxg HTTP/1.1
Content-Type: application/json
{
"name": "Test Name"
}

View File

@ -0,0 +1,26 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.jms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class JmsSender {
@Autowired
private JmsTemplate jmsTemplate;
public void sendDeleteUserMessage(String userId) {
jmsTemplate.convertAndSend("deleted_user", userId);
}
}

View File

@ -0,0 +1,47 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.repository;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class UserRecord {
@Id
@Column(name = "user_id")
private String id;
private String name;
public UserRecord(final String id, final String name) {
this.id = id;
this.name = name;
}
private UserRecord() {
// Needed for JPA
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}

View File

@ -0,0 +1,19 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.adapters.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UsersRepository extends JpaRepository<UserRecord, String> {
}

View File

@ -0,0 +1,24 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.service;
public class UnknownUserException extends RuntimeException {
private final String id;
public UnknownUserException(final String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,64 @@
/****************************************************************************************************************
*
* Copyright (c) 2022 OCLC, Inc. All Rights Reserved.
*
* OCLC proprietary information: the enclosed materials contain
* proprietary information of OCLC, Inc. and shall not be disclosed in whole or in
* any part to any third party or used by any person for any purpose, without written
* consent of OCLC, Inc. Duplication of any portion of these materials shall include this notice.
*
******************************************************************************************************************/
package com.baeldung.usersservice.service;
import java.util.Optional;
import java.util.UUID;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baeldung.usersservice.adapters.jms.JmsSender;
import com.baeldung.usersservice.adapters.repository.UserRecord;
import com.baeldung.usersservice.adapters.repository.UsersRepository;
@Service
public class UsersService {
@Autowired
private UsersRepository usersRepository;
@Autowired
private JmsSender jmsSender;
public UserRecord getUserById(String id) {
return usersRepository.findById(id)
.orElseThrow(() -> new UnknownUserException(id));
}
@Transactional
public void deleteUserById(String id) {
var user = usersRepository.findById(id)
.orElseThrow(() -> new UnknownUserException(id));
usersRepository.delete(user);
jmsSender.sendDeleteUserMessage(id);
}
@Transactional
public UserRecord updateUser(String id, Optional<String> newName) {
var user = usersRepository.findById(id)
.orElseThrow(() -> new UnknownUserException(id));
newName.ifPresent(user::setName);
return user;
}
public UserRecord createUser(String name) {
var user = new UserRecord(UUID.randomUUID()
.toString(), name);
usersRepository.save(user);
return user;
}
}

View File

@ -0,0 +1,7 @@
server.port=8081
spring.artemis.host=localhost
spring.artemis.port=61616
spring.jms.template.default-destination=my-queue-1
logging.level.org.apache.activemq.audit.base=WARN
logging.level.org.apache.activemq.audit.message=WARN

View File

@ -0,0 +1,8 @@
CREATE TABLE users (
user_id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
INSERT INTO users(user_id, name) VALUES
('baeldung', 'Baeldung'),
('coxg', 'Graham');

View File

@ -5,7 +5,6 @@ import com.google.common.flogger.LoggerConfig;
import com.google.common.flogger.StackSize;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.IntStream;
@ -25,13 +24,6 @@ public class FloggerIntegrationTest {
});
}
@Test
public void givenATimeInterval_shouldLogAfterEveryTimeInterval() {
IntStream.range(0, 1_000_0000).forEach(value -> {
logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value);
});
}
@Test
public void givenAnObject_shouldLogTheObject() {
User user = new User();

View File

@ -0,0 +1,25 @@
package com.baeldung.flogger;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.junit.Test;
import com.google.common.flogger.FluentLogger;
public class FloggerManualTest {
static {
// System.setProperty("flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
System.setProperty("flogger.backend_factory",
"com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");
}
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Test
public void givenATimeInterval_shouldLogAfterEveryTimeInterval() {
IntStream.range(0, 1_000_0000).forEach(value -> {
logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value);
});
}
}

View File

@ -1209,6 +1209,7 @@
<module>jenkins/plugins</module>
<module>jhipster</module>
<module>jhipster-5</module>
<module>jws</module>
<module>libraries</module> <!-- very long running -->
@ -1329,6 +1330,7 @@
<module>spring-boot-modules/spring-boot-camel</module>
<module>testing-modules/testing-assertions</module>
<module>persistence-modules/fauna</module>
<module>lightrun</module>
</modules>
</profile>
@ -1396,6 +1398,7 @@
<module>spring-boot-modules/spring-boot-camel</module>
<module>testing-modules/testing-assertions</module>
<module>persistence-modules/fauna</module>
<module>lightrun</module>
</modules>
</profile>
</profiles>

View File

@ -1 +1,2 @@
app.name=restx-demo
restx.stats.share.enable=false

View File

@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Example1IntegrationTest {
public class Example1ManualTest {
@Test
public void test1a() {

View File

@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Example2IntegrationTest {
public class Example2ManualTest {
@Test
public void test1a() {

View File

@ -5,18 +5,18 @@ import org.junit.experimental.ParallelComputer;
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;
public class ParallelIntegrationTest {
public class ParallelManualTest {
@Test
public void runTests() {
final Class<?>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
final Class<?>[] classes = { Example1ManualTest.class, Example2ManualTest.class };
JUnitCore.runClasses(new Computer(), classes);
}
@Test
public void runTestsInParallel() {
final Class<?>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
final Class<?>[] classes = { Example1ManualTest.class, Example2ManualTest.class };
JUnitCore.runClasses(new ParallelComputer(true, true), classes);
}

View File

@ -1,24 +1,24 @@
package com.baeldung.jupiter;
import com.baeldung.Example1IntegrationTest;
import com.baeldung.Example2IntegrationTest;
import com.baeldung.Example1ManualTest;
import com.baeldung.Example2ManualTest;
import org.junit.experimental.ParallelComputer;
import org.junit.jupiter.api.Test;
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;
class Spring5JUnit5ParallelIntegrationTest {
class Spring5JUnit5ParallelManualTest {
@Test
void givenTwoTestClasses_whenJUnitRunParallel_thenTheTestsExecutingParallel() {
final Class<?>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
final Class<?>[] classes = { Example1ManualTest.class, Example2ManualTest.class };
JUnitCore.runClasses(new ParallelComputer(true, true), classes);
}
@Test
void givenTwoTestClasses_whenJUnitRunParallel_thenTheTestsExecutingLinear() {
final Class<?>[] classes = { Example1IntegrationTest.class, Example2IntegrationTest.class };
final Class<?>[] classes = { Example1ManualTest.class, Example2ManualTest.class };
JUnitCore.runClasses(new Computer(), classes);
}

View File

@ -24,6 +24,8 @@ public class CustomUserAttrController {
final Principal principal = (Principal) authentication.getPrincipal();
String dob = "";
String userIdByToken = "";
String userIdByMapper = "";
if (principal instanceof KeycloakPrincipal) {
@ -31,6 +33,9 @@ public class CustomUserAttrController {
IDToken token = kPrincipal.getKeycloakSecurityContext()
.getIdToken();
userIdByToken = token.getSubject();
userIdByMapper = token.getOtherClaims().get("user_id").toString();
Map<String, Object> customClaims = token.getOtherClaims();
if (customClaims.containsKey("DOB")) {
@ -39,6 +44,8 @@ public class CustomUserAttrController {
}
model.addAttribute("username", principal.getName());
model.addAttribute("userIDByToken", userIdByToken);
model.addAttribute("userIDByMapper", userIdByMapper);
model.addAttribute("dob", dob);
return "userInfo";
}

View File

@ -0,0 +1,9 @@
### server port
server.port=8080
#Keycloak Configuration
keycloak.auth-server-url=http://localhost:8083/auth
keycloak.realm=baeldung
keycloak.resource=customerClient
keycloak.public-client=true
keycloak.principal-attribute=preferred_username

View File

@ -7,6 +7,12 @@
<h1>
Hello, <span th:text="${username}">--name--</span>.
</h1>
<h1>
User ID By Token: <span th:text="${userIDByToken}">--userID--</span>.
</h1>
<h1>
User ID By Mapper: <span th:text="${userIDByMapper}">--userID--</span>.
</h1>
<h3>
Your Date of Birth as per our records is <span th:text="${dob}" />.
</h3>

View File

@ -24,7 +24,7 @@ import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootPropertiesTestApplication.class)
public class PropertiesReloadIntegrationTest {
public class PropertiesReloadManualTest {
protected MockMvc mvc;

View File

@ -1,4 +1,4 @@
saml.keystore.location=classpath:/saml/samlKeystore.jks
saml.keystore.location=classpath:/saml/saml-keystore
# Password for Java keystore and item therein
saml.keystore.password=<key_pass>
saml.keystore.alias=<key_alias>
@ -6,3 +6,4 @@ saml.keystore.alias=<key_alias>
# SAML Entity ID extracted from top of SAML metadata file
saml.idp=<idp_issuer_url>
saml.sp=http://localhost:8080/saml/metadata
spring.main.allow-circular-references=true

View File

@ -0,0 +1,15 @@
package com.baeldung.requestmappingvalue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/${request.value}")
public class WelcomeController {
@GetMapping
public String getWelcomeMessage() {
return "Welcome to Baeldung!";
}
}

View File

@ -1 +1,2 @@
server.servlet.context-path=/spring-mvc-basics
request.value=welcome

View File

@ -0,0 +1,25 @@
package com.baeldung.requestmappingvalue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
class WelcomeControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenUserAccessToWelcome_thenReturnOK() throws Exception {
this.mockMvc.perform(get("/welcome"))
.andExpect(status().isOk());
}
}