Merge branch 'master' into readme-description-edits
This commit is contained in:
commit
744e6e9f0d
|
@ -0,0 +1,36 @@
|
|||
package com.baeldung.algorithms.prim;
|
||||
|
||||
public class Edge {
|
||||
|
||||
private int weight;
|
||||
private boolean isIncluded = false;
|
||||
private boolean isPrinted = false;
|
||||
|
||||
public Edge(int weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(int weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public boolean isIncluded() {
|
||||
return isIncluded;
|
||||
}
|
||||
|
||||
public void setIncluded(boolean included) {
|
||||
isIncluded = included;
|
||||
}
|
||||
|
||||
public boolean isPrinted() {
|
||||
return isPrinted;
|
||||
}
|
||||
|
||||
public void setPrinted(boolean printed) {
|
||||
isPrinted = printed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.baeldung.algorithms.prim;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
|
||||
public class Prim {
|
||||
|
||||
private List<Vertex> graph;
|
||||
|
||||
public Prim(List<Vertex> graph){
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
public void run(){
|
||||
if (graph.size() > 0){
|
||||
graph.get(0).setVisited(true);
|
||||
}
|
||||
while (isDisconnected()){
|
||||
Edge nextMinimum = new Edge(Integer.MAX_VALUE);
|
||||
Vertex nextVertex = graph.get(0);
|
||||
for (Vertex vertex : graph){
|
||||
if (vertex.isVisited()){
|
||||
Pair<Vertex, Edge> candidate = vertex.nextMinimum();
|
||||
if (candidate.getValue().getWeight() < nextMinimum.getWeight()){
|
||||
nextMinimum = candidate.getValue();
|
||||
nextVertex = candidate.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
nextMinimum.setIncluded(true);
|
||||
nextVertex.setVisited(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDisconnected(){
|
||||
for (Vertex vertex : graph){
|
||||
if (!vertex.isVisited()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String originalGraphToString(){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Vertex vertex : graph){
|
||||
sb.append(vertex.originalToString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void resetPrintHistory(){
|
||||
for (Vertex vertex : graph){
|
||||
Iterator<Map.Entry<Vertex,Edge>> it = vertex.getEdges().entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Vertex,Edge> pair = it.next();
|
||||
pair.getValue().setPrinted(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String minimumSpanningTreeToString(){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Vertex vertex : graph){
|
||||
sb.append(vertex.includedToString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package com.baeldung.algorithms.prim;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.math3.util.Pair;
|
||||
|
||||
public class Vertex {
|
||||
|
||||
private String label = null;
|
||||
private Map<Vertex, Edge> edges = new HashMap<>();
|
||||
private boolean isVisited = false;
|
||||
|
||||
public Vertex(String label){
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public Map<Vertex, Edge> getEdges() {
|
||||
return edges;
|
||||
}
|
||||
|
||||
public void addEdge(Vertex vertex, Edge edge){
|
||||
if (this.edges.containsKey(vertex)){
|
||||
if (edge.getWeight() < this.edges.get(vertex).getWeight()){
|
||||
this.edges.replace(vertex, edge);
|
||||
}
|
||||
} else {
|
||||
this.edges.put(vertex, edge);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisited() {
|
||||
return isVisited;
|
||||
}
|
||||
|
||||
public void setVisited(boolean visited) {
|
||||
isVisited = visited;
|
||||
}
|
||||
|
||||
public Pair<Vertex, Edge> nextMinimum(){
|
||||
Edge nextMinimum = new Edge(Integer.MAX_VALUE);
|
||||
Vertex nextVertex = this;
|
||||
Iterator<Map.Entry<Vertex,Edge>> it = edges.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Vertex,Edge> pair = it.next();
|
||||
if (!pair.getKey().isVisited()){
|
||||
if (!pair.getValue().isIncluded()) {
|
||||
if (pair.getValue().getWeight() < nextMinimum.getWeight()) {
|
||||
nextMinimum = pair.getValue();
|
||||
nextVertex = pair.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Pair<>(nextVertex, nextMinimum);
|
||||
}
|
||||
|
||||
public String originalToString(){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Iterator<Map.Entry<Vertex,Edge>> it = edges.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Vertex,Edge> pair = it.next();
|
||||
if (!pair.getValue().isPrinted()) {
|
||||
sb.append(getLabel());
|
||||
sb.append(" --- ");
|
||||
sb.append(pair.getValue().getWeight());
|
||||
sb.append(" --- ");
|
||||
sb.append(pair.getKey().getLabel());
|
||||
sb.append("\n");
|
||||
pair.getValue().setPrinted(true);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String includedToString(){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isVisited()) {
|
||||
Iterator<Map.Entry<Vertex,Edge>> it = edges.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Vertex,Edge> pair = it.next();
|
||||
if (pair.getValue().isIncluded()) {
|
||||
if (!pair.getValue().isPrinted()) {
|
||||
sb.append(getLabel());
|
||||
sb.append(" --- ");
|
||||
sb.append(pair.getValue().getWeight());
|
||||
sb.append(" --- ");
|
||||
sb.append(pair.getKey().getLabel());
|
||||
sb.append("\n");
|
||||
pair.getValue().setPrinted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.baeldung.algorithms.prim;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PrimUnitTest {
|
||||
|
||||
@Test
|
||||
public void givenAGraph_whenPrimRuns_thenPrintMST() {
|
||||
Prim prim = new Prim(createGraph());
|
||||
System.out.println(prim.originalGraphToString());
|
||||
System.out.println("----------------");
|
||||
prim.run();
|
||||
System.out.println();
|
||||
prim.resetPrintHistory();
|
||||
System.out.println(prim.minimumSpanningTreeToString());
|
||||
}
|
||||
|
||||
public static List<Vertex> createGraph() {
|
||||
List<Vertex> graph = new ArrayList<>();
|
||||
Vertex a = new Vertex("A");
|
||||
Vertex b = new Vertex("B");
|
||||
Vertex c = new Vertex("C");
|
||||
Vertex d = new Vertex("D");
|
||||
Vertex e = new Vertex("E");
|
||||
Edge ab = new Edge(2);
|
||||
a.addEdge(b, ab);
|
||||
b.addEdge(a, ab);
|
||||
Edge ac = new Edge(3);
|
||||
a.addEdge(c, ac);
|
||||
c.addEdge(a, ac);
|
||||
Edge bc = new Edge(2);
|
||||
b.addEdge(c, bc);
|
||||
c.addEdge(b, bc);
|
||||
Edge be = new Edge(5);
|
||||
b.addEdge(e, be);
|
||||
e.addEdge(b, be);
|
||||
Edge cd = new Edge(1);
|
||||
c.addEdge(d, cd);
|
||||
d.addEdge(c, cd);
|
||||
Edge ce = new Edge(1);
|
||||
c.addEdge(e, ce);
|
||||
e.addEdge(c, ce);
|
||||
graph.add(a);
|
||||
graph.add(b);
|
||||
graph.add(c);
|
||||
graph.add(d);
|
||||
graph.add(e);
|
||||
return graph;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
### Relevant Articles
|
||||
|
||||
- [Introduction to Apache CXF Aegis Data Binding](https://www.baeldung.com/aegis-data-binding-in-apache-cxf)
|
|
@ -0,0 +1,3 @@
|
|||
### Relevant Articles:
|
||||
|
||||
- [A Guide to Apache CXF with Spring](https://www.baeldung.com/apache-cxf-with-spring)
|
|
@ -0,0 +1,4 @@
|
|||
### Relevant Articles:
|
||||
|
||||
- [OData Protocol Guide](https://www.baeldung.com/odata)
|
||||
- [Intro to OData with Olingo](https://www.baeldung.com/olingo)
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,29 @@
|
|||
participant "Client 1" as C1
|
||||
participant "Client 2" as C2
|
||||
participant "Reactive Web App" as RWS
|
||||
participant "Backend" as S3
|
||||
C1 -> RWS: POST
|
||||
activate C1
|
||||
activate RWS
|
||||
RWS -> S3: Async POST
|
||||
deactivate RWS
|
||||
C2 -> RWS: POST
|
||||
activate C2
|
||||
activate RWS
|
||||
RWS -> S3: Async POST
|
||||
deactivate RWS
|
||||
S3 --> RWS: Async Result
|
||||
activate RWS
|
||||
RWS -->C2: Result
|
||||
deactivate RWS
|
||||
deactivate C2
|
||||
// First file EOF
|
||||
S3 --> RWS: Async Result
|
||||
activate RWS
|
||||
RWS -->C1: Result
|
||||
deactivate RWS
|
||||
deactivate C1
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
|
@ -0,0 +1,28 @@
|
|||
participant Client 1
|
||||
participant Client 2
|
||||
participant Controller
|
||||
participant Backend
|
||||
Client 1-> Controller: POST Data
|
||||
activate Client 1
|
||||
activate Controller
|
||||
Controller -> Backend: Save Data
|
||||
activate Backend
|
||||
note left of Controller #yellow: Controller blocked\nuntil result received
|
||||
Backend --> Controller: Result
|
||||
deactivate Backend
|
||||
Controller --> Client 1: Result
|
||||
deactivate Client 1
|
||||
deactivate Controller
|
||||
// 2nd Upload
|
||||
Client 2-> Controller: POST Data
|
||||
activate Client 2
|
||||
activate Controller
|
||||
Controller -> Backend: Save Data
|
||||
activate Backend
|
||||
note left of Controller #yellow: Controller blocket\nuntil result received
|
||||
Backend --> Controller: Result
|
||||
deactivate Backend
|
||||
Controller --> Client 2: Result
|
||||
deactivate Controller
|
||||
deactivate Client 2
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>aws-reactive</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>aws-reactive</name>
|
||||
<description>AWS Reactive Sample</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<!-- Import dependency management from Spring Boot -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>bom</artifactId>
|
||||
<version>2.10.27</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,32 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.http.SdkHttpResponse;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class DownloadFailedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int statusCode;
|
||||
private Optional<String> statusText;
|
||||
|
||||
public DownloadFailedException(SdkResponse response) {
|
||||
|
||||
SdkHttpResponse httpResponse = response.sdkHttpResponse();
|
||||
if (httpResponse != null) {
|
||||
this.statusCode = httpResponse.statusCode();
|
||||
this.statusText = httpResponse.statusText();
|
||||
} else {
|
||||
this.statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
this.statusText = Optional.of("UNKNOWN");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.ResponseEntity.BodyBuilder;
|
||||
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.RestController;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import software.amazon.awssdk.core.ResponseBytes;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
||||
import software.amazon.awssdk.core.async.SdkPublisher;
|
||||
import software.amazon.awssdk.core.internal.async.ByteArrayAsyncResponseTransformer;
|
||||
import software.amazon.awssdk.http.SdkHttpResponse;
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
|
||||
|
||||
/**
|
||||
* @author Philippe
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/inbox")
|
||||
@Slf4j
|
||||
public class DownloadResource {
|
||||
|
||||
|
||||
private final S3AsyncClient s3client;
|
||||
private final S3ClientConfigurarionProperties s3config;
|
||||
|
||||
public DownloadResource(S3AsyncClient s3client, S3ClientConfigurarionProperties s3config) {
|
||||
this.s3client = s3client;
|
||||
this.s3config = s3config;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping(path="/{filekey}")
|
||||
public Mono<ResponseEntity<Flux<ByteBuffer>>> downloadFile(@PathVariable("filekey") String filekey) {
|
||||
|
||||
GetObjectRequest request = GetObjectRequest.builder()
|
||||
.bucket(s3config.getBucket())
|
||||
.key(filekey)
|
||||
.build();
|
||||
|
||||
return Mono.fromFuture(s3client.getObject(request,new FluxResponseProvider()))
|
||||
.map( (response) -> {
|
||||
checkResult(response.sdkResponse);
|
||||
String filename = getMetadataItem(response.sdkResponse,"filename",filekey);
|
||||
|
||||
log.info("[I65] filename={}, length={}",filename, response.sdkResponse.contentLength() );
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_TYPE, response.sdkResponse.contentType())
|
||||
.header(HttpHeaders.CONTENT_LENGTH, Long.toString(response.sdkResponse.contentLength()))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
|
||||
.body(response.flux);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a metadata key in a case-insensitive way.
|
||||
* @param sdkResponse
|
||||
* @param key
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
private String getMetadataItem(GetObjectResponse sdkResponse, String key, String defaultValue) {
|
||||
for( Entry<String, String> entry : sdkResponse.metadata().entrySet()) {
|
||||
if ( entry.getKey().equalsIgnoreCase(key)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
// Helper used to check return codes from an API call
|
||||
private static void checkResult(GetObjectResponse response) {
|
||||
SdkHttpResponse sdkResponse = response.sdkHttpResponse();
|
||||
if ( sdkResponse != null && sdkResponse.isSuccessful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DownloadFailedException(response);
|
||||
}
|
||||
|
||||
|
||||
static class FluxResponseProvider implements AsyncResponseTransformer<GetObjectResponse,FluxResponse> {
|
||||
|
||||
private FluxResponse response;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<FluxResponse> prepare() {
|
||||
response = new FluxResponse();
|
||||
return response.cf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(GetObjectResponse sdkResponse) {
|
||||
this.response.sdkResponse = sdkResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStream(SdkPublisher<ByteBuffer> publisher) {
|
||||
response.flux = Flux.from(publisher);
|
||||
response.cf.complete(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionOccurred(Throwable error) {
|
||||
response.cf.completeExceptionally(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the API response and stream
|
||||
* @author Philippe
|
||||
*/
|
||||
static class FluxResponse {
|
||||
|
||||
final CompletableFuture<FluxResponse> cf = new CompletableFuture<>();
|
||||
GetObjectResponse sdkResponse;
|
||||
Flux<ByteBuffer> flux;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ReactiveS3Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ReactiveS3Application.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
@ConfigurationProperties(prefix = "aws.s3")
|
||||
@Data
|
||||
public class S3ClientConfigurarionProperties {
|
||||
|
||||
private Region region = Region.US_EAST_1;
|
||||
private URI endpoint = null;
|
||||
|
||||
private String accessKeyId;
|
||||
private String secretAccessKey;
|
||||
|
||||
// Bucket name we'll be using as our backend storage
|
||||
private String bucket;
|
||||
|
||||
// AWS S3 requires that file parts must have at least 5MB, except
|
||||
// for the last part. This may change for other S3-compatible services, so let't
|
||||
// define a configuration property for that
|
||||
private int multipartMinPartSize = 5*1024*1024;
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
|
||||
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClientBuilder;
|
||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||
import software.amazon.awssdk.utils.StringUtils;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(S3ClientConfigurarionProperties.class)
|
||||
public class S3ClientConfiguration {
|
||||
|
||||
@Bean
|
||||
public S3AsyncClient s3client(S3ClientConfigurarionProperties s3props, AwsCredentialsProvider credentialsProvider) {
|
||||
|
||||
SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder()
|
||||
.writeTimeout(Duration.ZERO)
|
||||
.maxConcurrency(64)
|
||||
.build();
|
||||
|
||||
S3Configuration serviceConfiguration = S3Configuration.builder()
|
||||
.checksumValidationEnabled(false)
|
||||
.chunkedEncodingEnabled(true)
|
||||
.build();
|
||||
|
||||
S3AsyncClientBuilder b = S3AsyncClient.builder()
|
||||
.httpClient(httpClient)
|
||||
.region(s3props.getRegion())
|
||||
.credentialsProvider(credentialsProvider)
|
||||
.serviceConfiguration(serviceConfiguration);
|
||||
|
||||
if (s3props.getEndpoint() != null) {
|
||||
b = b.endpointOverride(s3props.getEndpoint());
|
||||
}
|
||||
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AwsCredentialsProvider awsCredentialsProvider(S3ClientConfigurarionProperties s3props) {
|
||||
|
||||
if (StringUtils.isBlank(s3props.getAccessKeyId())) {
|
||||
// Return default provider
|
||||
return DefaultCredentialsProvider.create();
|
||||
}
|
||||
else {
|
||||
// Return custom credentials provider
|
||||
return () -> {
|
||||
AwsCredentials creds = AwsBasicCredentials.create(s3props.getAccessKeyId(), s3props.getSecretAccessKey());
|
||||
return creds;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.http.SdkHttpResponse;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class UploadFailedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int statusCode;
|
||||
private Optional<String> statusText;
|
||||
|
||||
public UploadFailedException(SdkResponse response) {
|
||||
|
||||
SdkHttpResponse httpResponse = response.sdkHttpResponse();
|
||||
if (httpResponse != null) {
|
||||
this.statusCode = httpResponse.statusCode();
|
||||
this.statusText = httpResponse.statusText();
|
||||
} else {
|
||||
this.statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
this.statusText = Optional.of("UNKNOWN");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.core.async.AsyncRequestBody;
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
|
||||
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse;
|
||||
import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload;
|
||||
import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload.Builder;
|
||||
import software.amazon.awssdk.services.s3.model.CompletedPart;
|
||||
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
|
||||
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
|
||||
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
|
||||
import software.amazon.awssdk.services.s3.model.UploadPartResponse;
|
||||
|
||||
/**
|
||||
* @author Philippe
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/inbox")
|
||||
@Slf4j
|
||||
public class UploadResource {
|
||||
|
||||
private final S3AsyncClient s3client;
|
||||
private final S3ClientConfigurarionProperties s3config;
|
||||
|
||||
public UploadResource(S3AsyncClient s3client, S3ClientConfigurarionProperties s3config) {
|
||||
this.s3client = s3client;
|
||||
this.s3config = s3config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard file upload.
|
||||
*/
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<UploadResult>> uploadHandler(@RequestHeader HttpHeaders headers, @RequestBody Flux<ByteBuffer> body) {
|
||||
|
||||
long length = headers.getContentLength();
|
||||
if (length < 0) {
|
||||
throw new UploadFailedException(HttpStatus.BAD_REQUEST.value(), Optional.of("required header missing: Content-Length"));
|
||||
}
|
||||
|
||||
String fileKey = UUID.randomUUID().toString();
|
||||
Map<String, String> metadata = new HashMap<String, String>();
|
||||
MediaType mediaType = headers.getContentType();
|
||||
|
||||
if (mediaType == null) {
|
||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
log.info("[I95] uploadHandler: mediaType{}, length={}", mediaType, length);
|
||||
CompletableFuture<PutObjectResponse> future = s3client
|
||||
.putObject(PutObjectRequest.builder()
|
||||
.bucket(s3config.getBucket())
|
||||
.contentLength(length)
|
||||
.key(fileKey.toString())
|
||||
.contentType(mediaType.toString())
|
||||
.metadata(metadata)
|
||||
.build(),
|
||||
AsyncRequestBody.fromPublisher(body));
|
||||
|
||||
return Mono.fromFuture(future)
|
||||
.map((response) -> {
|
||||
checkResult(response);
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.CREATED)
|
||||
.body(new UploadResult(HttpStatus.CREATED, new String[] {fileKey}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Multipart file upload
|
||||
* @param bucket
|
||||
* @param parts
|
||||
* @param headers
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, method = {RequestMethod.POST, RequestMethod.PUT})
|
||||
public Mono<ResponseEntity<UploadResult>> multipartUploadHandler(@RequestHeader HttpHeaders headers, @RequestBody Flux<Part> parts ) {
|
||||
|
||||
return parts
|
||||
.ofType(FilePart.class) // We'll ignore other data for now
|
||||
.flatMap((part) -> saveFile(headers, s3config.getBucket(), part))
|
||||
.collect(Collectors.toList())
|
||||
.map((keys) -> ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(new UploadResult(HttpStatus.CREATED,keys)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save file using a multipart upload. This method does not require any temporary
|
||||
* storage at the REST service
|
||||
* @param headers
|
||||
* @param bucket Bucket name
|
||||
* @param part Uploaded file
|
||||
* @return
|
||||
*/
|
||||
protected Mono<String> saveFile(HttpHeaders headers,String bucket, FilePart part) {
|
||||
|
||||
// Generate a filekey for this upload
|
||||
String filekey = UUID.randomUUID().toString();
|
||||
|
||||
log.info("[I137] saveFile: filekey={}, filename={}", filekey, part.filename());
|
||||
|
||||
// Gather metadata
|
||||
Map<String, String> metadata = new HashMap<String, String>();
|
||||
String filename = part.filename();
|
||||
if ( filename == null ) {
|
||||
filename = filekey;
|
||||
}
|
||||
|
||||
metadata.put("filename", filename);
|
||||
|
||||
MediaType mt = part.headers().getContentType();
|
||||
if ( mt == null ) {
|
||||
mt = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
// Create multipart upload request
|
||||
CompletableFuture<CreateMultipartUploadResponse> uploadRequest = s3client
|
||||
.createMultipartUpload(CreateMultipartUploadRequest.builder()
|
||||
.contentType(mt.toString())
|
||||
.key(filekey)
|
||||
.metadata(metadata)
|
||||
.bucket(bucket)
|
||||
.build());
|
||||
|
||||
// This variable will hold the upload state that we must keep
|
||||
// around until all uploads complete
|
||||
final UploadState uploadState = new UploadState(bucket,filekey);
|
||||
|
||||
return Mono
|
||||
.fromFuture(uploadRequest)
|
||||
.flatMapMany((response) -> {
|
||||
checkResult(response);
|
||||
uploadState.uploadId = response.uploadId();
|
||||
log.info("[I183] uploadId={}", response.uploadId());
|
||||
return part.content();
|
||||
})
|
||||
.bufferUntil((buffer) -> {
|
||||
uploadState.buffered += buffer.readableByteCount();
|
||||
if ( uploadState.buffered >= s3config.getMultipartMinPartSize() ) {
|
||||
log.info("[I173] bufferUntil: returning true, bufferedBytes={}, partCounter={}, uploadId={}", uploadState.buffered, uploadState.partCounter, uploadState.uploadId);
|
||||
uploadState.buffered = 0;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map((buffers) -> concatBuffers(buffers))
|
||||
.flatMap((buffer) -> uploadPart(uploadState,buffer))
|
||||
.onBackpressureBuffer()
|
||||
.reduce(uploadState,(state,completedPart) -> {
|
||||
log.info("[I188] completed: partNumber={}, etag={}", completedPart.partNumber(), completedPart.eTag());
|
||||
state.completedParts.put(completedPart.partNumber(), completedPart);
|
||||
return state;
|
||||
})
|
||||
.flatMap((state) -> completeUpload(state))
|
||||
.map((response) -> {
|
||||
checkResult(response);
|
||||
return uploadState.filekey;
|
||||
});
|
||||
}
|
||||
|
||||
private static ByteBuffer concatBuffers(List<DataBuffer> buffers) {
|
||||
log.info("[I198] creating BytBuffer from {} chunks", buffers.size());
|
||||
|
||||
int partSize = 0;
|
||||
for( DataBuffer b : buffers) {
|
||||
partSize += b.readableByteCount();
|
||||
}
|
||||
|
||||
ByteBuffer partData = ByteBuffer.allocate(partSize);
|
||||
buffers.forEach((buffer) -> {
|
||||
partData.put(buffer.asByteBuffer());
|
||||
});
|
||||
|
||||
// Reset read pointer to first byte
|
||||
partData.rewind();
|
||||
|
||||
log.info("[I208] partData: size={}", partData.capacity());
|
||||
return partData;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a single file part to the requested bucket
|
||||
* @param uploadState
|
||||
* @param buffer
|
||||
* @return
|
||||
*/
|
||||
private Mono<CompletedPart> uploadPart(UploadState uploadState, ByteBuffer buffer) {
|
||||
final int partNumber = ++uploadState.partCounter;
|
||||
log.info("[I218] uploadPart: partNumber={}, contentLength={}",partNumber, buffer.capacity());
|
||||
|
||||
CompletableFuture<UploadPartResponse> request = s3client.uploadPart(UploadPartRequest.builder()
|
||||
.bucket(uploadState.bucket)
|
||||
.key(uploadState.filekey)
|
||||
.partNumber(partNumber)
|
||||
.uploadId(uploadState.uploadId)
|
||||
.contentLength((long) buffer.capacity())
|
||||
.build(),
|
||||
AsyncRequestBody.fromPublisher(Mono.just(buffer)));
|
||||
|
||||
return Mono
|
||||
.fromFuture(request)
|
||||
.map((uploadPartResult) -> {
|
||||
checkResult(uploadPartResult);
|
||||
log.info("[I230] uploadPart complete: part={}, etag={}",partNumber,uploadPartResult.eTag());
|
||||
return CompletedPart.builder()
|
||||
.eTag(uploadPartResult.eTag())
|
||||
.partNumber(partNumber)
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<CompleteMultipartUploadResponse> completeUpload(UploadState state) {
|
||||
log.info("[I202] completeUpload: bucket={}, filekey={}, completedParts.size={}", state.bucket, state.filekey, state.completedParts.size());
|
||||
|
||||
CompletedMultipartUpload multipartUpload = CompletedMultipartUpload.builder()
|
||||
.parts(state.completedParts.values())
|
||||
.build();
|
||||
|
||||
return Mono.fromFuture(s3client.completeMultipartUpload(CompleteMultipartUploadRequest.builder()
|
||||
.bucket(state.bucket)
|
||||
.uploadId(state.uploadId)
|
||||
.multipartUpload(multipartUpload)
|
||||
.key(state.filekey)
|
||||
.build()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check result from an API call.
|
||||
* @param result Result from an API call
|
||||
*/
|
||||
private static void checkResult(SdkResponse result) {
|
||||
if (result.sdkHttpResponse() == null || !result.sdkHttpResponse().isSuccessful()) {
|
||||
throw new UploadFailedException(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Holds upload state during a multipart upload
|
||||
*/
|
||||
static class UploadState {
|
||||
final String bucket;
|
||||
final String filekey;
|
||||
|
||||
String uploadId;
|
||||
int partCounter;
|
||||
Map<Integer, CompletedPart> completedParts = new HashMap<>();
|
||||
int buffered = 0;
|
||||
|
||||
UploadState(String bucket, String filekey) {
|
||||
this.bucket = bucket;
|
||||
this.filekey = filekey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class UploadResult {
|
||||
HttpStatus status;
|
||||
String[] keys;
|
||||
|
||||
public UploadResult() {}
|
||||
|
||||
public UploadResult(HttpStatus status, List<String> keys) {
|
||||
this.status = status;
|
||||
this.keys = keys == null ? new String[] {}: keys.toArray(new String[] {});
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
#
|
||||
# Minio profile
|
||||
#
|
||||
aws:
|
||||
s3:
|
||||
region: sa-east-1
|
||||
endpoint: http://localhost:9000
|
||||
accessKeyId: 8KLF8U60JER4AP23H0A6
|
||||
secretAccessKey: vX4uM7e7nNGPqjcXycVVhceNR7NQkiMQkR9Hoctf
|
||||
bucket: bucket1
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
#
|
||||
# Configurações de acesso ao Minio
|
||||
#
|
||||
aws:
|
||||
s3:
|
||||
region: sa-east-1
|
||||
# When using AWS, the library will use one of the available
|
||||
# credential sources described in the documentation.
|
||||
# accessKeyId: ****
|
||||
# secretAccessKey: ****
|
||||
bucket: dev1.token.com.br
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.baeldung.aws.reactive.s3;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("minio")
|
||||
class ReactiveS3ApplicationLiveTest {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@LocalServerPort
|
||||
private int serverPort;
|
||||
|
||||
|
||||
@Test
|
||||
void whenUploadSingleFile_thenSuccess() throws Exception {
|
||||
|
||||
String url = "http://localhost:" + serverPort + "/inbox";
|
||||
byte[] data = Files.readAllBytes(Paths.get("src/test/resources/testimage1.png"));
|
||||
UploadResult result = restTemplate.postForObject(url, data , UploadResult.class);
|
||||
|
||||
assertEquals("Expected CREATED (202)", result.getStatus(), HttpStatus.CREATED );
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUploadMultipleFiles_thenSuccess() throws Exception {
|
||||
|
||||
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
addFileEntity("f1", body, new File("src/test/resources/testimage1.png"));
|
||||
addFileEntity("f2", body, new File("src/test/resources/testimage2.png"));
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body);
|
||||
String url = "http://localhost:" + serverPort + "/inbox";
|
||||
|
||||
ResponseEntity<UploadResult> result = restTemplate.postForEntity(url, requestEntity, UploadResult.class);
|
||||
|
||||
assertEquals("Http Code",HttpStatus.CREATED, result.getStatusCode() );
|
||||
assertEquals("File keys",2, result.getBody().getKeys().length);
|
||||
|
||||
}
|
||||
|
||||
private void addFileEntity(String name, MultiValueMap<String, Object> body, File file) throws Exception {
|
||||
|
||||
byte[] data = Files.readAllBytes(file.toPath());
|
||||
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
ContentDisposition contentDispositionHeader = ContentDisposition.builder("form-data")
|
||||
.name(name)
|
||||
.filename(file.getName())
|
||||
.build();
|
||||
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, contentDispositionHeader.toString());
|
||||
|
||||
HttpEntity<byte[]> fileEntity = new HttpEntity<>(data, headers);
|
||||
body.add(name, fileEntity);
|
||||
}
|
||||
|
||||
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,3 @@
|
|||
### Relevant Articles:
|
||||
|
||||
- [Building Java Applications with Bazel](https://www.baeldung.com/bazel-build-tool)
|
|
@ -0,0 +1,3 @@
|
|||
### Relevant Articles:
|
||||
|
||||
- [Building Java Applications with Bazel](https://www.baeldung.com/bazel-build-tool)
|
|
@ -99,3 +99,7 @@ Invokes the CAS Command Line Shell. For a list of commands either use no argumen
|
|||
```bash
|
||||
./build.sh cli
|
||||
```
|
||||
|
||||
### Relevant Articles:
|
||||
|
||||
- [CAS SSO With Spring Security](https://www.baeldung.com/spring-security-cas-sso)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>core-java-13</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<name>core-java-13</name>
|
||||
<packaging>jar</packaging>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source.version}</source>
|
||||
<target>${maven.compiler.target.version}</target>
|
||||
<release>13</release>
|
||||
<compilerArgs>--enable-preview</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<configuration>
|
||||
<argLine>--enable-preview</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source.version>13</maven.compiler.source.version>
|
||||
<maven.compiler.target.version>13</maven.compiler.target.version>
|
||||
<assertj.version>3.6.1</assertj.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,76 @@
|
|||
package com.baeldung.switchExpression;
|
||||
|
||||
import static java.time.Month.AUGUST;
|
||||
import static java.time.Month.JUNE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.time.Month;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class SwitchExpressionsUnitTest {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings ("preview")
|
||||
public void whenSwitchingOverMonthJune_thenWillReturn3() {
|
||||
|
||||
var month = JUNE;
|
||||
|
||||
var result = switch (month) {
|
||||
case JANUARY, JUNE, JULY -> 3;
|
||||
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
|
||||
case MARCH, MAY, APRIL -> 2;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
assertEquals(result, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings ("preview")
|
||||
public void whenSwitchingOverMonthAugust_thenWillReturn24() {
|
||||
var month = AUGUST;
|
||||
|
||||
var result = switch (month) {
|
||||
case JANUARY, JUNE, JULY -> 3;
|
||||
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
|
||||
case MARCH, MAY, APRIL, AUGUST -> {
|
||||
int monthLength = month.toString().length();
|
||||
yield monthLength * 4;
|
||||
}
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
assertEquals(24, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings ("preview")
|
||||
public void whenSwitchingOverMonthJanuary_thenWillReturn3() {
|
||||
|
||||
Function<Month, Integer> func = (month) -> {
|
||||
switch (month) {
|
||||
case JANUARY, JUNE, JULY -> { return 3; }
|
||||
default -> { return 0; }
|
||||
}
|
||||
};
|
||||
|
||||
assertEquals(Integer.valueOf(3), func.apply(Month.JANUARY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings ("preview")
|
||||
public void whenSwitchingOverMonthAugust_thenWillReturn2() {
|
||||
var month = AUGUST;
|
||||
|
||||
var result = switch (month) {
|
||||
case JANUARY, JUNE, JULY -> 3;
|
||||
case FEBRUARY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER -> 1;
|
||||
case MARCH, MAY, APRIL, AUGUST -> 2;
|
||||
};
|
||||
|
||||
assertEquals(result, 2);
|
||||
}
|
||||
}
|
|
@ -6,4 +6,6 @@ This module contains articles about advanced topics about multithreading with co
|
|||
|
||||
### Relevant Articles:
|
||||
|
||||
- [Common Concurrency Pitfalls in Java](https://www.baeldung.com/java-common-concurrency-pitfalls)
|
||||
- [Guide to RejectedExecutionHandler](https://www.baeldung.com/java-rejectedexecutionhandler)
|
||||
[[<-- previous]](/core-java-modules/core-java-concurrency-advanced-2)
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
## Core Date Operations
|
||||
This module contains articles about date operations in Java.
|
||||
|
||||
### Relevant Articles:
|
||||
- [Get the Current Date Prior to Java 8](https://www.baeldung.com/java-get-the-current-date-legacy)
|
||||
- [Skipping Weekends While Adding Days to LocalDate in Java 8](https://www.baeldung.com/java-localdate-add-days-skip-weekends)
|
||||
- [Checking If Two Java Dates Are on the Same Day](https://www.baeldung.com/java-check-two-dates-on-same-day)
|
||||
- [Converting Java Date to OffsetDateTime](https://www.baeldung.com/java-convert-date-to-offsetdatetime)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>core-java-date-operations</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<name>core-java-date-operations</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.date.conversion;
|
||||
package com.baeldung.offsetdatetime;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.datetime;
|
||||
package com.baeldung.skipweekends;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
|
@ -1,13 +1,13 @@
|
|||
package com.baeldung.date.comparison;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DateComparisonUtilsUnitTest {
|
||||
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package com.baeldung.datetime;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.baeldung.datetime.CalendarUtils;
|
||||
import com.baeldung.datetime.DateUtils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CalendarUtilsUnitTest {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package com.baeldung.datetime;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.baeldung.datetime.DateUtils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DateUtilsUnitTest {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package com.baeldung.date.conversion;
|
||||
package com.baeldung.offsetdatetime;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ConvertToOffsetDateTimeUnitTest {
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
package com.baeldung.datetime;
|
||||
package com.baeldung.skipweekends;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.baeldung.skipweekends.AddSubtractDaysSkippingWeekendsUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.LocalDate;
|
|
@ -0,0 +1,5 @@
|
|||
## Core Java Exceptions 2
|
||||
|
||||
This module contains articles about core java exceptions
|
||||
|
||||
###
|
|
@ -0,0 +1,24 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>core-java-exceptions-2</artifactId>
|
||||
<name>core-java-exceptions-2</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-java</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../../parent-java</relativePath>
|
||||
</parent>
|
||||
|
||||
<description> </description>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,29 @@
|
|||
package com.baeldung.rethrow;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.baeldung.rethrow.custom.InvalidDataException;
|
||||
|
||||
public class RethrowDifferentExceptionDemo {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(RethrowDifferentExceptionDemo.class.getName());
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String name = null;
|
||||
|
||||
try {
|
||||
|
||||
// Below line will throw NullPointerException
|
||||
if (name.equals("Joe")) {
|
||||
// Do blah blah..
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "So and so user is unable to cast vote because he is found uneligible");
|
||||
throw new InvalidDataException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.baeldung.rethrow;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class RethrowSameExceptionDemo {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(RethrowDifferentExceptionDemo.class.getName());
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String name = null;
|
||||
|
||||
try {
|
||||
|
||||
// Below line will throw NullPointerException
|
||||
if (name.equals("Joe")) {
|
||||
// Do blah blah..
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Exception occurred due to invalid name");
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.baeldung.rethrow.custom;
|
||||
|
||||
public class InvalidDataException extends Exception {
|
||||
|
||||
public InvalidDataException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.baeldung.exitvshalt;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JvmExitAndHaltDemo {
|
||||
|
||||
private static Logger LOGGER = LoggerFactory.getLogger(JvmExitAndHaltDemo.class);
|
||||
|
||||
static {
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(new Thread(() -> {
|
||||
LOGGER.info("Shutdown hook initiated.");
|
||||
}));
|
||||
}
|
||||
|
||||
public void processAndExit() {
|
||||
process();
|
||||
LOGGER.info("Calling System.exit().");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public void processAndHalt() {
|
||||
process();
|
||||
LOGGER.info("Calling Runtime.getRuntime().halt().");
|
||||
Runtime.getRuntime()
|
||||
.halt(0);
|
||||
}
|
||||
|
||||
private void process() {
|
||||
LOGGER.info("Process started.");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.baeldung.exitvshalt;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class JvmExitDemoUnitTest {
|
||||
|
||||
JvmExitAndHaltDemo jvmExitAndHaltDemo = new JvmExitAndHaltDemo();
|
||||
|
||||
@Test
|
||||
public void givenProcessComplete_whenExitCalled_thenTriggerShutdownHook() {
|
||||
jvmExitAndHaltDemo.processAndExit();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.baeldung.exitvshalt;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class JvmHaltDemoUnitTest {
|
||||
|
||||
JvmExitAndHaltDemo jvmExitAndHaltDemo = new JvmExitAndHaltDemo();
|
||||
|
||||
@Test
|
||||
public void givenProcessComplete_whenHaltCalled_thenDoNotTriggerShutdownHook() {
|
||||
jvmExitAndHaltDemo.processAndHalt();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.baeldung.shutdownhook;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
public class ShutdownHookUnitTest {
|
||||
|
||||
@Test
|
||||
public void givenAHook_WhenShutsDown_ThenHookShouldBeExecuted() {
|
||||
Thread printingHook = new Thread(() -> System.out.println("In the middle of a shutdown"));
|
||||
Runtime.getRuntime().addShutdownHook(printingHook);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addingAHook_WhenThreadAlreadyStarted_ThenThrowsAnException() {
|
||||
Thread longRunningHook = new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException ignored) {}
|
||||
});
|
||||
longRunningHook.start();
|
||||
|
||||
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(longRunningHook))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Hook already running");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addingAHook_WhenAlreadyExists_ThenAnExceptionWouldBeThrown() {
|
||||
Thread unfortunateHook = new Thread(() -> {});
|
||||
Runtime.getRuntime().addShutdownHook(unfortunateHook);
|
||||
|
||||
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(unfortunateHook))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Hook previously registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAHook_WhenItIsAlreadyRegistered_ThenWouldDeRegisterTheHook() {
|
||||
Thread willNotRun = new Thread(() -> System.out.println("Won't run!"));
|
||||
Runtime.getRuntime().addShutdownHook(willNotRun);
|
||||
|
||||
assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();
|
||||
}
|
||||
}
|
|
@ -10,7 +10,5 @@ This module contains articles about core Kotlin.
|
|||
- [Kotlin Scope Functions](https://www.baeldung.com/kotlin-scope-functions)
|
||||
- [Kotlin Annotations](https://www.baeldung.com/kotlin-annotations)
|
||||
- [Split a List into Parts in Kotlin](https://www.baeldung.com/kotlin-split-list-into-parts)
|
||||
- [String Comparison in Kotlin](https://www.baeldung.com/kotlin-string-comparison)
|
||||
- [Guide to JVM Platform Annotations in Kotlin](https://www.baeldung.com/kotlin-jvm-annotations)
|
||||
- [Finding an Element in a List Using Kotlin](https://www.baeldung.com/kotlin-finding-element-in-list)
|
||||
- More articles: [[<-- prev]](/core-kotlin)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/bin/
|
||||
|
||||
#ignore gradle
|
||||
.gradle/
|
||||
|
||||
|
||||
#ignore build and generated files
|
||||
build/
|
||||
node/
|
||||
target/
|
||||
out/
|
|
@ -1,97 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>core-kotlin-io</artifactId>
|
||||
<name>core-kotlin-io</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-kotlin</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../parent-kotlin</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>${byte-buddy.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit5</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>1.8</jvmTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<kotlin.version>1.3.30</kotlin.version>
|
||||
<junit-jupiter.version>5.4.2</junit-jupiter.version>
|
||||
<mockito.version>2.27.0</mockito.version>
|
||||
<byte-buddy.version>1.9.12</byte-buddy.version>
|
||||
<assertj.version>3.10.0</assertj.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,5 @@
|
|||
## Core Kotlin
|
||||
|
||||
This module contains articles about core Kotlin.
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
## Core Kotlin Annotations
|
||||
|
||||
This module contains articles about core Kotlin annotations.
|
||||
|
||||
### Relevant articles:
|
||||
- [Kotlin Annotations](https://www.baeldung.com/kotlin-annotations)
|
||||
- [Guide to Kotlin @JvmField](https://www.baeldung.com/kotlin-jvm-field-annotation)
|
||||
- [Guide to JVM Platform Annotations in Kotlin](https://www.baeldung.com/kotlin-jvm-annotations)
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>core-kotlin-annotations</artifactId>
|
||||
<name>core-kotlin-annotations</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.core-kotlin-modules</groupId>
|
||||
<artifactId>core-kotlin-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<kotlin.version>1.3.30</kotlin.version>
|
||||
<assertj.version>3.10.0</assertj.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.kotlin
|
||||
package com.baeldung.jvmfield
|
||||
|
||||
class JvmSample(text:String) {
|
||||
@JvmField
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.kotlin
|
||||
package com.baeldung.jvmfield
|
||||
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
|
@ -3,7 +3,7 @@
|
|||
This module contains articles about core Kotlin I/O.
|
||||
|
||||
### Relevant articles:
|
||||
|
||||
- [InputStream to String in Kotlin](https://www.baeldung.com/kotlin-inputstream-to-string)
|
||||
- [Console I/O in Kotlin](https://www.baeldung.com/kotlin-console-io)
|
||||
|
||||
- [Reading from a File in Kotlin](https://www.baeldung.com/kotlin-read-file)
|
||||
- [Writing to a File in Kotlin](https://www.baeldung.com/kotlin-write-file)
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>core-kotlin-io</artifactId>
|
||||
<name>core-kotlin-io</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.core-kotlin-modules</groupId>
|
||||
<artifactId>core-kotlin-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<junit-jupiter.version>5.4.2</junit-jupiter.version>
|
||||
<mockito.version>2.27.0</mockito.version>
|
||||
<assertj.version>3.10.0</assertj.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -7,6 +7,7 @@ import java.io.File
|
|||
import kotlin.test.assertEquals
|
||||
|
||||
class InputStreamToStringTest {
|
||||
|
||||
private val fileName = "src/test/resources/inputstream2string.txt"
|
||||
private val endOfLine = System.lineSeparator()
|
||||
private val fileFullContent = "Computer programming can be a hassle$endOfLine" +
|
||||
|
@ -19,6 +20,7 @@ class InputStreamToStringTest {
|
|||
val content = inputStream.bufferedReader().use(BufferedReader::readText)
|
||||
assertEquals(fileFullContent, content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenReadFileWithBufferedReaderReadText_thenFullFileContentIsReadAsString() {
|
||||
val file = File(fileName)
|
||||
|
@ -32,6 +34,7 @@ class InputStreamToStringTest {
|
|||
}
|
||||
assertEquals(fileFullContent, content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenReadFileWithBufferedReaderManually_thenFullFileContentIsReadAsString() {
|
||||
val file = File(fileName)
|
||||
|
@ -67,6 +70,5 @@ class InputStreamToStringTest {
|
|||
assertEquals(fileFullContent, content)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
## Core Kotlin Strings
|
||||
|
||||
This module contains articles about core Kotlin strings.
|
||||
|
||||
### Relevant articles:
|
||||
- [Generate a Random Alphanumeric String in Kotlin](https://www.baeldung.com/kotlin-random-alphanumeric-string)
|
||||
- [String Comparison in Kotlin](https://www.baeldung.com/kotlin-string-comparison)
|
||||
- [Concatenate Strings in Kotlin](https://www.baeldung.com/kotlin-concatenate-strings)
|
||||
- [Kotlin String Templates](https://www.baeldung.com/kotlin-string-template)
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>core-kotlin-strings</artifactId>
|
||||
<name>core-kotlin-strings</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.core-kotlin-modules</groupId>
|
||||
<artifactId>core-kotlin-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,3 +1,5 @@
|
|||
package com.baeldung.randomstring
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils
|
||||
import org.junit.Before
|
||||
import org.junit.jupiter.api.BeforeAll
|
|
@ -1,4 +1,4 @@
|
|||
package stringcomparison
|
||||
package com.baeldung.stringcomparison
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFalse
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.kotlin
|
||||
package com.baeldung.stringconcatenation
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung.core-kotlin-modules</groupId>
|
||||
<artifactId>core-kotlin-modules</artifactId>
|
||||
<name>core-kotlin-modules</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-kotlin</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../parent-kotlin</relativePath>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>core-kotlin-annotations</module>
|
||||
<module>core-kotlin-io</module>
|
||||
<module>core-kotlin-strings</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>1.8</jvmTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<kotlin.version>1.3.30</kotlin.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -24,12 +24,8 @@ This module contains articles about core Kotlin.
|
|||
- [Try-with-resources in Kotlin](https://www.baeldung.com/kotlin-try-with-resources)
|
||||
- [Regular Expressions in Kotlin](https://www.baeldung.com/kotlin-regular-expressions)
|
||||
- [Objects in Kotlin](https://www.baeldung.com/kotlin-objects)
|
||||
- [Reading from a File in Kotlin](https://www.baeldung.com/kotlin-read-file)
|
||||
- [Guide to Kotlin @JvmField](https://www.baeldung.com/kotlin-jvm-field-annotation)
|
||||
- [Filtering Kotlin Collections](https://www.baeldung.com/kotlin-filter-collection)
|
||||
- [Writing to a File in Kotlin](https://www.baeldung.com/kotlin-write-file)
|
||||
- [Lambda Expressions in Kotlin](https://www.baeldung.com/kotlin-lambda-expressions)
|
||||
- [Kotlin String Templates](https://www.baeldung.com/kotlin-string-template)
|
||||
- [Working with Enums in Kotlin](https://www.baeldung.com/kotlin-enum)
|
||||
- [Create a Java and Kotlin Project with Maven](https://www.baeldung.com/kotlin-maven-java-project)
|
||||
- [Reflection with Kotlin](https://www.baeldung.com/kotlin-reflection)
|
||||
|
@ -41,7 +37,6 @@ This module contains articles about core Kotlin.
|
|||
- [Fuel HTTP Library with Kotlin](https://www.baeldung.com/kotlin-fuel)
|
||||
- [Introduction to Kovenant Library for Kotlin](https://www.baeldung.com/kotlin-kovenant)
|
||||
- [Converting Kotlin Data Class from JSON using GSON](https://www.baeldung.com/kotlin-json-convert-data-class)
|
||||
- [Concatenate Strings in Kotlin](https://www.baeldung.com/kotlin-concatenate-strings)
|
||||
- [Kotlin return, break, continue Keywords](https://www.baeldung.com/kotlin-return-break-continue)
|
||||
- [Mapping of Data Objects in Kotlin](https://www.baeldung.com/kotlin-data-objects)
|
||||
- [Initializing Arrays in Kotlin](https://www.baeldung.com/kotlin-initialize-array)
|
||||
|
@ -50,7 +45,6 @@ This module contains articles about core Kotlin.
|
|||
- [Guide to Sorting in Kotlin](https://www.baeldung.com/kotlin-sort)
|
||||
- [Dependency Injection for Kotlin with Injekt](https://www.baeldung.com/kotlin-dependency-injection-with-injekt)
|
||||
- [Implementing a Binary Tree in Kotlin](https://www.baeldung.com/kotlin-binary-tree)
|
||||
- [Generate a Random Alphanumeric String in Kotlin](https://www.baeldung.com/kotlin-random-alphanumeric-string)
|
||||
- [Kotlin Contracts](https://www.baeldung.com/kotlin-contracts)
|
||||
- [Operator Overloading in Kotlin](https://www.baeldung.com/kotlin-operator-overloading)
|
||||
- [Inline Classes in Kotlin](https://www.baeldung.com/kotlin-inline-classes)
|
||||
|
|
|
@ -76,6 +76,11 @@
|
|||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.baeldung.ddd;
|
|||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(scanBasePackages = "com.baeldung.ddd.order")
|
||||
public class PersistingDddAggregatesApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.dddhexagonalspring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@SpringBootApplication
|
||||
@PropertySource(value = { "classpath:ddd-layers.properties" })
|
||||
public class DomainLayerApplication {
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(DomainLayerApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.dddhexagonalspring.application.controller;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.application.request.AddProductRequest;
|
||||
import com.baeldung.dddhexagonalspring.application.request.CreateOrderRequest;
|
||||
import com.baeldung.dddhexagonalspring.application.response.CreateOrderResponse;
|
||||
import com.baeldung.dddhexagonalspring.domain.service.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/orders")
|
||||
public class OrderController {
|
||||
|
||||
private final OrderService orderService;
|
||||
|
||||
@Autowired
|
||||
public OrderController(OrderService orderService) {
|
||||
this.orderService = orderService;
|
||||
}
|
||||
|
||||
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
CreateOrderResponse createOrder(@RequestBody final CreateOrderRequest createOrderRequest) {
|
||||
final UUID id = orderService.createOrder(createOrderRequest.getProduct());
|
||||
|
||||
return new CreateOrderResponse(id);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
void addProduct(@PathVariable final UUID id, @RequestBody final AddProductRequest addProductRequest) {
|
||||
orderService.addProduct(id, addProductRequest.getProduct());
|
||||
}
|
||||
|
||||
@DeleteMapping(value = "/{id}/products", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
void deleteProduct(@PathVariable final UUID id, @RequestParam final UUID productId) {
|
||||
orderService.deleteProduct(id, productId);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/complete")
|
||||
void completeOrder(@PathVariable final UUID id) {
|
||||
orderService.completeOrder(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.baeldung.dddhexagonalspring.application.request;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class AddProductRequest {
|
||||
@NotNull private Product product;
|
||||
|
||||
@JsonCreator
|
||||
public AddProductRequest(@JsonProperty("product") final Product product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.baeldung.dddhexagonalspring.application.request;
|
||||
|
||||
import com.baeldung.dddhexagonalspring.domain.Product;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class CreateOrderRequest {
|
||||
@NotNull private Product product;
|
||||
|
||||
@JsonCreator
|
||||
public CreateOrderRequest(@JsonProperty("product") @NotNull final Product product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.baeldung.dddhexagonalspring.application.response;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreateOrderResponse {
|
||||
private final UUID id;
|
||||
|
||||
public CreateOrderResponse(final UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
class DomainException extends RuntimeException {
|
||||
DomainException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Order {
|
||||
private UUID id;
|
||||
private OrderStatus status;
|
||||
private List<OrderItem> orderItems;
|
||||
private BigDecimal price;
|
||||
|
||||
public Order(final UUID id, final Product product) {
|
||||
this.id = id;
|
||||
this.orderItems = new ArrayList<>(Collections.singletonList(new OrderItem(product)));
|
||||
this.status = OrderStatus.CREATED;
|
||||
this.price = product.getPrice();
|
||||
}
|
||||
|
||||
public void complete() {
|
||||
validateState();
|
||||
this.status = OrderStatus.COMPLETED;
|
||||
}
|
||||
|
||||
public void addOrder(final Product product) {
|
||||
validateState();
|
||||
validateProduct(product);
|
||||
orderItems.add(new OrderItem(product));
|
||||
price = price.add(product.getPrice());
|
||||
}
|
||||
|
||||
public void removeOrder(final UUID id) {
|
||||
validateState();
|
||||
final OrderItem orderItem = getOrderItem(id);
|
||||
orderItems.remove(orderItem);
|
||||
|
||||
price = price.subtract(orderItem.getPrice());
|
||||
}
|
||||
|
||||
private OrderItem getOrderItem(final UUID id) {
|
||||
return orderItems
|
||||
.stream()
|
||||
.filter(orderItem -> orderItem
|
||||
.getProductId()
|
||||
.equals(id))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new DomainException("Product with " + id + " doesn't exist."));
|
||||
}
|
||||
|
||||
private void validateState() {
|
||||
if (OrderStatus.COMPLETED.equals(status)) {
|
||||
throw new DomainException("The order is in completed state.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProduct(final Product product) {
|
||||
if (product == null) {
|
||||
throw new DomainException("The product cannot be null.");
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public OrderStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public List<OrderItem> getOrderItems() {
|
||||
return Collections.unmodifiableList(orderItems);
|
||||
}
|
||||
|
||||
private Order() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.baeldung.dddhexagonalspring.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class OrderItem {
|
||||
private UUID productId;
|
||||
private BigDecimal price;
|
||||
|
||||
public OrderItem(final Product product) {
|
||||
this.productId = product.getId();
|
||||
this.price = product.getPrice();
|
||||
}
|
||||
|
||||
public UUID getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public BigDecimal getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
private OrderItem() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
OrderItem orderItem = (OrderItem) o;
|
||||
return Objects.equals(productId, orderItem.productId) && Objects.equals(price, orderItem.price);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(productId, price);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue