diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithm.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithm.java index 0aaa1b6c23..60171220cd 100644 --- a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithm.java +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithm.java @@ -13,22 +13,22 @@ public class KadaneAlgorithm { int start = 0; int end = 0; - int maxSoFar = 0, maxEndingHere = 0; - + int maxSoFar = arr[0], maxEndingHere = arr[0]; for (int i = 0; i < size; i++) { if (arr[i] > maxEndingHere + arr[i]) { start = i; maxEndingHere = arr[i]; - } else + } else { maxEndingHere = maxEndingHere + arr[i]; + } if (maxSoFar < maxEndingHere) { maxSoFar = maxEndingHere; end = i; } } - logger.info("Found Maximum Subarray between {} and {}", start, end); + logger.info("Found Maximum Subarray between {} and {}", Math.min(start, end), end); return maxSoFar; } } diff --git a/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithmUnitTest.java b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithmUnitTest.java index 9bece56f5b..71531dcf59 100644 --- a/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithmUnitTest.java +++ b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/maximumsubarray/KadaneAlgorithmUnitTest.java @@ -9,11 +9,22 @@ class KadaneAlgorithmUnitTest { @Test void givenArrayWithNegativeNumberWhenMaximumSubarrayThenReturns6() { //given - int[] arr = new int[]{-3, 1, -8, 4, -1, 2, 1, -5, 5}; + int[] arr = new int[] { -3, 1, -8, 4, -1, 2, 1, -5, 5 }; //when KadaneAlgorithm algorithm = new KadaneAlgorithm(); int maxSum = algorithm.maxSubArraySum(arr); //then assertEquals(6, maxSum); } + + @Test + void givenArrayWithAllNegativeNumbersWhenMaximumSubarrayThenReturnsExpectedResult() { + //given + int[] arr = new int[] { -8, -7, -5, -4, -3, -1, -2 }; + //when + KadaneAlgorithm algorithm = new KadaneAlgorithm(); + int maxSum = algorithm.maxSubArraySum(arr); + //then + assertEquals(-1, maxSum); + } } \ No newline at end of file diff --git a/apache-poi/README.md b/apache-poi/README.md index 599b21e063..d500787536 100644 --- a/apache-poi/README.md +++ b/apache-poi/README.md @@ -11,3 +11,4 @@ This module contains articles about Apache POI - [Get String Value of Excel Cell with Apache POI](https://www.baeldung.com/java-apache-poi-cell-string-value) - [Read Excel Cell Value Rather Than Formula With Apache POI](https://www.baeldung.com/apache-poi-read-cell-value-formula) - [Setting Formulas in Excel with Apache POI](https://www.baeldung.com/java-apache-poi-set-formulas) +- [Insert a Row in Excel Using Apache POI](https://www.baeldung.com/apache-poi-insert-excel-row) diff --git a/apache-poi/src/test/java/com/baeldung/poi/excel/insert/ExcelInsertRowUnitTest.java b/apache-poi/src/test/java/com/baeldung/poi/excel/insert/ExcelInsertRowUnitTest.java new file mode 100644 index 0000000000..0f137aa4d2 --- /dev/null +++ b/apache-poi/src/test/java/com/baeldung/poi/excel/insert/ExcelInsertRowUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.poi.excel.insert; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +public class ExcelInsertRowUnitTest { + private static final String FILE_NAME = "test.xlsx"; + private static final String NEW_FILE_NAME = "new_test.xlsx"; + private String fileLocation; + + @Before + public void setup() throws URISyntaxException { + fileLocation = Paths.get(ClassLoader.getSystemResource(FILE_NAME).toURI()).toString(); + } + + @Test + public void givenWorkbook_whenInsertRowBetween_thenRowCreated() throws IOException { + int startRow = 2; + int rowNumber = 1; + Workbook workbook = new XSSFWorkbook(fileLocation); + Sheet sheet = workbook.getSheetAt(0); + int lastRow = sheet.getLastRowNum(); + if (lastRow < startRow) { + sheet.createRow(startRow); + } + sheet.shiftRows(startRow, lastRow, rowNumber, true, true); + sheet.createRow(startRow); + FileOutputStream outputStream = new FileOutputStream(NEW_FILE_NAME); + workbook.write(outputStream); + File file = new File(NEW_FILE_NAME); + + final int expectedRowResult = 5; + Assertions.assertEquals(expectedRowResult, workbook.getSheetAt(0).getLastRowNum()); + + outputStream.close(); + file.delete(); + workbook.close(); + } + +} \ No newline at end of file diff --git a/core-java-modules/core-java-10/README.md b/core-java-modules/core-java-10/README.md index 38b1db1c05..9a110449bc 100644 --- a/core-java-modules/core-java-10/README.md +++ b/core-java-modules/core-java-10/README.md @@ -5,9 +5,10 @@ This module contains articles about Java 10 core features ### Relevant Articles: - [Java 10 LocalVariable Type-Inference](http://www.baeldung.com/java-10-local-variable-type-inference) -- [Guide to Java 10](http://www.baeldung.com/java-10-overview) +- [New Features in Java 10](https://www.baeldung.com/java-10-overview) - [Copy a List to Another List in Java](http://www.baeldung.com/java-copy-list-to-another) - [Deep Dive Into the New Java JIT Compiler – Graal](https://www.baeldung.com/graal-java-jit-compiler) - [Copying Sets in Java](https://www.baeldung.com/java-copy-sets) - [Converting between a List and a Set in Java](https://www.baeldung.com/convert-list-to-set-and-set-to-list) - [Java IndexOutOfBoundsException “Source Does Not Fit in Dest”](https://www.baeldung.com/java-indexoutofboundsexception) +- [Collect a Java Stream to an Immutable Collection](https://www.baeldung.com/java-stream-immutable-collection) diff --git a/core-java-modules/core-java-10/src/test/java/com/baeldung/java10/streams/StreamToImmutableJava10UnitTest.java b/core-java-modules/core-java-10/src/test/java/com/baeldung/java10/streams/StreamToImmutableJava10UnitTest.java new file mode 100644 index 0000000000..8b1ef54fd2 --- /dev/null +++ b/core-java-modules/core-java-10/src/test/java/com/baeldung/java10/streams/StreamToImmutableJava10UnitTest.java @@ -0,0 +1,20 @@ +package com.baeldung.java10.streams; + +import static java.util.stream.Collectors.toUnmodifiableList; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class StreamToImmutableJava10UnitTest { + + @Test + public void whenUsingCollectorsToUnmodifiableList_thenSuccess() { + List givenList = Arrays.asList("a", "b", "c"); + List result = givenList.stream() + .collect(toUnmodifiableList()); + + System.out.println(result.getClass()); + } +} diff --git a/core-java-modules/core-java-12/README.md b/core-java-modules/core-java-12/README.md index c28df26c6f..b509be876c 100644 --- a/core-java-modules/core-java-12/README.md +++ b/core-java-modules/core-java-12/README.md @@ -1,5 +1,4 @@ ## Relevant Articles: - - [String API Updates in Java 12](https://www.baeldung.com/java12-string-api) -- [Java 12 New Features](https://www.baeldung.com/java-12-new-features) +- [New Features in Java 12](https://www.baeldung.com/java-12-new-features) diff --git a/core-java-modules/core-java-13/README.md b/core-java-modules/core-java-13/README.md index 697f89c362..9215139dd4 100644 --- a/core-java-modules/core-java-13/README.md +++ b/core-java-modules/core-java-13/README.md @@ -1,4 +1,4 @@ ### Relevant articles: - [Java Switch Statement](https://www.baeldung.com/java-switch) -- [New Java 13 Features](https://www.baeldung.com/java-13-new-features) +- [New Features in Java 13](https://www.baeldung.com/java-13-new-features) diff --git a/core-java-modules/core-java-14/README.md b/core-java-modules/core-java-14/README.md index 004b3587c4..07cdf9859c 100644 --- a/core-java-modules/core-java-14/README.md +++ b/core-java-modules/core-java-14/README.md @@ -10,4 +10,4 @@ This module contains articles about Java 14. - [Helpful NullPointerExceptions in Java 14](https://www.baeldung.com/java-14-nullpointerexception) - [Foreign Memory Access API in Java 14](https://www.baeldung.com/java-foreign-memory-access) - [Java 14 Record Keyword](https://www.baeldung.com/java-record-keyword) -- [Java 14 – New Features](https://www.baeldung.com/java-14-new-features) +- [New Features in Java 14](https://www.baeldung.com/java-14-new-features) diff --git a/core-java-modules/core-java-9-new-features/README.md b/core-java-modules/core-java-9-new-features/README.md index 5af069c6f0..4045a37d9d 100644 --- a/core-java-modules/core-java-9-new-features/README.md +++ b/core-java-modules/core-java-9-new-features/README.md @@ -4,7 +4,7 @@ This module contains articles about core Java features that have been introduced ### Relevant Articles: -- [Java 9 New Features](https://www.baeldung.com/new-java-9) +- [New Features in Java 9](https://www.baeldung.com/new-java-9) - [Java 9 Variable Handles Demystified](http://www.baeldung.com/java-variable-handles) - [Exploring the New HTTP Client in Java 9 and 11](http://www.baeldung.com/java-9-http-client) - [Multi-Release Jar Files](https://www.baeldung.com/java-multi-release-jar) diff --git a/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/AnnotatedClass.java b/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/AnnotatedClass.java new file mode 100644 index 0000000000..d5d9c88388 --- /dev/null +++ b/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/AnnotatedClass.java @@ -0,0 +1,8 @@ +package com.baeldung.annotations; + +import javax.annotation.Generated; + +@RetentionAnnotation +@Generated("Available only on source code") +public class AnnotatedClass { +} diff --git a/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/RetentionAnnotation.java b/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/RetentionAnnotation.java new file mode 100644 index 0000000000..4591334dde --- /dev/null +++ b/core-java-modules/core-java-annotations/src/main/java/com/baeldung/annotations/RetentionAnnotation.java @@ -0,0 +1,12 @@ +package com.baeldung.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; + +@Target(TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface RetentionAnnotation { +} \ No newline at end of file diff --git a/core-java-modules/core-java-annotations/src/test/java/com/baeldung/annotations/AnnotatedClassUnitTest.java b/core-java-modules/core-java-annotations/src/test/java/com/baeldung/annotations/AnnotatedClassUnitTest.java new file mode 100644 index 0000000000..9bf0b78cbb --- /dev/null +++ b/core-java-modules/core-java-annotations/src/test/java/com/baeldung/annotations/AnnotatedClassUnitTest.java @@ -0,0 +1,18 @@ +package com.baeldung.annotations; + +import org.junit.Test; + +import java.lang.annotation.Annotation; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class AnnotatedClassUnitTest { + + @Test + public void whenAnnotationRetentionPolicyRuntime_shouldAccess() { + AnnotatedClass anAnnotatedClass = new AnnotatedClass(); + Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations(); + assertThat(annotations.length, is(1)); + } +} diff --git a/core-java-modules/core-java-char/README.md b/core-java-modules/core-java-char/README.md index fd79da15ab..5f33aa6914 100644 --- a/core-java-modules/core-java-char/README.md +++ b/core-java-modules/core-java-char/README.md @@ -3,4 +3,4 @@ This module contains articles about Java Character Class ### Relevant Articles: -- [Character#isAlphabetic vs Character#isLetter](https://www.baeldung.com/java-character-isletter-isalphabetic) +- [Character#isAlphabetic vs. Character#isLetter](https://www.baeldung.com/java-character-isletter-isalphabetic) diff --git a/core-java-modules/core-java-collections-4/README.md b/core-java-modules/core-java-collections-4/README.md index 6e117c98b1..4fd77473d4 100644 --- a/core-java-modules/core-java-collections-4/README.md +++ b/core-java-modules/core-java-collections-4/README.md @@ -5,3 +5,4 @@ ### Relevant Articles: - [ArrayList vs. LinkedList vs. HashMap in Java](https://www.baeldung.com/java-arraylist-vs-linkedlist-vs-hashmap) +- [Java Deque vs. Stack](https://www.baeldung.com/java-deque-vs-stack) diff --git a/core-java-modules/core-java-collections-4/pom.xml b/core-java-modules/core-java-collections-4/pom.xml index 0e3cabf40e..b4f8aa6320 100644 --- a/core-java-modules/core-java-collections-4/pom.xml +++ b/core-java-modules/core-java-collections-4/pom.xml @@ -25,7 +25,7 @@ - 3.18.0 + 3.19.0 diff --git a/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/ArrayLifoStack.java b/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/ArrayLifoStack.java new file mode 100644 index 0000000000..16fdec5b77 --- /dev/null +++ b/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/ArrayLifoStack.java @@ -0,0 +1,91 @@ +package com.baeldung.collections.dequestack; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; + +public class ArrayLifoStack implements LifoStack { + private final Deque deque = new ArrayDeque<>(); + + @Override + public void push(E item) { + deque.addFirst(item); + } + + @Override + public E pop() { + return deque.removeFirst(); + } + + @Override + public E peek() { + return deque.peekFirst(); + } + + // implementing methods from the Collection interface + @Override + public int size() { + return deque.size(); + } + + @Override + public boolean isEmpty() { + return deque.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return deque.contains(o); + } + + @Override + public Iterator iterator() { + return deque.iterator(); + } + + @Override + public Object[] toArray() { + return deque.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return deque.toArray(a); + } + + @Override + public boolean add(E e) { + return deque.add(e); + } + + @Override + public boolean remove(Object o) { + return deque.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return deque.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return deque.addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return deque.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return deque.retainAll(c); + } + + @Override + public void clear() { + deque.clear(); + } +} diff --git a/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/LifoStack.java b/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/LifoStack.java new file mode 100644 index 0000000000..11e07e3555 --- /dev/null +++ b/core-java-modules/core-java-collections-4/src/main/java/com/baeldung/collections/dequestack/LifoStack.java @@ -0,0 +1,12 @@ +package com.baeldung.collections.dequestack; + +import java.util.Collection; + +public interface LifoStack extends Collection { + + E peek(); + + E pop(); + + void push(E item); +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/dequestack/StackVsDequeUnitTest.java b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/dequestack/StackVsDequeUnitTest.java new file mode 100644 index 0000000000..ca3b0e8d54 --- /dev/null +++ b/core-java-modules/core-java-collections-4/src/test/java/com/baeldung/collections/dequestack/StackVsDequeUnitTest.java @@ -0,0 +1,90 @@ +package com.baeldung.collections.dequestack; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.Stack; + +import static org.assertj.core.api.Assertions.assertThat; + +class StackVsDequeUnitTest { + + @Test + void givenAStack_whenAccessByIndex_thenElementCanBeRead() { + Stack myStack = new Stack<>(); + myStack.push("I am the 1st element."); //index 0 + myStack.push("I am the 2nd element."); //index 1 + myStack.push("I am the 3rd element."); //index 2 + //access by index + assertThat(myStack.get(0)).isEqualTo("I am the 1st element."); + } + + @Test + void givenAStack_whenIterate_thenFromBottomToTop() { + Stack myStack = new Stack<>(); + myStack.push("I am at the bottom."); + myStack.push("I am in the middle."); + myStack.push("I am at the top."); + + Iterator it = myStack.iterator(); + + assertThat(it).toIterable().containsExactly( + "I am at the bottom.", + "I am in the middle.", + "I am at the top."); + } + + @Test + void givenAStack_whenAddOrRemoveByIndex_thenElementCanBeAddedOrRemoved() { + Stack myStack = new Stack<>(); + myStack.push("I am the 1st element."); + myStack.push("I am the 3rd element."); + + assertThat(myStack.size()).isEqualTo(2); + + //insert by index + myStack.add(1, "I am the 2nd element."); + assertThat(myStack.size()).isEqualTo(3); + assertThat(myStack.get(1)).isEqualTo("I am the 2nd element."); + //remove by index + myStack.remove(1); + assertThat(myStack.size()).isEqualTo(2); + } + + @Test + void givenADeque_whenAddOrRemoveLastElement_thenTheLastElementCanBeAddedOrRemoved() { + Deque myStack = new ArrayDeque<>(); + myStack.push("I am the 1st element."); + myStack.push("I am the 2nd element."); + myStack.push("I am the 3rd element."); + + assertThat(myStack.size()).isEqualTo(3); + + //insert element to the bottom of the stack + myStack.addLast("I am the NEW element."); + assertThat(myStack.size()).isEqualTo(4); + assertThat(myStack.peek()).isEqualTo("I am the 3rd element."); + + //remove element from the bottom of the stack + String removedStr = myStack.removeLast(); + assertThat(myStack.size()).isEqualTo(3); + assertThat(removedStr).isEqualTo("I am the NEW element."); + } + + @Test + void givenADeque_whenIterate_thenFromTopToBottom() { + Deque myStack = new ArrayDeque<>(); + myStack.push("I am at the bottom."); + myStack.push("I am in the middle."); + myStack.push("I am at the top."); + + Iterator it = myStack.iterator(); + + assertThat(it).toIterable().containsExactly( + "I am at the top.", + "I am in the middle.", + "I am at the bottom."); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-collections-list-3/src/main/java/com/baeldung/list/primitive/PrimitivesListPerformance.java b/core-java-modules/core-java-collections-list-3/src/main/java/com/baeldung/list/primitive/PrimitivesListPerformance.java index bd37e5e74e..5dd1eaef09 100644 --- a/core-java-modules/core-java-collections-list-3/src/main/java/com/baeldung/list/primitive/PrimitivesListPerformance.java +++ b/core-java-modules/core-java-collections-list-3/src/main/java/com/baeldung/list/primitive/PrimitivesListPerformance.java @@ -8,6 +8,7 @@ import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -18,12 +19,12 @@ import java.util.concurrent.TimeUnit; @State(Scope.Thread) public class PrimitivesListPerformance { - private List arrayList = new ArrayList<>(); - private TIntArrayList tList = new TIntArrayList(); - private cern.colt.list.IntArrayList coltList = new cern.colt.list.IntArrayList(); - private IntArrayList fastUtilList = new IntArrayList(); + private List arrayList = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + private TIntArrayList tList = new TIntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + private cern.colt.list.IntArrayList coltList = new cern.colt.list.IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + private IntArrayList fastUtilList = new IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); - private int getValue = 10; + private int getValue = 4; @Benchmark public boolean addArrayList() { diff --git a/core-java-modules/core-java-collections-maps-3/README.md b/core-java-modules/core-java-collections-maps-3/README.md index 918c81fe4b..530a9310c2 100644 --- a/core-java-modules/core-java-collections-maps-3/README.md +++ b/core-java-modules/core-java-collections-maps-3/README.md @@ -7,4 +7,6 @@ This module contains articles about Map data structures in Java. - [Comparing Two HashMaps in Java](https://www.baeldung.com/java-compare-hashmaps) - [The Map.computeIfAbsent() Method](https://www.baeldung.com/java-map-computeifabsent) - [Collections.synchronizedMap vs. ConcurrentHashMap](https://www.baeldung.com/java-synchronizedmap-vs-concurrenthashmap) +- [Java HashMap Load Factor](https://www.baeldung.com/java-hashmap-load-factor) +- [Converting java.util.Properties to HashMap](https://www.baeldung.com/java-convert-properties-to-hashmap) - More articles: [[<-- prev]](/core-java-modules/core-java-collections-maps-2) diff --git a/core-java-modules/core-java-collections-maps-3/pom.xml b/core-java-modules/core-java-collections-maps-3/pom.xml index 577ad58255..c9fda26fc3 100644 --- a/core-java-modules/core-java-collections-maps-3/pom.xml +++ b/core-java-modules/core-java-collections-maps-3/pom.xml @@ -20,6 +20,11 @@ jmh-core ${jmh-core.version} + + com.google.guava + guava + ${guava.version} + diff --git a/core-java-modules/core-java-collections-maps-3/src/main/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverter.java b/core-java-modules/core-java-collections-maps-3/src/main/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverter.java new file mode 100644 index 0000000000..3472f998f5 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-3/src/main/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverter.java @@ -0,0 +1,39 @@ +package com.baeldung.map.propertieshashmap; + +import com.google.common.collect.Maps; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +public class PropertiesToHashMapConverter { + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static HashMap typeCastConvert(Properties prop) { + Map step1 = prop; + Map step2 = (Map) step1; + return new HashMap<>(step2); + } + + public static HashMap loopConvert(Properties prop) { + HashMap retMap = new HashMap<>(); + for (Map.Entry entry : prop.entrySet()) { + retMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); + } + return retMap; + } + + public static HashMap streamConvert(Properties prop) { + return prop.entrySet().stream().collect( + Collectors.toMap( + e -> String.valueOf(e.getKey()), + e -> String.valueOf(e.getValue()), + (prev, next) -> next, HashMap::new + )); + } + + public static HashMap guavaConvert(Properties prop) { + return Maps.newHashMap(Maps.fromProperties(prop)); + } +} diff --git a/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/hashmap/loadfactor/HashMapLoadFactorUnitTest.java b/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/hashmap/loadfactor/HashMapLoadFactorUnitTest.java new file mode 100644 index 0000000000..94967a4905 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/hashmap/loadfactor/HashMapLoadFactorUnitTest.java @@ -0,0 +1,49 @@ +package com.baeldung.map.hashmap.loadfactor; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; + +public class HashMapLoadFactorUnitTest { + + @Test + public void whenCreateMapWithDefaultParam_thenSucces() { + Map mapWithDefaultParams = new HashMap<>(); + mapWithDefaultParams.put("1", "one"); + mapWithDefaultParams.put("2", "two"); + mapWithDefaultParams.put("3", "three"); + mapWithDefaultParams.put("4", "four"); + mapWithDefaultParams.put("5", "five"); + + Assert.assertEquals(5, mapWithDefaultParams.size()); + } + + @Test + public void whenCreateMapWithInitialCapacity_thenSucces() { + Map mapWithInitialCapacity = new HashMap<>(5); + mapWithInitialCapacity.put("1", "one"); + mapWithInitialCapacity.put("2", "two"); + mapWithInitialCapacity.put("3", "three"); + + Assert.assertEquals(3, mapWithInitialCapacity.size()); + } + + @Test + public void whenCreateMapWithInitialCapacityAndLF_thenSucces() { + Map mapWithInitialCapacityAndLF = new HashMap<>(5, 0.5f); + mapWithInitialCapacityAndLF.put("1", "one"); + mapWithInitialCapacityAndLF.put("2", "two"); + mapWithInitialCapacityAndLF.put("3", "three"); + mapWithInitialCapacityAndLF.put("4", "four"); + mapWithInitialCapacityAndLF.put("5", "five"); + mapWithInitialCapacityAndLF.put("6", "six"); + mapWithInitialCapacityAndLF.put("7", "seven"); + mapWithInitialCapacityAndLF.put("8", "eight"); + mapWithInitialCapacityAndLF.put("9", "nine"); + mapWithInitialCapacityAndLF.put("10", "ten"); + + Assert.assertEquals(10, mapWithInitialCapacityAndLF.size()); + } +} diff --git a/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverterUnitTest.java b/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverterUnitTest.java new file mode 100644 index 0000000000..1985fbe673 --- /dev/null +++ b/core-java-modules/core-java-collections-maps-3/src/test/java/com/baeldung/map/propertieshashmap/PropertiesToHashMapConverterUnitTest.java @@ -0,0 +1,192 @@ +package com.baeldung.map.propertieshashmap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +class PropertiesToHashMapConverterUnitTest { + + private Properties properties; + + private final static String propertyFileName = "toHashMap.properties"; + + @BeforeEach + public void setup() throws IOException { + properties = new Properties(); + try (InputStream is = getClass().getClassLoader().getResourceAsStream(propertyFileName)) { + if (is != null) { + properties.load(is); + } + } + } + + @Test + public void havingPropertiesLoaded_whenCheck_thenEquals() { + assertEquals(3, properties.size()); + assertEquals("str_value", properties.get("property1")); + assertEquals("123", properties.get("property2")); + assertEquals("", properties.get("property3")); + } + + @Test + public void whenPropertiesModified_thenTypeSafeIssues() { + compromiseProperties(properties); + + assertEquals(5, properties.size()); + + assertNull(properties.getProperty("property4")); + assertNotEquals(String.class, properties.get("property4").getClass()); + assertEquals(456, properties.get("property4")); + + + assertNull(properties.getProperty("5")); + assertNotEquals(String.class, properties.get(5).getClass()); + assertEquals(10.11, properties.get(5)); + } + + @Test + public void havingNonModifiedProperties_whenTypeCastConvert_thenNoTypeSafeIssues() { + HashMap hMap = PropertiesToHashMapConverter.typeCastConvert(properties); + + assertEquals(3, hMap.size()); + assertEquals(String.class, hMap.get("property1").getClass()); + assertEquals(properties.get("property1"), hMap.get("property1")); + assertEquals(String.class, hMap.get("property2").getClass()); + assertEquals(properties.get("property2"), hMap.get("property2")); + assertEquals(String.class, hMap.get("property3").getClass()); + assertEquals(properties.get("property3"), hMap.get("property3")); + } + + @Test + public void havingModifiedProperties_whenTypeCastConvert_thenClassCastException() { + compromiseProperties(properties); + HashMap hMap = PropertiesToHashMapConverter.typeCastConvert(properties); + assertEquals(5, hMap.size()); + + assertThrows(ClassCastException.class, () -> { + String s = hMap.get("property4"); + }); + assertEquals(Integer.class, ((Object) hMap.get("property4")).getClass()); + + assertNull(hMap.get("5")); + assertNotNull(hMap.get(5)); + assertThrows(ClassCastException.class, () -> { + String s = hMap.get(5); + }); + assertEquals(Double.class, ((Object) hMap.get(5)).getClass()); + } + + @Test + public void havingNonModifiedProperties_whenLoopConvert_thenNoTypeSafeIssues() { + HashMap hMap = PropertiesToHashMapConverter.loopConvert(properties); + + assertEquals(3, hMap.size()); + assertEquals(String.class, hMap.get("property1").getClass()); + assertEquals(properties.get("property1"), hMap.get("property1")); + assertEquals(String.class, hMap.get("property2").getClass()); + assertEquals(properties.get("property2"), hMap.get("property2")); + assertEquals(String.class, hMap.get("property3").getClass()); + assertEquals(properties.get("property3"), hMap.get("property3")); + } + + @Test + public void havingModifiedProperties_whenLoopConvert_thenNoClassCastException() { + compromiseProperties(properties); + HashMap hMap = PropertiesToHashMapConverter.loopConvert(properties); + assertEquals(5, hMap.size()); + + assertDoesNotThrow(() -> { + String s = hMap.get("property4"); + }); + assertEquals(String.class, hMap.get("property4").getClass()); + assertEquals("456", hMap.get("property4")); + + assertDoesNotThrow(() -> { + String s = hMap.get("5"); + }); + assertEquals("10.11", hMap.get("5")); + } + + @Test + public void havingNonModifiedProperties_whenStreamConvert_thenNoTypeSafeIssues() { + HashMap hMap = PropertiesToHashMapConverter.streamConvert(properties); + + assertEquals(3, hMap.size()); + assertEquals(String.class, hMap.get("property1").getClass()); + assertEquals(properties.get("property1"), hMap.get("property1")); + assertEquals(String.class, hMap.get("property2").getClass()); + assertEquals(properties.get("property2"), hMap.get("property2")); + assertEquals(String.class, hMap.get("property3").getClass()); + assertEquals(properties.get("property3"), hMap.get("property3")); + } + + @Test + public void havingModifiedProperties_whenStreamConvert_thenNoClassCastException() { + compromiseProperties(properties); + HashMap hMap = PropertiesToHashMapConverter.streamConvert(properties); + assertEquals(5, hMap.size()); + + assertDoesNotThrow(() -> { + String s = hMap.get("property4"); + }); + assertEquals(String.class, hMap.get("property4").getClass()); + assertEquals("456", hMap.get("property4")); + + assertDoesNotThrow(() -> { + String s = hMap.get("5"); + }); + assertEquals("10.11", hMap.get("5")); + } + + @Test + public void havingModifiedProperties_whenLoopConvertAndStreamConvert_thenHashMapsSame() { + compromiseProperties(properties); + HashMap hMap1 = PropertiesToHashMapConverter.loopConvert(properties); + HashMap hMap2 = PropertiesToHashMapConverter.streamConvert(properties); + + assertEquals(hMap2, hMap1); + } + + @Test + public void havingNonModifiedProperties_whenGuavaConvert_thenNoTypeSafeIssues() { + HashMap hMap = PropertiesToHashMapConverter.guavaConvert(properties); + + assertEquals(3, hMap.size()); + assertEquals(String.class, hMap.get("property1").getClass()); + assertEquals(properties.get("property1"), hMap.get("property1")); + assertEquals(String.class, hMap.get("property2").getClass()); + assertEquals(properties.get("property2"), hMap.get("property2")); + assertEquals(String.class, hMap.get("property3").getClass()); + assertEquals(properties.get("property3"), hMap.get("property3")); + } + + @Test + public void havingModifiedProperties_whenGuavaConvert_thenUnableToConvertAndThrowException() { + compromiseProperties(properties); + assertThrows(Exception.class, () -> PropertiesToHashMapConverter.guavaConvert(properties)); + } + + @Test + public void havingModifiedPropertiesWithNoIntegerValue_whenGuavaConvert_thenNullPointerException() { + properties.put("property4", 456); + assertThrows(NullPointerException.class, () -> PropertiesToHashMapConverter.guavaConvert(properties)); + } + + @Test + public void havingModifiedPropertiesWithNoIntegerKey_whenGuavaConvert_thenClassCastException() { + properties.put(5, 10.11); + assertThrows(ClassCastException.class, () -> PropertiesToHashMapConverter.guavaConvert(properties)); + } + + + private void compromiseProperties(Properties prop) { + prop.put("property4", 456); + prop.put(5, 10.11); + } +} diff --git a/core-java-modules/core-java-collections-maps-3/src/test/resources/toHashMap.properties b/core-java-modules/core-java-collections-maps-3/src/test/resources/toHashMap.properties new file mode 100644 index 0000000000..727e858a8c --- /dev/null +++ b/core-java-modules/core-java-collections-maps-3/src/test/resources/toHashMap.properties @@ -0,0 +1,3 @@ +property1=str_value +property2=123 +property3= diff --git a/core-java-modules/core-java-concurrency-collections/src/test/java/com/baeldung/java/stream/ThreadPoolInParallelStreamIntegrationTest.java b/core-java-modules/core-java-concurrency-collections/src/test/java/com/baeldung/java/stream/ThreadPoolInParallelStreamIntegrationTest.java index 7ee849b0a2..ce96c107f8 100644 --- a/core-java-modules/core-java-concurrency-collections/src/test/java/com/baeldung/java/stream/ThreadPoolInParallelStreamIntegrationTest.java +++ b/core-java-modules/core-java-concurrency-collections/src/test/java/com/baeldung/java/stream/ThreadPoolInParallelStreamIntegrationTest.java @@ -21,14 +21,16 @@ public class ThreadPoolInParallelStreamIntegrationTest { long lastNum = 1_000_000; List aList = LongStream.rangeClosed(firstNum, lastNum).boxed().collect(Collectors.toList()); - ForkJoinPool customThreadPool = new ForkJoinPool(4); - long actualTotal = customThreadPool - .submit(() -> aList.parallelStream() - .reduce(0L, Long::sum)) - .get(); - assertEquals((lastNum + firstNum) * lastNum / 2, actualTotal); + try { + long actualTotal = customThreadPool + .submit(() -> aList.parallelStream().reduce(0L, Long::sum)) + .get(); + assertEquals((lastNum + firstNum) * lastNum / 2, actualTotal); + } finally { + customThreadPool.shutdown(); + } } @Test diff --git a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java index caedd1747b..c20752639f 100644 --- a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java +++ b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java @@ -5,12 +5,12 @@ import com.google.common.io.CharSource; import com.google.common.io.Files; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.ReaderInputStream; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.charset.StandardCharsets; public class JavaXToInputStreamUnitTest { protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -28,7 +28,7 @@ public class JavaXToInputStreamUnitTest { @Test public final void givenUsingGuava_whenConvertingStringToInputStream_thenCorrect() throws IOException { final String initialString = "text"; - final InputStream targetStream = new ReaderInputStream(CharSource.wrap(initialString).openStream()); + final InputStream targetStream = CharSource.wrap(initialString).asByteSource(StandardCharsets.UTF_8).openStream(); IOUtils.closeQuietly(targetStream); } diff --git a/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule/pom.xml b/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule/pom.xml index e6b351b1b9..816f5cf9e8 100644 --- a/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule/pom.xml +++ b/core-java-modules/core-java-jpms/decoupling-pattern2/consumermodule/pom.xml @@ -17,7 +17,7 @@ com.baeldung.servicemodule - servicemodule + servicemodule2 ${servicemodule.version} @@ -41,4 +41,4 @@ 1.0 - \ No newline at end of file + diff --git a/core-java-modules/core-java-jvm/pom.xml b/core-java-modules/core-java-jvm/pom.xml index 08e536998c..17ff2f658f 100644 --- a/core-java-modules/core-java-jvm/pom.xml +++ b/core-java-modules/core-java-jvm/pom.xml @@ -77,7 +77,6 @@ 3.6.1 3.27.0-GA - 2.1.0.1 1.8.0 0.10 8.0.1 diff --git a/core-java-modules/core-java-lang-3/README.md b/core-java-modules/core-java-lang-3/README.md index 5279cc23b0..8ed945a56c 100644 --- a/core-java-modules/core-java-lang-3/README.md +++ b/core-java-modules/core-java-lang-3/README.md @@ -11,4 +11,5 @@ This module contains articles about core features in the Java language - [The transient Keyword in Java](https://www.baeldung.com/java-transient-keyword) - [How to Access an Iteration Counter in a For Each Loop](https://www.baeldung.com/java-foreach-counter) - [Comparing Doubles in Java](https://www.baeldung.com/java-comparing-doubles) +- [Guide to Implementing the compareTo Method](https://www.baeldung.com/java-compareto) - [[<-- Prev]](/core-java-modules/core-java-lang-2) diff --git a/core-java-modules/core-java-lang-4/README.md b/core-java-modules/core-java-lang-4/README.md new file mode 100644 index 0000000000..77d14f0fc8 --- /dev/null +++ b/core-java-modules/core-java-lang-4/README.md @@ -0,0 +1,5 @@ +## Core Java Lang (Part 4) + +This module contains articles about core features in the Java language + +- [The Java final Keyword – Impact on Performance](https://www.baeldung.com/java-final-performance) diff --git a/core-java-modules/core-java-lang-4/pom.xml b/core-java-modules/core-java-lang-4/pom.xml new file mode 100644 index 0000000000..3e92e9f9c7 --- /dev/null +++ b/core-java-modules/core-java-lang-4/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + core-java-lang-4 + 0.1.0-SNAPSHOT + core-java-lang-4 + jar + + + com.baeldung.core-java-modules + core-java-modules + 0.0.1-SNAPSHOT + ../ + + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + core-java-lang-4 + + + src/main/resources + true + + + + + + 1.28 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/BenchmarkRunner.java b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/BenchmarkRunner.java new file mode 100644 index 0000000000..ee34114195 --- /dev/null +++ b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/BenchmarkRunner.java @@ -0,0 +1,34 @@ +package com.baeldung.finalkeyword; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; + +import java.util.concurrent.TimeUnit; + +public class BenchmarkRunner { + + public static void main(String[] args) throws Exception { + org.openjdk.jmh.Main.main(args); + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public static String concatNonFinalStrings() { + String x = "x"; + String y = "y"; + return x + y; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @BenchmarkMode(Mode.AverageTime) + public static String concatFinalStrings() { + final String x = "x"; + final String y = "y"; + return x + y; + } + +} diff --git a/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableFinal.java b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableFinal.java new file mode 100644 index 0000000000..1aeef76e58 --- /dev/null +++ b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableFinal.java @@ -0,0 +1,19 @@ +package com.baeldung.finalkeyword; + +import java.io.Console; + +public class ClassVariableFinal { + + static final boolean doX = false; + static final boolean doY = true; + + public static void main(String[] args) { + Console console = System.console(); + if (doX) { + console.writer().println("x"); + } else if (doY) { + console.writer().println("y"); + } + } + +} diff --git a/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableNonFinal.java b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableNonFinal.java new file mode 100644 index 0000000000..a6d83a66cb --- /dev/null +++ b/core-java-modules/core-java-lang-4/src/main/java/com/baeldung/finalkeyword/ClassVariableNonFinal.java @@ -0,0 +1,19 @@ +package com.baeldung.finalkeyword; + +import java.io.Console; + +public class ClassVariableNonFinal { + + static boolean doX = false; + static boolean doY = true; + + public static void main(String[] args) { + Console console = System.console(); + if (doX) { + console.writer().println("x"); + } else if (doY) { + console.writer().println("y"); + } + } + +} diff --git a/core-java-modules/core-java-lang-math-3/README.md b/core-java-modules/core-java-lang-math-3/README.md index dda3013407..1dd3a3c7e0 100644 --- a/core-java-modules/core-java-lang-math-3/README.md +++ b/core-java-modules/core-java-lang-math-3/README.md @@ -4,6 +4,5 @@ ### Relevant articles: -- [Calculate Factorial in Java](https://www.baeldung.com/java-calculate-factorial) - [Evaluating a Math Expression in Java](https://www.baeldung.com/java-evaluate-math-expression-string) - More articles: [[<-- Prev]](/core-java-modules/core-java-lang-math-2) diff --git a/core-java-modules/core-java-lang-oop-generics/README.md b/core-java-modules/core-java-lang-oop-generics/README.md index 9c9080ece3..720ba9dcfd 100644 --- a/core-java-modules/core-java-lang-oop-generics/README.md +++ b/core-java-modules/core-java-lang-oop-generics/README.md @@ -8,3 +8,4 @@ This module contains articles about generics in Java - [Raw Types in Java](https://www.baeldung.com/raw-types-java) - [Super Type Tokens in Java Generics](https://www.baeldung.com/java-super-type-tokens) - [Java Warning “unchecked conversion”](https://www.baeldung.com/java-unchecked-conversion) +- [Java Warning “Unchecked Cast”](https://www.baeldung.com/java-warning-unchecked-cast) diff --git a/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/uncheckedcast/UncheckedCast.java b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/uncheckedcast/UncheckedCast.java new file mode 100644 index 0000000000..fe79488d11 --- /dev/null +++ b/core-java-modules/core-java-lang-oop-generics/src/main/java/com/baeldung/uncheckedcast/UncheckedCast.java @@ -0,0 +1,26 @@ +package com.baeldung.uncheckedcast; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class UncheckedCast { + public static Map getRawMap() { + Map rawMap = new HashMap(); + rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10)); + rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8)); + rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18)); + return rawMap; + } + + public static Map getRawMapWithMixedTypes() { + Map rawMap = new HashMap(); + rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10)); + rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8)); + rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18)); + rawMap.put("date 4", new Date()); + return rawMap; + } +} diff --git a/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/uncheckedcast/UncheckedCastUnitTest.java b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/uncheckedcast/UncheckedCastUnitTest.java new file mode 100644 index 0000000000..b1bd46555e --- /dev/null +++ b/core-java-modules/core-java-lang-oop-generics/src/test/java/com/baeldung/uncheckedcast/UncheckedCastUnitTest.java @@ -0,0 +1,25 @@ +package com.baeldung.uncheckedcast; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDate; +import java.time.Month; +import java.util.Map; + +public class UncheckedCastUnitTest { + + @Test + public void givenRawMap_whenCastToTypedMap_shouldHaveCompilerWarning() { + Map castFromRawMap = (Map) UncheckedCast.getRawMap(); + Assert.assertEquals(3, castFromRawMap.size()); + Assert.assertEquals(castFromRawMap.get("date 2"), LocalDate.of(1992, Month.AUGUST, 8)); + } + + @Test(expected = ClassCastException.class) + public void givenMixTypedRawMap_whenCastToTypedMap_shouldThrowClassCastException() { + Map castFromRawMap = (Map) UncheckedCast.getRawMapWithMixedTypes(); + Assert.assertEquals(4, castFromRawMap.size()); + Assert.assertTrue(castFromRawMap.get("date 4").isAfter(castFromRawMap.get("date 3"))); + } +} diff --git a/core-java-modules/core-java-lang-oop-types/src/main/java/com/baeldung/classfile/Outer.java b/core-java-modules/core-java-lang-oop-types/src/main/java/com/baeldung/classfile/Outer.java new file mode 100644 index 0000000000..b140f7085b --- /dev/null +++ b/core-java-modules/core-java-lang-oop-types/src/main/java/com/baeldung/classfile/Outer.java @@ -0,0 +1,131 @@ +package com.baeldung.classfile; + +import org.apache.commons.lang3.StringUtils; +import com.baeldung.classfile.HelloWorld.HelloSomeone; + +public class Outer { + + // Static Nested class + static class StaticNested { + public String message() { + return "This is a static Nested Class"; + } + } + + // Non-static Nested class + class Nested { + public String message() { + return "This is a non-static Nested Class"; + } + } + + // Local class + public String message() { + class Local { + private String message() { + return "This is a Local Class within a method"; + } + } + Local local = new Local(); + return local.message(); + } + + // Local class within if clause + public String message(String name) { + if (StringUtils.isEmpty(name)) { + class Local { + private String message() { + return "This is a Local Class within if clause"; + } + } + Local local = new Local(); + return local.message(); + } else + return "Welcome to " + name; + } + + // Anonymous Inner class extending a class + public String greet() { + Outer anonymous = new Outer() { + public String greet() { + return "Running Anonymous Class..."; + } + }; + return anonymous.greet(); + } + + // Anonymous inner class implementing an interface + public String greet(String name) { + + HelloWorld helloWorld = new HelloWorld() { + public String greet(String name) { + return "Welcome to " + name; + } + + }; + return helloWorld.greet(name); + } + + // Anonymous inner class implementing nested interface + public String greetSomeone(String name) { + + HelloSomeone helloSomeOne = new HelloSomeone() { + public String greet(String name) { + return "Hello " + name; + } + + }; + return helloSomeOne.greet(name); + } + + // Nested interface within a class + interface HelloOuter { + public String hello(String name); + } + + // Enum within a class + enum Color { + RED, GREEN, BLUE; + } +} + +interface HelloWorld { + + public String greet(String name); + + // Nested class within an interface + class InnerClass { + public String greet(String name) { + return "Inner class within an interface"; + } + } + + // Nested interface within an interfaces + interface HelloSomeone { + public String greet(String name); + } + + // Enum within an interface + enum Directon { + NORTH, SOUTH, EAST, WEST; + } +} + +enum Level { + LOW, MEDIUM, HIGH; + +} + +enum Foods { + + DRINKS, EATS; + + // Enum within Enum + enum DRINKS { + APPLE_JUICE, COLA; + } + + enum EATS { + POTATO, RICE; + } +} diff --git a/core-java-modules/core-java-lang-oop-types/src/test/java/com/baeldung/classfile/OuterUnitTest.java b/core-java-modules/core-java-lang-oop-types/src/test/java/com/baeldung/classfile/OuterUnitTest.java new file mode 100644 index 0000000000..3ffe7d561a --- /dev/null +++ b/core-java-modules/core-java-lang-oop-types/src/test/java/com/baeldung/classfile/OuterUnitTest.java @@ -0,0 +1,46 @@ +package com.baeldung.classfile; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import com.baeldung.classfile.Outer.Nested; + +public class OuterUnitTest { + + @Test + public void when_static_nestedClass_then_verifyOutput() { + Outer.StaticNested nestedClass = new Outer.StaticNested(); + assertEquals("This is a static Nested Class", nestedClass.message()); + } + + @Test + public void when_nestedClass_then_verifyOutput() { + Outer outer = new Outer(); + Nested nestedClass = outer.new Nested(); + assertEquals("This is a non-static Nested Class", nestedClass.message()); + } + + @Test + public void when_localClass_then_verifyOutput() { + Outer outer = new Outer(); + assertEquals("This is a Local Class within a method", outer.message()); + } + + @Test + public void when_localClassInIfClause_then_verifyOutput() { + Outer outer = new Outer(); + assertEquals("Welcome to Baeldung", outer.message("Baeldung")); + assertEquals("This is a Local Class within if clause", outer.message("")); + } + + @Test + public void when_anonymousClass_then_verifyOutput() { + Outer outer = new Outer(); + assertEquals("Running Anonymous Class...", outer.greet()); + } + + @Test + public void when_anonymousClassHelloWorld_then_verifyOutput() { + Outer outer = new Outer(); + assertEquals("Welcome to Baeldung", outer.greet("Baeldung")); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-nio-2/pom.xml b/core-java-modules/core-java-nio-2/pom.xml index 0c7c079406..7c4583476d 100644 --- a/core-java-modules/core-java-nio-2/pom.xml +++ b/core-java-modules/core-java-nio-2/pom.xml @@ -14,5 +14,12 @@ 0.0.1-SNAPSHOT ../ - + + + org.assertj + assertj-core + 3.6.1 + test + + \ No newline at end of file diff --git a/core-java-modules/core-java-nio-2/src/test/java/com/baeldung/selector/SelectorManualTest.java b/core-java-modules/core-java-nio-2/src/test/java/com/baeldung/selector/SelectorManualTest.java new file mode 100644 index 0000000000..7a6c9e6eab --- /dev/null +++ b/core-java-modules/core-java-nio-2/src/test/java/com/baeldung/selector/SelectorManualTest.java @@ -0,0 +1,64 @@ +package com.baeldung.selector; + +import org.junit.Test; +import java.io.IOException; +import java.nio.channels.Pipe; +import java.nio.channels.SelectableChannel; +import java.nio.channels.Selector; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +import static java.nio.channels.SelectionKey.OP_READ; + +public class SelectorManualTest { + + @Test + public void whenWakeUpCalledOnSelector_thenBlockedThreadReturns() throws IOException, InterruptedException { + Pipe pipe = Pipe.open(); + + Selector selector = Selector.open(); + SelectableChannel channel = pipe.source(); + channel.configureBlocking(false); + channel.register(selector, OP_READ); + + List invocationStepsTracker = Collections.synchronizedList(new ArrayList<>()); + + CountDownLatch latch = new CountDownLatch(1); + + Thread thread = new Thread(() -> { + invocationStepsTracker.add(">> Count down"); + latch.countDown(); + try { + invocationStepsTracker.add(">> Start select"); + selector.select(); + invocationStepsTracker.add(">> End select"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + invocationStepsTracker.add(">> Start await"); + thread.start(); + latch.await(); + invocationStepsTracker.add(">> End await"); + + invocationStepsTracker.add(">> Wakeup thread"); + + selector.wakeup(); + channel.close(); + + assertThat(invocationStepsTracker) + .containsExactly( + ">> Start await", + ">> Count down", + ">> Start select", + ">> End await", + ">> Wakeup thread", + ">> End select" + ); + } +} diff --git a/core-java-modules/core-java-reflection-2/README.md b/core-java-modules/core-java-reflection-2/README.md index 9ed14f08dc..3195cddc42 100644 --- a/core-java-modules/core-java-reflection-2/README.md +++ b/core-java-modules/core-java-reflection-2/README.md @@ -4,3 +4,4 @@ - [Set Field Value With Reflection](https://www.baeldung.com/java-set-private-field-value) - [Checking If a Method is Static Using Reflection in Java](https://www.baeldung.com/java-check-method-is-static) - [Checking if a Java Class is ‘abstract’ Using Reflection](https://www.baeldung.com/java-reflection-is-class-abstract) +- [Invoking a Private Method in Java](https://www.baeldung.com/java-call-private-method) diff --git a/core-java-modules/core-java-reflection-2/pom.xml b/core-java-modules/core-java-reflection-2/pom.xml index ea76ee8c98..02a806f87c 100644 --- a/core-java-modules/core-java-reflection-2/pom.xml +++ b/core-java-modules/core-java-reflection-2/pom.xml @@ -14,6 +14,15 @@ ../ + + + org.springframework + spring-test + ${spring.version} + test + + + core-java-reflection-2 @@ -40,5 +49,6 @@ 3.8.0 1.8 1.8 + 5.3.4 \ No newline at end of file diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java new file mode 100644 index 0000000000..03d8daabbf --- /dev/null +++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java @@ -0,0 +1,18 @@ +package com.baeldung.reflection.access.privatemethods; + +public class LongArrayUtil { + + public static int indexOf(long[] array, long target) { + return indexOf(array, target, 0, array.length); + } + + private static int indexOf(long[] array, long target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + +} diff --git a/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java new file mode 100644 index 0000000000..e98eb9ec7f --- /dev/null +++ b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java @@ -0,0 +1,28 @@ +package com.baeldung.reflection.access.privatemethods; + +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InvokePrivateMethodsUnitTest { + + private final long[] someLongArray = new long[] { 1L, 2L, 1L, 4L, 2L }; + + @Test + void whenSearchingForLongValueInSubsequenceUsingReflection_thenTheCorrectIndexOfTheValueIsReturned() throws Exception { + Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod("indexOf", long[].class, long.class, int.class, int.class); + indexOfMethod.setAccessible(true); + + assertEquals(2, indexOfMethod.invoke(LongArrayUtil.class, someLongArray, 1L, 1, someLongArray.length), "The index should be 2."); + } + + @Test + void whenSearchingForLongValueInSubsequenceUsingSpring_thenTheCorrectIndexOfTheValueIsReturned() throws Exception { + int indexOfSearchTarget = ReflectionTestUtils.invokeMethod(LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length); + assertEquals(2, indexOfSearchTarget, "The index should be 2."); + } + +} diff --git a/core-java-modules/core-java-security-2/README.md b/core-java-modules/core-java-security-2/README.md index 9b99d624c9..3f9520b888 100644 --- a/core-java-modules/core-java-security-2/README.md +++ b/core-java-modules/core-java-security-2/README.md @@ -15,4 +15,5 @@ This module contains articles about core Java Security - [Security Context Basics: User, Subject and Principal](https://www.baeldung.com/security-context-basics) - [Java AES Encryption and Decryption](https://www.baeldung.com/java-aes-encryption-decryption) - [InvalidAlgorithmParameterException: Wrong IV Length](https://www.baeldung.com/java-invalidalgorithmparameter-exception) +- [The java.security.egd JVM Option](https://www.baeldung.com/java-security-egd) - More articles: [[<-- prev]](/core-java-modules/core-java-security) diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 2d342c4216..7dc54bd907 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -82,6 +82,7 @@ core-java-lang core-java-lang-2 core-java-lang-3 + core-java-lang-4 core-java-lang-math core-java-lang-math-2 core-java-lang-math-3 diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java index 6b315265b4..cde07728ba 100644 --- a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -10,10 +10,8 @@ public class CircularBuffer { @SuppressWarnings("unchecked") public CircularBuffer(int capacity) { - this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; - this.data = (E[]) new Object[capacity]; - + this.data = (E[]) new Object[this.capacity]; this.readSequence = 0; this.writeSequence = -1; } diff --git a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java index 47368d7f15..cf18f0783e 100644 --- a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java +++ b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java @@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory; public class CircularLinkedList { - final Logger LOGGER = LoggerFactory.getLogger(CircularLinkedList.class); + final Logger logger = LoggerFactory.getLogger(CircularLinkedList.class); private Node head = null; private Node tail = null; @@ -42,24 +42,29 @@ public class CircularLinkedList { } public void deleteNode(int valueToDelete) { - Node currentNode = head; - - if (head != null) { - if (currentNode.value == valueToDelete) { - head = head.nextNode; - tail.nextNode = head; - } else { - do { - Node nextNode = currentNode.nextNode; - if (nextNode.value == valueToDelete) { - currentNode.nextNode = nextNode.nextNode; - break; - } - currentNode = currentNode.nextNode; - } while (currentNode != head); - } + if (head == null) { + return; } + do { + Node nextNode = currentNode.nextNode; + if (nextNode.value == valueToDelete) { + if (tail == head) { + head = null; + tail = null; + } else { + currentNode.nextNode = nextNode.nextNode; + if (head == nextNode) { + head = head.nextNode; + } + if (tail == nextNode) { + tail = currentNode; + } + } + break; + } + currentNode = nextNode; + } while (currentNode != head); } public void traverseList() { @@ -68,7 +73,7 @@ public class CircularLinkedList { if (head != null) { do { - LOGGER.info(currentNode.value + " "); + logger.info(currentNode.value + " "); currentNode = currentNode.nextNode; } while (currentNode != head); } diff --git a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java index 5b0573a1ce..23829df7e9 100644 --- a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java +++ b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java @@ -1,10 +1,10 @@ package com.baeldung.circularlinkedlist; +import org.junit.Test; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.junit.Test; - public class CircularLinkedListUnitTest { @Test @@ -23,7 +23,7 @@ public class CircularLinkedListUnitTest { } @Test - public void givenACircularLinkedList_WhenDeletingElements_ThenListDoesNotContainThoseElements() { + public void givenACircularLinkedList_WhenDeletingInOrderHeadMiddleTail_ThenListDoesNotContainThoseElements() { CircularLinkedList cll = createCircularLinkedList(); assertTrue(cll.containsNode(13)); @@ -39,6 +39,32 @@ public class CircularLinkedListUnitTest { assertFalse(cll.containsNode(46)); } + @Test + public void givenACircularLinkedList_WhenDeletingInOrderTailMiddleHead_ThenListDoesNotContainThoseElements() { + CircularLinkedList cll = createCircularLinkedList(); + + assertTrue(cll.containsNode(46)); + cll.deleteNode(46); + assertFalse(cll.containsNode(46)); + + assertTrue(cll.containsNode(1)); + cll.deleteNode(1); + assertFalse(cll.containsNode(1)); + + assertTrue(cll.containsNode(13)); + cll.deleteNode(13); + assertFalse(cll.containsNode(13)); + } + + @Test + public void givenACircularLinkedListWithOneNode_WhenDeletingElement_ThenListDoesNotContainTheElement() { + CircularLinkedList cll = new CircularLinkedList(); + cll.addNode(1); + cll.deleteNode(1); + assertFalse(cll.containsNode(1)); + } + + private CircularLinkedList createCircularLinkedList() { CircularLinkedList cll = new CircularLinkedList(); diff --git a/docker/README.md b/docker/README.md index ce7fe261c2..f1b4181e64 100644 --- a/docker/README.md +++ b/docker/README.md @@ -3,3 +3,4 @@ - [Introduction to Docker Compose](https://www.baeldung.com/docker-compose) - [Reusing Docker Layers with Spring Boot](https://www.baeldung.com/docker-layers-spring-boot) - [Running Spring Boot with PostgreSQL in Docker Compose](https://www.baeldung.com/spring-boot-postgresql-docker) +- [How To Configure Java Heap Size Inside a Docker Container](https://www.baeldung.com/ops/docker-jvm-heap-size) diff --git a/docker/heap-sizing/Dockerfile b/docker/heap-sizing/Dockerfile new file mode 100644 index 0000000000..c455e45c70 --- /dev/null +++ b/docker/heap-sizing/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:8u92-jdk-alpine +COPY /src /src/ +RUN mkdir /app && ls /src && javac /src/main/java/com/baeldung/docker/heapsizing/PrintXmxXms.java -d /app +CMD java -version && java $JAVA_OPTS -cp /app com.baeldung.docker.heapsizing.PrintXmxXms \ No newline at end of file diff --git a/docker/heap-sizing/pom.xml b/docker/heap-sizing/pom.xml new file mode 100644 index 0000000000..a86a67fdcd --- /dev/null +++ b/docker/heap-sizing/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.2 + + + com.baeldung.docker + heap-sizing + 0.0.1-SNAPSHOT + heap-sizing + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + heapsizing-demo + + + + + com.google.cloud.tools + jib-maven-plugin + 2.7.1 + + + + heapsizing-demo-jib + + + + + + + diff --git a/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/HeapSizingApplication.java b/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/HeapSizingApplication.java new file mode 100644 index 0000000000..00bee376c5 --- /dev/null +++ b/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/HeapSizingApplication.java @@ -0,0 +1,19 @@ +package com.baeldung.docker.heapsizing; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.logging.Logger; + +import static com.baeldung.docker.heapsizing.PrintXmxXms.logMemory; + +@SpringBootApplication +public class HeapSizingApplication { + private static final Logger logger = Logger.getLogger(HeapSizingApplication.class.getName()); + + public static void main(String[] args) { + SpringApplication.run(HeapSizingApplication.class, args); + logMemory(logger); + } + +} diff --git a/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/PrintXmxXms.java b/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/PrintXmxXms.java new file mode 100644 index 0000000000..98f2f6455f --- /dev/null +++ b/docker/heap-sizing/src/main/java/com/baeldung/docker/heapsizing/PrintXmxXms.java @@ -0,0 +1,36 @@ +package com.baeldung.docker.heapsizing; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PrintXmxXms { + + private static final Logger logger = Logger.getLogger(PrintXmxXms.class.getName()); + + public static void main(String[] args) { + logMemory(logger); + } + + /** + * We're reusing this method in HeapSizingApplication, therefore this method was extracted + * to avoid repetition. + */ + static void logMemory(Logger logger) { + float mb = 1024f * 1024f; + MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + + // xmx controls the maximum size of the memory allocation pool, + // which includes the heap, the garbage collector's survivor space, and other pools. + float xmx = memoryBean.getHeapMemoryUsage().getMax() / mb; + float xms = memoryBean.getHeapMemoryUsage().getInit() / mb; + logger.log(Level.INFO, "Initial Memory (xms) : {0}mb", xms); + logger.log(Level.INFO, "Max Memory (xmx) : {0}mb", xmx); + + for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { + logger.log(Level.INFO, "Pool: {0} (type {1}) = {2}", new Object[]{ mp.getName(), mp.getType(), mp.getUsage().getMax() / mb }); + } + } +} diff --git a/guest/core-kotlin/README.md b/guest/core-kotlin/README.md index c211773f27..fad62ebea6 100644 --- a/guest/core-kotlin/README.md +++ b/guest/core-kotlin/README.md @@ -1,3 +1,3 @@ ### Relevant Articles: -- [Kotlin vs Java](https://www.baeldung.com/kotlin/kotlin-vs-java) +- [Kotlin vs Java](https://www.baeldung.com/kotlin/vs-java) diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java index 0ca8d721e8..1bd2451c6c 100644 --- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java @@ -1,12 +1,12 @@ package com.baeldung.jackson.bidirection; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonBackReference; public class ItemWithRef { public int id; public String itemName; - @JsonManagedReference + @JsonBackReference public UserWithRef owner; public ItemWithRef() { diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java index 3de03fc651..cc7b2b52ca 100644 --- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java @@ -3,13 +3,13 @@ package com.baeldung.jackson.bidirection; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; public class UserWithRef { public int id; public String name; - @JsonBackReference + @JsonManagedReference public List userItems; public UserWithRef() { @@ -19,7 +19,7 @@ public class UserWithRef { public UserWithRef(final int id, final String name) { this.id = id; this.name = name; - userItems = new ArrayList(); + userItems = new ArrayList<>(); } public void addItem(final ItemWithRef item) { diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java index d0c5209891..b3ce27ad94 100644 --- a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java @@ -1,7 +1,9 @@ package com.baeldung.jackson.bidirection; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -16,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonBidirectionRelationUnitTest { - @Test(expected = JsonMappingException.class) + @Test (expected = JsonMappingException.class) public void givenBidirectionRelation_whenSerializing_thenException() throws JsonProcessingException { final User user = new User(1, "John"); final Item item = new Item(2, "book", user); @@ -26,16 +28,39 @@ public class JacksonBidirectionRelationUnitTest { } @Test - public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect() throws JsonProcessingException { + public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithSerialization_thenCorrect() throws JsonProcessingException { final UserWithRef user = new UserWithRef(1, "John"); final ItemWithRef item = new ItemWithRef(2, "book", user); user.addItem(item); - final String result = new ObjectMapper().writeValueAsString(item); + final String itemJson = new ObjectMapper().writeValueAsString(item); + final String userJson = new ObjectMapper().writeValueAsString(user); - assertThat(result, containsString("book")); - assertThat(result, containsString("John")); - assertThat(result, not(containsString("userItems"))); + assertThat(itemJson, containsString("book")); + assertThat(itemJson, not(containsString("John"))); + + assertThat(userJson, containsString("John")); + assertThat(userJson, containsString("userItems")); + assertThat(userJson, containsString("book")); + } + + @Test + public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithDeserialization_thenCorrect() throws JsonProcessingException { + final UserWithRef user = new UserWithRef(1, "John"); + final ItemWithRef item = new ItemWithRef(2, "book", user); + user.addItem(item); + + final String itemJson = new ObjectMapper().writeValueAsString(item); + final String userJson = new ObjectMapper().writeValueAsString(user); + + final ItemWithRef itemRead = new ObjectMapper().readValue(itemJson, ItemWithRef.class); + final UserWithRef userRead = new ObjectMapper().readValue(userJson, UserWithRef.class); + + assertThat(itemRead.itemName, is("book")); + assertThat(itemRead.owner, nullValue()); + + assertThat(userRead.name, is("John")); + assertThat(userRead.userItems.get(0).itemName, is("book")); } @Test diff --git a/java-collections-maps-3/README.md b/java-collections-maps-3/README.md index bd1029c9cf..9b1d8510c2 100644 --- a/java-collections-maps-3/README.md +++ b/java-collections-maps-3/README.md @@ -3,3 +3,4 @@ - [Java Map With Case-Insensitive Keys](https://www.baeldung.com/java-map-with-case-insensitive-keys) - [Using a Byte Array as Map Key in Java](https://www.baeldung.com/java-map-key-byte-array) - [Using the Map.Entry Java Class](https://www.baeldung.com/java-map-entry) +- [Optimizing HashMap’s Performance](https://www.baeldung.com/java-hashmap-optimize-performance) diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java new file mode 100644 index 0000000000..22b6a61d4d --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java @@ -0,0 +1,6 @@ +package com.baeldung.map.hashing; + +class Member { + Integer id; + String name; +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java new file mode 100644 index 0000000000..2a69291742 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java @@ -0,0 +1,8 @@ +package com.baeldung.map.hashing; + +public class MemberWithBadHashing extends Member { + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java new file mode 100644 index 0000000000..bb33ace0b7 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java @@ -0,0 +1,18 @@ +package com.baeldung.map.hashing; + +import com.google.common.base.Charsets; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +public class MemberWithGuavaHashing extends Member { + @Override + public int hashCode() { + HashFunction hashFunction = Hashing.murmur3_32(); + return hashFunction.newHasher() + .putInt(id) + .putString(name, Charsets.UTF_8) + .hash().hashCode(); + } + + +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java new file mode 100644 index 0000000000..5d82eb0cb7 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java @@ -0,0 +1,18 @@ +package com.baeldung.map.hashing; + +public class MemberWithId extends Member { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MemberWithId that = (MemberWithId) o; + + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java new file mode 100644 index 0000000000..81ae48cf55 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java @@ -0,0 +1,23 @@ +package com.baeldung.map.hashing; + +import java.util.Objects; + +public class MemberWithIdAndName extends Member { + public static final int PRIME = 31; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemberWithObjects that = (MemberWithObjects) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = PRIME * result + (name == null ? 0 : name.hashCode()); + return result; + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java new file mode 100644 index 0000000000..b641035e4f --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java @@ -0,0 +1,19 @@ +package com.baeldung.map.hashing; + +import java.util.Objects; + +public class MemberWithObjects extends Member { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemberWithObjects that = (MemberWithObjects) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } +} diff --git a/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java b/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java new file mode 100644 index 0000000000..d28a3b50b5 --- /dev/null +++ b/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java @@ -0,0 +1,76 @@ +package com.baeldung.map.hashing; + +import com.google.common.base.Stopwatch; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.HashMap; +import java.util.SplittableRandom; +import java.util.function.Supplier; + +public class HashingUnitTest { + + public static final int SAMPLES = 1000000; + private SplittableRandom random = new SplittableRandom(); + + private String[] names = {"John", "Adam", "Suzie"}; + + @Test + void givenPrimitiveByteArrayKey_whenRetrievingFromMap_shouldRetrieveDifferentObjects() { + // bad hashing example is prohibitively slow for bigger samples +// Duration[] badHashing = testDuration(MemberWithBadHashing::new); + Duration[] withId = testDuration(MemberWithId::new); + Duration[] withObjects = testDuration(MemberWithObjects::new); + Duration[] withIdAndName = testDuration(MemberWithIdAndName::new); + +// System.out.println("Inserting with bad hashing:"); +// System.out.println(badHashing[0]); +// System.out.println("Getting with bad hashing:"); +// System.out.println(badHashing[1]); + + System.out.println("Inserting with id hashing:"); + System.out.println(withId[0]); + System.out.println("Getting with id hashing:"); + System.out.println(withId[1]); + + System.out.println("Inserting with id and name hashing:"); + System.out.println(withIdAndName[0]); + System.out.println("Getting with id and name hashing:"); + System.out.println(withIdAndName[1]); + + System.out.println("Inserting with Objects hashing:"); + System.out.println(withObjects[0]); + System.out.println("Getting with Objects hashing:"); + System.out.println(withObjects[1]); + } + + private String randomName() { + return names[random.nextInt(2)]; + } + + private Duration[] testDuration(Supplier factory) { + HashMap map = new HashMap<>(); + Stopwatch stopwatch = Stopwatch.createUnstarted(); + + stopwatch.start(); + for(int i = 0; i < SAMPLES; i++) { + T member = factory.get(); + member.id = i; + member.name = randomName(); + map.put(member, member.name); + } + stopwatch.stop(); + Duration elapsedInserting = stopwatch.elapsed(); + stopwatch.reset(); + + stopwatch.start(); + for (T key : map.keySet()) { + map.get(key); + } + stopwatch.stop(); + Duration elapsedGetting = stopwatch.elapsed(); + stopwatch.reset(); + + return new Duration[]{elapsedInserting, elapsedGetting}; + } +} diff --git a/java-rmi/src/test/java/com/baeldung/rmi/JavaRMIIntegrationTest.java b/java-rmi/src/test/java/com/baeldung/rmi/JavaRMIIntegrationTest.java index 66bfbe49eb..5e4ecb3e76 100644 --- a/java-rmi/src/test/java/com/baeldung/rmi/JavaRMIIntegrationTest.java +++ b/java-rmi/src/test/java/com/baeldung/rmi/JavaRMIIntegrationTest.java @@ -20,7 +20,7 @@ public class JavaRMIIntegrationTest { MessengerServiceImpl server = new MessengerServiceImpl(); server.createStubAndBind(); } catch (RemoteException e) { - fail("Exception Occurred"); + fail("Exception Occurred: " + e); } } @@ -34,11 +34,9 @@ public class JavaRMIIntegrationTest { String expectedMessage = "Server Message"; assertEquals(responseMessage, expectedMessage); - } catch (RemoteException e) { - fail("Exception Occurred"); - } catch (NotBoundException nb) { - fail("Exception Occurred"); - } + } catch (RemoteException | NotBoundException e) { + fail("Exception Occurred: " + e); + }; } } \ No newline at end of file diff --git a/jjwt/README.md b/jjwt/README.md index 25f5a8f6f0..1798d5193b 100644 --- a/jjwt/README.md +++ b/jjwt/README.md @@ -47,3 +47,4 @@ Available commands (assumes httpie - https://github.com/jkbrzt/httpie): ## Relevant articles: - [Supercharge Java Authentication with JSON Web Tokens (JWTs)](https://www.baeldung.com/java-json-web-tokens-jjwt) +- [Decode a JWT Token in Java](https://www.baeldung.com/java-jwt-token-decode) diff --git a/jjwt/pom.xml b/jjwt/pom.xml index aa238fafb5..9d38d1b0e9 100644 --- a/jjwt/pom.xml +++ b/jjwt/pom.xml @@ -41,6 +41,12 @@ jjwt ${jjwt.version} + + + org.assertj + assertj-core + test + diff --git a/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtil.java b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtil.java new file mode 100644 index 0000000000..922d5c0ce5 --- /dev/null +++ b/jjwt/src/main/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtil.java @@ -0,0 +1,46 @@ +package io.jsonwebtoken.jjwtfun.util; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator; + +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; + +import static io.jsonwebtoken.SignatureAlgorithm.HS256; + +public class JWTDecoderUtil { + + public static String decodeJWTToken(String token) { + Base64.Decoder decoder = Base64.getDecoder(); + + String[] chunks = token.split("\\."); + + String header = new String(decoder.decode(chunks[0])); + String payload = new String(decoder.decode(chunks[1])); + + return header + " " + payload; + } + + public static String decodeJWTToken(String token, String secretKey) throws Exception { + Base64.Decoder decoder = Base64.getDecoder(); + + String[] chunks = token.split("\\."); + + String header = new String(decoder.decode(chunks[0])); + String payload = new String(decoder.decode(chunks[1])); + + String tokenWithoutSignature = chunks[0] + "." + chunks[1]; + String signature = chunks[2]; + + SignatureAlgorithm sa = HS256; + SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), sa.getJcaName()); + + DefaultJwtSignatureValidator validator = new DefaultJwtSignatureValidator(sa, secretKeySpec); + + if (!validator.isValid(tokenWithoutSignature, signature)) { + throw new Exception("Could not verify JWT token integrity!"); + } + + return header + " " + payload; + } +} diff --git a/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtilUnitTest.java b/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtilUnitTest.java new file mode 100644 index 0000000000..3103a6c8a3 --- /dev/null +++ b/jjwt/src/test/java/io/jsonwebtoken/jjwtfun/util/JWTDecoderUtilUnitTest.java @@ -0,0 +1,32 @@ +package io.jsonwebtoken.jjwtfun.util; + +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class JWTDecoderUtilUnitTest { + + private final static String SIMPLE_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJhZWxkdW5nIFVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9"; + private final static String SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJhZWxkdW5nIFVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.qH7Zj_m3kY69kxhaQXTa-ivIpytKXXjZc1ZSmapZnGE"; + + @Test + void givenSimpleToken_whenDecoding_thenStringOfHeaderPayloadAreReturned() { + assertThat(JWTDecoderUtil.decodeJWTToken(SIMPLE_TOKEN)) + .contains(SignatureAlgorithm.HS256.getValue()); + } + + @Test + void givenSignedToken_whenDecodingWithInvalidSecret_thenIntegrityIsNotValidated() { + assertThatThrownBy(() -> JWTDecoderUtil.decodeJWTToken(SIGNED_TOKEN, "BAD_SECRET")) + .hasMessage("Could not verify JWT token integrity!"); + } + + @Test + void givenSignedToken_whenDecodingWithValidSecret_thenIntegrityIsValidated() throws Exception { + assertThat(JWTDecoderUtil.decodeJWTToken(SIGNED_TOKEN, "MySecretKey")) + .contains("Baeldung User"); + } +} diff --git a/jmeter/README.md b/jmeter/README.md index 81300afe7c..53aca8d34b 100644 --- a/jmeter/README.md +++ b/jmeter/README.md @@ -7,7 +7,7 @@ It contains the code of a simple API for some CRUD operations built using Spring - Maven - JDK 8 -- MongoDB +- MongoDB (Note: for the Write Extracted Data to a File Using JMeter example MongoDB is not required) ### Running @@ -36,6 +36,14 @@ Or create a new one via a POST: $ curl -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Dassi", "lastName" : "Orleando", "phoneNumber": "+237 545454545", "email": "mymail@yahoo.fr" }' localhost:8080/students ``` +### Available UUID API + +You can view the test response using curl: + +```bash +$ curl localhost:8080/api/uuid +``` + Now with default configurations it will be available at: [http://localhost:8080](http://localhost:8080) Enjoy it :) @@ -44,3 +52,4 @@ Enjoy it :) - [Intro to Performance Testing using JMeter](https://www.baeldung.com/jmeter) - [Configure Jenkins to Run and Show JMeter Tests](https://www.baeldung.com/jenkins-and-jmeter) +- [Write Extracted Data to a File Using JMeter](https://www.baeldung.com/jmeter-write-to-file) diff --git a/jmeter/src/main/java/com/baeldung/controller/RetrieveUuidController.java b/jmeter/src/main/java/com/baeldung/controller/RetrieveUuidController.java new file mode 100644 index 0000000000..32265c0170 --- /dev/null +++ b/jmeter/src/main/java/com/baeldung/controller/RetrieveUuidController.java @@ -0,0 +1,18 @@ +package com.baeldung.controller; + +import com.baeldung.model.Response; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +import static java.lang.String.format; + +@RestController +public class RetrieveUuidController { + + @GetMapping("/api/uuid") + public Response uuid() { + return new Response(format("Test message... %s.", UUID.randomUUID())); + } +} diff --git a/jmeter/src/main/java/com/baeldung/model/Response.java b/jmeter/src/main/java/com/baeldung/model/Response.java new file mode 100644 index 0000000000..547e5b536e --- /dev/null +++ b/jmeter/src/main/java/com/baeldung/model/Response.java @@ -0,0 +1,40 @@ +package com.baeldung.model; + +import java.time.Instant; +import java.util.UUID; + +public class Response { + private Instant timestamp; + private UUID uuid; + private String message; + + public Response(String message) { + this.timestamp = Instant.now(); + this.uuid = UUID.randomUUID(); + this.message = message; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/jmeter/src/main/resources/FileExtractionExample.jmx b/jmeter/src/main/resources/FileExtractionExample.jmx new file mode 100644 index 0000000000..961b6f143f --- /dev/null +++ b/jmeter/src/main/resources/FileExtractionExample.jmx @@ -0,0 +1,124 @@ + + + + + To run this test plan you must also be running the Spring application "JmeterApplication" That can be found in this directory + false + true + false + + + + + + + + continue + + false + 1 + + 1 + 1 + false + + + true + + + + + + + localhost + 8080 + http + + /api/test + GET + true + false + true + false + + + + + + + message + $.message + 1 + true + NOT_FOUND + + + + + + false + FileWriter fWriter = new FileWriter("/result.txt", true); +BufferedWriter buff = new BufferedWriter(fWriter); + +buff.write("Response Code : " + ctx.getPreviousResult().getResponseCode()); +buff.write(System.getProperty("line.separator")); +buff.write("Response Headers : " + ctx.getPreviousResult().getResponseHeaders()); +buff.write(System.getProperty("line.separator")); +buff.write("Response Body : " + new String(ctx.getPreviousResult().getResponseData())); + +buff.write("More complex extraction : " + vars.get("message")); + +buff.close(); +fWriter.close(); + + + + response + false + false + false + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + diff --git a/jmeter/src/test/java/com/baeldung/JmeterIntegrationTest.java b/jmeter/src/test/java/com/baeldung/JmeterIntegrationTest.java new file mode 100644 index 0000000000..a1494416ef --- /dev/null +++ b/jmeter/src/test/java/com/baeldung/JmeterIntegrationTest.java @@ -0,0 +1,35 @@ +package com.baeldung; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +class JmeterIntegrationTest { + + MockMvc mvc; + + public JmeterIntegrationTest(WebApplicationContext wac) { + this.mvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + @Test + void whenCallingUUIDController_thenWeShouldRecieveRandomizedResponse() throws Exception { + MockHttpServletResponse response = mvc.perform(get("/api/uuid")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse(); + + assertThat(response.getContentAsString()) + .contains("Test message..."); + } +} diff --git a/kubernetes/k8s-intro/README.md b/kubernetes/k8s-intro/README.md new file mode 100644 index 0000000000..37ca5f3686 --- /dev/null +++ b/kubernetes/k8s-intro/README.md @@ -0,0 +1,13 @@ +# Kubernetes Java API Sample Code + +This module contains sample code used to show how to use the Kubernetes client Java API. + +Before running those samples, make sure that your environment is correctly configured to access +a working Kubernetes cluster. + +An easy way to check that everything is working as expected is issuing any *kubectl get* command: + +```shell +$ kubectl get nodes +``` +If you get a valid response, then you're good to go. \ No newline at end of file diff --git a/kubernetes/k8s-intro/pom.xml b/kubernetes/k8s-intro/pom.xml new file mode 100644 index 0000000000..4a5bb09711 --- /dev/null +++ b/kubernetes/k8s-intro/pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + + com.baeldung + kubernetes-parent + 1.0.0-SNAPSHOT + + k8s-intro + 0.0.1-SNAPSHOT + + + + io.kubernetes + client-java + 11.0.0 + + + + ch.qos.logback + logback-classic + 1.2.3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + 1.8 + 1.8 + + + + + \ No newline at end of file diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ApiInvoker.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ApiInvoker.java new file mode 100644 index 0000000000..fbc2662170 --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ApiInvoker.java @@ -0,0 +1,11 @@ +package com.baeldung.kubernetes.intro; + +import io.kubernetes.client.openapi.ApiCallback; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import okhttp3.Call; + +@FunctionalInterface +public interface ApiInvoker { + Call apply(CoreV1Api api, ApiCallback callback) throws ApiException; +} diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/AsyncHelper.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/AsyncHelper.java new file mode 100644 index 0000000000..d9c29dcb64 --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/AsyncHelper.java @@ -0,0 +1,74 @@ +package com.baeldung.kubernetes.intro; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.function.BiFunction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.kubernetes.client.openapi.ApiCallback; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import okhttp3.Call; + +public class AsyncHelper implements ApiCallback { + + private static final Logger log = LoggerFactory.getLogger(AsyncHelper.class); + + private CoreV1Api api; + private CompletableFuture callResult; + + private AsyncHelper(CoreV1Api api) { + this.api = api; + } + + public static CompletableFuture doAsync(CoreV1Api api, ApiInvoker invoker) { + + AsyncHelper p = new AsyncHelper<>(api); + return p.execute(invoker); + } + + private CompletableFuture execute( ApiInvoker invoker) { + + try { + callResult = new CompletableFuture<>(); + log.info("[I38] Calling API..."); + final Call call = invoker.apply(api,this); + log.info("[I41] API Succesfully invoked: method={}, url={}", + call.request().method(), + call.request().url()); + return callResult; + } + catch(ApiException aex) { + callResult.completeExceptionally(aex); + return callResult; + } + } + + @Override + public void onFailure(ApiException e, int statusCode, Map> responseHeaders) { + log.error("[E53] onFailure",e); + callResult.completeExceptionally(e); + } + + @Override + public void onSuccess(R result, int statusCode, Map> responseHeaders) { + log.error("[E61] onSuccess: statusCode={}",statusCode); + callResult.complete(result); + } + + @Override + public void onUploadProgress(long bytesWritten, long contentLength, boolean done) { + log.info("[E61] onUploadProgress: bytesWritten={}, contentLength={}, done={}",bytesWritten,contentLength,done); + } + + @Override + public void onDownloadProgress(long bytesRead, long contentLength, boolean done) { + log.info("[E75] onDownloadProgress: bytesRead={}, contentLength={}, done={}",bytesRead,contentLength,done); + } +} + diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodes.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodes.java new file mode 100644 index 0000000000..b0e540eb3a --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodes.java @@ -0,0 +1,31 @@ +/** + * + */ +package com.baeldung.kubernetes.intro; + + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1NodeList; +import io.kubernetes.client.util.Config; + +/** + * @author Philippe + * + */ +public class ListNodes { + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + ApiClient client = Config.defaultClient(); + CoreV1Api api = new CoreV1Api(client); + V1NodeList nodeList = api.listNode(null, null, null, null, null, null, null, null, 10, false); + nodeList.getItems() + .stream() + .forEach((node) -> System.out.println(node.getMetadata())); + } + +} diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodesAsync.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodesAsync.java new file mode 100644 index 0000000000..acd4512196 --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListNodesAsync.java @@ -0,0 +1,50 @@ +/** + * + */ +package com.baeldung.kubernetes.intro; + + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1NodeList; +import io.kubernetes.client.util.Config; + +/** + * @author Philippe + * + */ +public class ListNodesAsync { + + private static Logger log = LoggerFactory.getLogger(ListNodesAsync.class); + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + // Initial setup + ApiClient client = Config.defaultClient(); + CoreV1Api api = new CoreV1Api(client); + + // Start async call + CompletableFuture p = AsyncHelper.doAsync(api,(capi,cb) -> + capi.listNodeAsync(null, null, null, null, null, null, null, null, 10, false, cb) + ); + + p.thenAcceptAsync((nodeList) -> { + log.info("[I40] Processing results..."); + nodeList.getItems() + .stream() + .forEach((node) -> System.out.println(node.getMetadata())); + }); + + log.info("[I46] Waiting results..."); + p.get(10, TimeUnit.SECONDS); + } + +} diff --git a/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListPodsPaged.java b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListPodsPaged.java new file mode 100644 index 0000000000..1c160ae607 --- /dev/null +++ b/kubernetes/k8s-intro/src/main/java/com/baeldung/kubernetes/intro/ListPodsPaged.java @@ -0,0 +1,46 @@ +/** + * + */ +package com.baeldung.kubernetes.intro; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1NodeList; +import io.kubernetes.client.openapi.models.V1PodList; +import io.kubernetes.client.util.Config; + +/** + * @author Philippe + * + */ +public class ListPodsPaged { + + private static final Logger log = LoggerFactory.getLogger(ListPodsPaged.class); + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + ApiClient client = Config.defaultClient(); + CoreV1Api api = new CoreV1Api(client); + String continuationToken = null; + int limit = 2; // Just for illustration purposes. Real world values would range from ~100 to ~1000/page + Long remaining = null; + do { + log.info("=========================================================================="); + log.info("Retrieving data: continuationToken={}, remaining={}", continuationToken,remaining); + V1PodList items = api.listPodForAllNamespaces(null, continuationToken, null, null, limit, null, null, null, 10, false); + continuationToken = items.getMetadata().getContinue(); + remaining = items.getMetadata().getRemainingItemCount(); + items.getItems() + .stream() + .forEach((node) -> System.out.println(node.getMetadata())); + } while( continuationToken != null ); + } + +} diff --git a/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesAsyncLiveTest.java b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesAsyncLiveTest.java new file mode 100644 index 0000000000..af32df8224 --- /dev/null +++ b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesAsyncLiveTest.java @@ -0,0 +1,10 @@ +package com.baeldung.kubernetes.intro; + +import org.junit.jupiter.api.Test; + +class ListNodesAsyncLiveTest { + @Test + void whenListNodes_thenSuccess() throws Exception { + ListNodesAsync.main(new String[] {}); + } +} diff --git a/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesLiveTest.java b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesLiveTest.java new file mode 100644 index 0000000000..84a3efb903 --- /dev/null +++ b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListNodesLiveTest.java @@ -0,0 +1,10 @@ +package com.baeldung.kubernetes.intro; + +import org.junit.jupiter.api.Test; + +class ListNodesLiveTest { + @Test + void whenListNodes_thenSuccess() throws Exception { + ListNodes.main(new String[] {}); + } +} diff --git a/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListPodsPagedLiveTest.java b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListPodsPagedLiveTest.java new file mode 100644 index 0000000000..6fb3c5b32d --- /dev/null +++ b/kubernetes/k8s-intro/src/test/java/com/baeldung/kubernetes/intro/ListPodsPagedLiveTest.java @@ -0,0 +1,10 @@ +package com.baeldung.kubernetes.intro; + +import org.junit.jupiter.api.Test; + +class ListPodsPagedLiveTest { + @Test + void whenListPodsPage_thenSuccess() throws Exception { + ListPodsPaged.main(new String[] {}); + } +} diff --git a/kubernetes/k8s-intro/src/test/resources/logback.xml b/kubernetes/k8s-intro/src/test/resources/logback.xml new file mode 100644 index 0000000000..479545e643 --- /dev/null +++ b/kubernetes/k8s-intro/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/kubernetes/pom.xml b/kubernetes/pom.xml new file mode 100644 index 0000000000..fe10295e44 --- /dev/null +++ b/kubernetes/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + kubernetes-parent + pom + + k8s-intro + + \ No newline at end of file diff --git a/libraries-http-2/README.md b/libraries-http-2/README.md index c0d6e76f1b..f3fdf1becb 100644 --- a/libraries-http-2/README.md +++ b/libraries-http-2/README.md @@ -7,5 +7,6 @@ This module contains articles about HTTP libraries. - [Jetty ReactiveStreams HTTP Client](https://www.baeldung.com/jetty-reactivestreams-http-client) - [Decode an OkHttp JSON Response](https://www.baeldung.com/okhttp-json-response) - [Retrofit 2 – Dynamic URL](https://www.baeldung.com/retrofit-dynamic-url) +- [Adding Interceptors in OkHTTP](https://www.baeldung.com/java-okhttp-interceptors) - More articles [[<-- prev]](/libraries-http) diff --git a/libraries-http-2/pom.xml b/libraries-http-2/pom.xml index d0bdb26bd4..855008521f 100644 --- a/libraries-http-2/pom.xml +++ b/libraries-http-2/pom.xml @@ -1,7 +1,7 @@ + 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"> 4.0.0 libraries-http-2 libraries-http-2 @@ -13,12 +13,17 @@ - + com.squareup.okhttp3 okhttp ${okhttp.version} + + com.squareup.okhttp3 + logging-interceptor + ${okhttp.version} + com.fasterxml.jackson.core jackson-databind @@ -81,9 +86,9 @@ - 3.14.2 + 4.9.1 2.8.5 - 3.14.2 + 4.9.1 1.0.3 9.4.19.v20190610 2.2.11 diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java new file mode 100644 index 0000000000..e9f8db19be --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java @@ -0,0 +1,18 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Response; + +public class CacheControlResponeInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + return response.newBuilder() + .header("Cache-Control", "no-store") + .build(); + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java new file mode 100644 index 0000000000..73411d947f --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java @@ -0,0 +1,21 @@ +package com.baeldung.okhttp.interceptors; + +public class ErrorMessage { + + private final int status; + private final String detail; + + public ErrorMessage(int status, String detail) { + this.status = status; + this.detail = detail; + } + + public int getStatus() { + return status; + } + + public String getDetail() { + return detail; + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java new file mode 100644 index 0000000000..f6c6673705 --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java @@ -0,0 +1,32 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import com.google.gson.Gson; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class ErrorResponseInterceptor implements Interceptor { + + public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8"); + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if(!response.isSuccessful()) { + Gson gson = new Gson(); + String body = gson.toJson(new ErrorMessage(response.code(), "The response from the server was not OK")); + ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON); + + return response.newBuilder() + .body(responseBody) + .build(); + } + return response; + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java new file mode 100644 index 0000000000..6d08546eea --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java @@ -0,0 +1,25 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class SimpleLoggingInterceptor implements Interceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class); + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url()); + + return chain.proceed(request); + } + +} diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java new file mode 100644 index 0000000000..d15b67ad3b --- /dev/null +++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java @@ -0,0 +1,90 @@ +package com.baeldung.okhttp.interceptors; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Rule; +import org.junit.Test; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.logging.HttpLoggingInterceptor.Level; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +public class InterceptorIntegrationTest { + + @Rule + public MockWebServer server = new MockWebServer(); + + @Test + public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException { + server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(new SimpleLoggingInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 200, response.code()); + assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string()); + } + } + + @Test + public void givenResponseInterceptor_whenRequestSent_thenCacheControlSetToNoStore() throws IOException { + server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(getHttpLogger()) + .addInterceptor(new CacheControlResponeInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 200, response.code()); + assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string()); + assertEquals("Response cache-control should be", "no-store", response.header("Cache-Control")); + } + } + + @Test + public void givenErrorResponseInterceptor_whenResponseIs500_thenBodyIsJsonWithStatus() throws IOException { + server.enqueue(new MockResponse().setResponseCode(500).setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(getHttpLogger()) + .addInterceptor(new ErrorResponseInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 500, response.code()); + assertEquals("Body should be: ", "{\"status\":500,\"detail\":\"The response from the server was not OK\"}", + response.body().string()); + } + } + + private HttpLoggingInterceptor getHttpLogger() { + HttpLoggingInterceptor logger = new HttpLoggingInterceptor(); + logger.setLevel(Level.HEADERS); + return logger; + } + +} diff --git a/maven-modules/maven-plugins/jaxws/README.md b/maven-modules/maven-plugins/jaxws/README.md new file mode 100644 index 0000000000..d17df7a5f6 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Generate WSDL Stubs with Maven](https://www.baeldung.com/maven-wsdl-stubs) diff --git a/maven-modules/maven-plugins/jaxws/pom.xml b/maven-modules/maven-plugins/jaxws/pom.xml new file mode 100644 index 0000000000..161c1dc731 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/pom.xml @@ -0,0 +1,45 @@ + + + + maven-plugins + com.baeldung + 0.0.1-SNAPSHOT + + 4.0.0 + + jaxws + + + + org.codehaus.mojo + jaxws-maven-plugin + 2.6 + + + + wsimport + + + + + ${project.basedir}/src/main/resources/ + com.baeldung.soap.ws.client + + ${project.build.directory}/generated-sources/ + + + + + maven-verifier-plugin + ${maven.verifier.version} + + ../input-resources/verifications.xml + false + + + + + + \ No newline at end of file diff --git a/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryNotFoundException.java b/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryNotFoundException.java new file mode 100644 index 0000000000..c68d65abb1 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryNotFoundException.java @@ -0,0 +1,8 @@ +package com.baeldung.soap.ws.client; + +public class CountryNotFoundException extends RuntimeException { + + public CountryNotFoundException() { + super("Country not found!"); + } +} diff --git a/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryServiceClient.java b/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryServiceClient.java new file mode 100644 index 0000000000..d6175890f9 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/src/main/java/com/baeldung/soap/ws/client/CountryServiceClient.java @@ -0,0 +1,27 @@ +package com.baeldung.soap.ws.client; + +import java.util.Optional; + +public class CountryServiceClient { + + private CountryService countryService; + + public CountryServiceClient(CountryService countryService) { + this.countryService = countryService; + } + + public String getCapitalByCountryName(String countryName) { + return Optional.of(countryService.findByName(countryName)) + .map(Country::getCapital).orElseThrow(CountryNotFoundException::new); + } + + public int getPopulationByCountryName(String countryName) { + return Optional.of(countryService.findByName(countryName)) + .map(Country::getPopulation).orElseThrow(CountryNotFoundException::new); + } + + public Currency getCurrencyByCountryName(String countryName) { + return Optional.of(countryService.findByName(countryName)) + .map(Country::getCurrency).orElseThrow(CountryNotFoundException::new); + } +} diff --git a/maven-modules/maven-plugins/jaxws/src/main/resources/country.wsdl b/maven-modules/maven-plugins/jaxws/src/main/resources/country.wsdl new file mode 100644 index 0000000000..b6efb7087f --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/src/main/resources/country.wsdl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/maven-modules/maven-plugins/jaxws/src/main/resources/country.xsd b/maven-modules/maven-plugins/jaxws/src/main/resources/country.xsd new file mode 100644 index 0000000000..c94b6047f9 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/src/main/resources/country.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/maven-modules/maven-plugins/jaxws/src/test/java/com/baeldung/soap/ws/client/CountryServiceClientUnitTest.java b/maven-modules/maven-plugins/jaxws/src/test/java/com/baeldung/soap/ws/client/CountryServiceClientUnitTest.java new file mode 100644 index 0000000000..65c4f39ee4 --- /dev/null +++ b/maven-modules/maven-plugins/jaxws/src/test/java/com/baeldung/soap/ws/client/CountryServiceClientUnitTest.java @@ -0,0 +1,73 @@ +package com.baeldung.soap.ws.client; + +import org.junit.jupiter.api.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class CountryServiceClientUnitTest { + + CountryServiceClient countryServiceClient; + CountryService countryService; + + @BeforeEach + void setUp() { + countryService = mock(CountryService.class); + countryServiceClient = new CountryServiceClient(countryService); + } + + @DisplayName("Get capital by country name when country not found") + @Test + void givenCountryDoesNotExist_whenGetCapitalByCountryName_thenThrowsCountryNotFoundException() { + doThrow(CountryNotFoundException.class).when(countryService).findByName(any()); + Assertions.assertThrows(CountryNotFoundException.class, () -> countryServiceClient.getCapitalByCountryName(any())); + } + + @DisplayName("Get capital by country name when country is India then should return capital") + @Test + void givenCountryIndia_whenGetCapitalByCountryName_thenShouldReturnCapital() { + Country country = mock(Country.class); + + doReturn("New Delhi").when(country).getCapital(); + doReturn(country).when(countryService).findByName("India"); + + Assertions.assertEquals("New Delhi", countryServiceClient.getCapitalByCountryName("India")); + } + + @DisplayName("Get population by country name when country not found") + @Test + void givenCountryDoesNotExist_getPopulationByCountryName_thenThrowsCountryNotFoundException() { + doThrow(CountryNotFoundException.class).when(countryService).findByName(any()); + Assertions.assertThrows(CountryNotFoundException.class, () -> countryServiceClient.getPopulationByCountryName(any())); + } + + @DisplayName("Get population by country name when country is India then should return population") + @Test + void givenCountryIndia_getPopulationByCountryName_thenShouldReturnPopulation() { + Country country = mock(Country.class); + + doReturn(1000000).when(country).getPopulation(); + doReturn(country).when(countryService).findByName("India"); + + Assertions.assertEquals(1000000, countryServiceClient.getPopulationByCountryName("India")); + } + + @DisplayName("Get currency by country name when country not found") + @Test + void givenCountryDoesNotExist_getCurrencyByCountryName_thenThrowsCountryNotFoundException() { + doThrow(CountryNotFoundException.class).when(countryService).findByName(any()); + Assertions.assertThrows(CountryNotFoundException.class, () -> countryServiceClient.getCurrencyByCountryName(any())); + } + + @DisplayName("Get currency by country name when country is India then should return currency") + @Test + void givenCountryIndia_getCurrencyByCountryName_thenShouldReturnCurrency() { + Country country = mock(Country.class); + + doReturn(Currency.INR).when(country).getCurrency(); + doReturn(country).when(countryService).findByName("India"); + + Assertions.assertEquals(Currency.INR, countryServiceClient.getCurrencyByCountryName("India")); + } + +} \ No newline at end of file diff --git a/maven-modules/maven-plugins/pom.xml b/maven-modules/maven-plugins/pom.xml index 9f28871ec0..20bdb4b45a 100644 --- a/maven-modules/maven-plugins/pom.xml +++ b/maven-modules/maven-plugins/pom.xml @@ -17,6 +17,7 @@ custom-rule maven-enforcer + jaxws diff --git a/maven-modules/pom.xml b/maven-modules/pom.xml index 86a7d5756c..c9a2b67a6c 100644 --- a/maven-modules/pom.xml +++ b/maven-modules/pom.xml @@ -27,6 +27,7 @@ versions-maven-plugin version-collision optional-dependencies + version-overriding-plugins diff --git a/maven-modules/version-overriding-plugins/README.md b/maven-modules/version-overriding-plugins/README.md new file mode 100644 index 0000000000..1542692ca0 --- /dev/null +++ b/maven-modules/version-overriding-plugins/README.md @@ -0,0 +1,5 @@ +Use `` mvn help:effective-pom`` to see the final generated pom. + +### Relevant Articles: + +- [Override Maven Plugin Configuration from Parent](https://www.baeldung.com/maven-plugin-override-parent) diff --git a/maven-modules/version-overriding-plugins/child-a/pom.xml b/maven-modules/version-overriding-plugins/child-a/pom.xml new file mode 100644 index 0000000000..780e1c4125 --- /dev/null +++ b/maven-modules/version-overriding-plugins/child-a/pom.xml @@ -0,0 +1,36 @@ + + + + version-overriding-plugins + com.baeldung + 0.0.1-SNAPSHOT + + 4.0.0 + pom + + child-a + + + + maven-resources-plugin + + + + child-a-resources + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + \ No newline at end of file diff --git a/maven-modules/version-overriding-plugins/child-b/pom.xml b/maven-modules/version-overriding-plugins/child-b/pom.xml new file mode 100644 index 0000000000..05f127bc5c --- /dev/null +++ b/maven-modules/version-overriding-plugins/child-b/pom.xml @@ -0,0 +1,15 @@ + + + + version-overriding-plugins + com.baeldung + 0.0.1-SNAPSHOT + + 4.0.0 + + child-b + + + \ No newline at end of file diff --git a/maven-modules/version-overriding-plugins/pom.xml b/maven-modules/version-overriding-plugins/pom.xml new file mode 100644 index 0000000000..8d703ab568 --- /dev/null +++ b/maven-modules/version-overriding-plugins/pom.xml @@ -0,0 +1,55 @@ + + + + maven-modules + com.baeldung + 0.0.1-SNAPSHOT + + + 3.8.0 + + 4.0.0 + + version-overriding-plugins + pom + + + child-a + child-b + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin} + + + + + + maven-resources-plugin + + + + parent-resources + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + 512m + + + + + \ No newline at end of file diff --git a/parent-boot-2/pom.xml b/parent-boot-2/pom.xml index ace3e538c9..f5bf8784a9 100644 --- a/parent-boot-2/pom.xml +++ b/parent-boot-2/pom.xml @@ -82,7 +82,7 @@ 3.3.0 1.0.22.RELEASE - 2.4.0 + 2.4.3 1.9.1 3.4.0 diff --git a/parent-spring-5/pom.xml b/parent-spring-5/pom.xml index 5893701c68..3219c504d5 100644 --- a/parent-spring-5/pom.xml +++ b/parent-spring-5/pom.xml @@ -31,7 +31,7 @@ - 5.2.8.RELEASE + 5.3.3 5.2.3.RELEASE 1.5.10.RELEASE diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java index 7f50aa87f1..8fff82de32 100644 --- a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java +++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/Application.java @@ -50,7 +50,7 @@ public class Application implements CommandLineRunner { LOGGER.info("@@ findByFirstName() call..."); repository.findByFirstName("Franz") .forEach(person -> LOGGER.info(person.toString())); - LOGGER.info("@@ findByFirstName() call..."); + LOGGER.info("@@ updateByFirstName() call..."); repository.updateByFirstName(2L, "Date Inferno"); repository.findAll() .forEach(person -> LOGGER.info(person.toString())); diff --git a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java index 2f2329caec..b2f026fa0c 100644 --- a/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java +++ b/persistence-modules/spring-data-jdbc/src/main/java/com/baeldung/springdatajdbcintro/repository/PersonRepository.java @@ -1,23 +1,20 @@ package com.baeldung.springdatajdbcintro.repository; -import java.util.List; - +import com.baeldung.springdatajdbcintro.entity.Person; import org.springframework.data.jdbc.repository.query.Modifying; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.baeldung.springdatajdbcintro.entity.Person; +import java.util.List; @Repository -public interface PersonRepository extends CrudRepository { +public interface PersonRepository extends CrudRepository { - @Query("select * from person where first_name=:firstName") - List findByFirstName(@Param("firstName") String firstName); + List findByFirstName(String firstName); @Modifying @Query("UPDATE person SET first_name = :name WHERE id = :id") boolean updateByFirstName(@Param("id") Long id, @Param("name") String name); - } diff --git a/persistence-modules/spring-data-jpa-annotations/README.md b/persistence-modules/spring-data-jpa-annotations/README.md index 1ee579cf6c..3892e75733 100644 --- a/persistence-modules/spring-data-jpa-annotations/README.md +++ b/persistence-modules/spring-data-jpa-annotations/README.md @@ -6,7 +6,6 @@ This module contains articles about annotations used in Spring Data JPA - [DDD Aggregates and @DomainEvents](https://www.baeldung.com/spring-data-ddd) - [JPA @Embedded And @Embeddable](https://www.baeldung.com/jpa-embedded-embeddable) -- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation) - [Spring JPA @Embedded and @EmbeddedId](https://www.baeldung.com/spring-jpa-embedded-method-parameters) - [Programmatic Transaction Management in Spring](https://www.baeldung.com/spring-programmatic-transaction-management) - [JPA Entity Lifecycle Events](https://www.baeldung.com/jpa-entity-lifecycle-events) diff --git a/persistence-modules/spring-data-jpa-enterprise/README.md b/persistence-modules/spring-data-jpa-enterprise/README.md index 81398b1f00..42fbecc880 100644 --- a/persistence-modules/spring-data-jpa-enterprise/README.md +++ b/persistence-modules/spring-data-jpa-enterprise/README.md @@ -12,6 +12,7 @@ This module contains articles about Spring Data JPA used in enterprise applicati - [Working with Lazy Element Collections in JPA](https://www.baeldung.com/java-jpa-lazy-collections) - [Custom Naming Convention with Spring Data JPA](https://www.baeldung.com/spring-data-jpa-custom-naming) - [Partial Data Update with Spring Data](https://www.baeldung.com/spring-data-partial-update) +- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation) ### Eclipse Config After importing the project into Eclipse, you may see the following error: diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java index 53f692ff28..cfa99414c6 100644 --- a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java +++ b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java @@ -93,6 +93,9 @@ public interface UserRepository extends JpaRepository , UserRepos @Query("delete User u where u.active = false") int deleteDeactivatedUsers(); + @Query("delete User u where u.active = false") + int deleteDeactivatedUsersWithNoModifyingAnnotation(); + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "alter table USERS add column deleted int(1) not null default 0", nativeQuery = true) void addDeletedColumn(); diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java index b2581b8034..e8841d921c 100644 --- a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java +++ b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java @@ -3,6 +3,7 @@ package com.baeldung.boot.daos; import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -21,6 +22,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.*; public class UserRepositoryCommon { @@ -520,6 +522,22 @@ public class UserRepositoryCommon { assertEquals(1, deletedUsersCount); } + @Test + @Transactional + public void givenTwoUsers_whenDeleteDeactivatedUsersWithNoModifyingAnnotation_ThenException() { + User usr01 = new User("usr01", LocalDate.of(2018, 1, 1), "usr01@baeldung.com", 1); + usr01.setLastLoginDate(LocalDate.now()); + User usr02 = new User("usr02", LocalDate.of(2018, 6, 1), "usr02@baeldung.com", 0); + usr02.setLastLoginDate(LocalDate.of(2018, 7, 20)); + usr02.setActive(false); + + userRepository.save(usr01); + userRepository.save(usr02); + + assertThatThrownBy(() -> userRepository.deleteDeactivatedUsersWithNoModifyingAnnotation()) + .isInstanceOf(InvalidDataAccessApiUsageException.class); + } + @Test @Transactional public void givenTwoUsers_whenAddDeletedColumn_ThenUsersHaveDeletedColumn() { diff --git a/persistence-modules/spring-data-mongodb/src/main/java/com/baeldung/config/MongoConfig.java b/persistence-modules/spring-data-mongodb/src/main/java/com/baeldung/config/MongoConfig.java index 90b1268133..a7db26eba5 100644 --- a/persistence-modules/spring-data-mongodb/src/main/java/com/baeldung/config/MongoConfig.java +++ b/persistence-modules/spring-data-mongodb/src/main/java/com/baeldung/config/MongoConfig.java @@ -68,4 +68,8 @@ public class MongoConfig extends AbstractMongoClientConfiguration { return new MongoTransactionManager(dbFactory); } + @Override + protected boolean autoIndexCreation() { + return true; + } } diff --git a/pom.xml b/pom.xml index 70efb02f4d..f202430ccf 100644 --- a/pom.xml +++ b/pom.xml @@ -471,7 +471,7 @@ json-path jsoup jta - + kubernetes language-interop libraries-2 @@ -531,7 +531,7 @@ protobuffer quarkus - quarkus-extension + rabbitmq @@ -631,6 +631,7 @@ spring-core-2 spring-core-3 spring-core-4 + spring-core-5 spring-cucumber spring-data-rest @@ -991,7 +992,7 @@ protobuffer quarkus - quarkus-extension + rabbitmq @@ -1082,6 +1083,7 @@ spring-core-2 spring-core-3 spring-core-4 + spring-core-5 spring-cucumber spring-data-rest @@ -1348,8 +1350,8 @@ true false false - false - false + false + true 4.12 2.2 @@ -1368,7 +1370,7 @@ 1.6.0 1.8 1.2.17 - 2.1.0.1 + 2.2.2.0 1.19 1.19 1.6.0 @@ -1389,7 +1391,7 @@ 0.3.1 2.5.2 0.0.1 - 3.8 + 3.12.2 3.0.0 3.13.0 diff --git a/spring-5-reactive-client/README.md b/spring-5-reactive-client/README.md index b247a1669b..154a3cab0b 100644 --- a/spring-5-reactive-client/README.md +++ b/spring-5-reactive-client/README.md @@ -8,7 +8,6 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring ### Relevant Articles - [Logging Spring WebClient Calls](https://www.baeldung.com/spring-log-webclient-calls) - [Simultaneous Spring WebClient Calls](https://www.baeldung.com/spring-webclient-simultaneous-calls) -- [Logging Spring WebClient Calls](https://www.baeldung.com/spring-log-webclient-calls) - [Mocking a WebClient in Spring](https://www.baeldung.com/spring-mocking-webclient) - [Spring WebClient Filters](https://www.baeldung.com/spring-webclient-filters) - [Get List of JSON Objects with WebClient](https://www.baeldung.com/spring-webclient-json-list) diff --git a/spring-5-reactive-client/pom.xml b/spring-5-reactive-client/pom.xml index 5b773cc63f..66861a3bb6 100644 --- a/spring-5-reactive-client/pom.xml +++ b/spring-5-reactive-client/pom.xml @@ -174,9 +174,8 @@ 1.0 1.0 4.1 - 1.0.3 + 1.1.6 4.0.1 - 2.3.3.RELEASE diff --git a/spring-5-reactive-client/src/main/resources/application.properties b/spring-5-reactive-client/src/main/resources/application.properties index 2d93456aeb..05033054b1 100644 --- a/spring-5-reactive-client/src/main/resources/application.properties +++ b/spring-5-reactive-client/src/main/resources/application.properties @@ -1,3 +1,5 @@ logging.level.root=INFO -server.port=8081 \ No newline at end of file +server.port=8081 + +logging.level.reactor.netty.http.client.HttpClient=DEBUG \ No newline at end of file diff --git a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/WebClientLoggingIntegrationTest.java b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/WebClientLoggingIntegrationTest.java index 95c63f267f..bb4e682481 100644 --- a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/WebClientLoggingIntegrationTest.java +++ b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/WebClientLoggingIntegrationTest.java @@ -1,13 +1,13 @@ package com.baeldung.reactive.logging; -import ch.qos.logback.classic.spi.LoggingEvent; -import ch.qos.logback.core.Appender; -import com.baeldung.reactive.logging.filters.LogFilters; -import com.baeldung.reactive.logging.netty.CustomLogger; -import com.fasterxml.jackson.databind.ObjectMapper; +import static com.baeldung.reactive.logging.jetty.RequestLogEnhancer.enhance; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import java.net.URI; -import lombok.AllArgsConstructor; -import lombok.Data; + import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.BeforeEach; @@ -17,14 +17,17 @@ import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; -import reactor.netty.channel.BootstrapHandlers; -import reactor.netty.http.client.HttpClient; -import static com.baeldung.reactive.logging.jetty.RequestLogEnhancer.enhance; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.baeldung.reactive.logging.filters.LogFilters; +import com.fasterxml.jackson.databind.ObjectMapper; + +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import io.netty.handler.logging.LogLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.logging.AdvancedByteBufFormat; public class WebClientLoggingIntegrationTest { @@ -114,21 +117,17 @@ public class WebClientLoggingIntegrationTest { @Test public void givenNettyHttpClientWithCustomLogger_whenEndpointIsConsumed_thenRequestAndResponseBodyLogged() { + reactor.netty.http.client.HttpClient httpClient = HttpClient.create() + .wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL); - reactor.netty.http.client.HttpClient httpClient = HttpClient - .create() - .tcpConfiguration( - tc -> tc.bootstrap( - b -> BootstrapHandlers.updateLogSupport(b, new CustomLogger(HttpClient.class)))); - WebClient - .builder() - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .build() - .post() - .uri(sampleUrl) - .body(BodyInserters.fromObject(post)) - .exchange() - .block(); + WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build() + .post() + .uri(sampleUrl) + .body(BodyInserters.fromObject(post)) + .exchange() + .block(); verify(nettyAppender).doAppend(argThat(argument -> (((LoggingEvent) argument).getFormattedMessage()).contains(sampleResponseBody))); } diff --git a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/netty/CustomLogger.java b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/netty/CustomLogger.java deleted file mode 100644 index 9f2a4d127f..0000000000 --- a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/logging/netty/CustomLogger.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.baeldung.reactive.logging.netty; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.logging.LoggingHandler; -import java.nio.charset.Charset; - -import static io.netty.util.internal.PlatformDependent.allocateUninitializedArray; -import static java.lang.Math.max; -import static java.nio.charset.Charset.defaultCharset; - -public class CustomLogger extends LoggingHandler { - public CustomLogger(Class clazz) { - super(clazz); - } - - @Override - protected String format(ChannelHandlerContext ctx, String event, Object arg) { - if (arg instanceof ByteBuf) { - ByteBuf msg = (ByteBuf) arg; - return decode(msg, msg.readerIndex(), msg.readableBytes(), defaultCharset()); - } - return super.format(ctx, event, arg); - } - - private String decode(ByteBuf src, int readerIndex, int len, Charset charset) { - if (len != 0) { - byte[] array; - int offset; - if (src.hasArray()) { - array = src.array(); - offset = src.arrayOffset() + readerIndex; - } else { - array = allocateUninitializedArray(max(len, 1024)); - offset = 0; - src.getBytes(readerIndex, array, 0, len); - } - return new String(array, offset, len, charset); - } - return ""; - } -} diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 39adf0b5c0..7e1fc86847 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -11,8 +11,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; @@ -41,6 +39,7 @@ import com.baeldung.web.reactive.client.Foo; import com.baeldung.web.reactive.client.WebClientApplication; import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Mono; @@ -265,17 +264,18 @@ public class WebClientIntegrationTest { .addHandlerLast(new WriteTimeoutHandler(1000, TimeUnit.MILLISECONDS))); WebClient timeoutClient = WebClient.builder() + .baseUrl("http://localhost:" + port) .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); - BodyInserter, ReactiveHttpOutputMessage> inserterCompleteSuscriber = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); - RequestHeadersSpec headersSpecInserterCompleteSuscriber = timeoutClient.post() + RequestHeadersSpec neverendingMonoBodyRequest = timeoutClient.post() .uri("/resource") - .body(inserterCompleteSuscriber); + .body(Mono.never(), String.class); - StepVerifier.create(headersSpecInserterCompleteSuscriber.retrieve() + StepVerifier.create(neverendingMonoBodyRequest.retrieve() .bodyToMono(String.class)) - .expectTimeout(Duration.ofMillis(2000)) + .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ReadTimeoutException.class.isAssignableFrom(ex.getCause() + .getClass())) .verify(); } diff --git a/spring-5/README.md b/spring-5/README.md index cce18bedf8..2ddd9fa94f 100644 --- a/spring-5/README.md +++ b/spring-5/README.md @@ -11,3 +11,5 @@ This module contains articles about Spring 5 - [Spring Assert Statements](https://www.baeldung.com/spring-assert) - [Difference between \ vs \](https://www.baeldung.com/spring-contextannotation-contextcomponentscan) - [Finding the Spring Version](https://www.baeldung.com/spring-find-version) +- [Spring 5 Testing with @EnabledIf Annotation](https://www.baeldung.com/spring-5-enabledIf) +- [Configuring a Hikari Connection Pool with Spring Boot](https://www.baeldung.com/spring-boot-hikari) diff --git a/spring-5/src/test/java/com/baeldung/README.md b/spring-5/src/test/java/com/baeldung/README.md new file mode 100644 index 0000000000..0ff61914d5 --- /dev/null +++ b/spring-5/src/test/java/com/baeldung/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Concurrent Test Execution in Spring 5](https://www.baeldung.com/spring-5-concurrent-tests) diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml index 263d2af089..4902368d7e 100644 --- a/spring-boot-modules/pom.xml +++ b/spring-boot-modules/pom.xml @@ -54,6 +54,7 @@ spring-boot-mvc-2 spring-boot-mvc-3 spring-boot-mvc-birt + spring-boot-mvc-jersey spring-boot-nashorn spring-boot-parent spring-boot-performance diff --git a/spring-boot-modules/spring-boot-admin/spring-boot-admin-client/pom.xml b/spring-boot-modules/spring-boot-admin/spring-boot-admin-client/pom.xml index eb40bfe8ea..f7a4b157e7 100644 --- a/spring-boot-modules/spring-boot-admin/spring-boot-admin-client/pom.xml +++ b/spring-boot-modules/spring-boot-admin/spring-boot-admin-client/pom.xml @@ -61,7 +61,7 @@ - 2.2.2 + 2.4.0 2.0.4.RELEASE diff --git a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/pom.xml b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/pom.xml index 558aed8b26..bcec12a14c 100644 --- a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/pom.xml +++ b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/pom.xml @@ -82,10 +82,9 @@ - 2.2.2 - 2.2.2 + 2.4.0 + 2.4.0 1.5.7 2.0.4.RELEASE - 2.3.3.RELEASE diff --git a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java index e934086cf4..5a713c7e8f 100644 --- a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java +++ b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/SpringBootAdminServerApplication.java @@ -1,11 +1,12 @@ package com.baeldung.springbootadminserver; +import de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableAdminServer -@SpringBootApplication +@SpringBootApplication(exclude = AdminServerHazelcastAutoConfiguration.class) public class SpringBootAdminServerApplication { public static void main(String[] args) { diff --git a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java index d38b0e933c..d53707c19a 100644 --- a/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java +++ b/spring-boot-modules/spring-boot-admin/spring-boot-admin-server/src/main/java/com/baeldung/springbootadminserver/configs/HazelcastConfig.java @@ -1,12 +1,13 @@ package com.baeldung.springbootadminserver.configs; import com.hazelcast.config.Config; +import com.hazelcast.config.EvictionConfig; import com.hazelcast.config.EvictionPolicy; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.config.MapConfig; import com.hazelcast.config.MergePolicyConfig; import com.hazelcast.config.TcpIpConfig; -import com.hazelcast.map.merge.PutIfAbsentMapMergePolicy; +import com.hazelcast.spi.merge.PutIfAbsentMergePolicy; import java.util.Collections; @@ -16,32 +17,27 @@ import org.springframework.context.annotation.Configuration; @Configuration public class HazelcastConfig { - @Bean - public Config hazelcast() { - MapConfig eventStoreMap = new MapConfig("spring-boot-admin-event-store").setInMemoryFormat(InMemoryFormat.OBJECT) - .setBackupCount(1) - .setEvictionPolicy(EvictionPolicy.NONE) - .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMapMergePolicy.class.getName(), 100)); + @Bean + public Config hazelcast() { + MapConfig eventStoreMap = new MapConfig("spring-boot-admin-event-store") + .setInMemoryFormat(InMemoryFormat.OBJECT).setBackupCount(1) + .setEvictionConfig(new EvictionConfig().setEvictionPolicy(EvictionPolicy.NONE)) + .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100)); - MapConfig sentNotificationsMap = new MapConfig("spring-boot-admin-application-store").setInMemoryFormat(InMemoryFormat.OBJECT) - .setBackupCount(1) - .setEvictionPolicy(EvictionPolicy.LRU) - .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMapMergePolicy.class.getName(), 100)); + MapConfig sentNotificationsMap = new MapConfig("spring-boot-admin-application-store") + .setInMemoryFormat(InMemoryFormat.OBJECT).setBackupCount(1) + .setEvictionConfig(new EvictionConfig().setEvictionPolicy(EvictionPolicy.LRU)) + .setMergePolicyConfig(new MergePolicyConfig(PutIfAbsentMergePolicy.class.getName(), 100)); - Config config = new Config(); - config.addMapConfig(eventStoreMap); - config.addMapConfig(sentNotificationsMap); - config.setProperty("hazelcast.jmx", "true"); + Config config = new Config(); + config.addMapConfig(eventStoreMap); + config.addMapConfig(sentNotificationsMap); + config.setProperty("hazelcast.jmx", "true"); - config.getNetworkConfig() - .getJoin() - .getMulticastConfig() - .setEnabled(false); - TcpIpConfig tcpIpConfig = config.getNetworkConfig() - .getJoin() - .getTcpIpConfig(); - tcpIpConfig.setEnabled(true); - tcpIpConfig.setMembers(Collections.singletonList("127.0.0.1")); - return config; - } + config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false); + TcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig(); + tcpIpConfig.setEnabled(true); + tcpIpConfig.setMembers(Collections.singletonList("127.0.0.1")); + return config; + } } diff --git a/spring-boot-modules/spring-boot-data-2/README.md b/spring-boot-modules/spring-boot-data-2/README.md index c21ce02a2e..29d9dafe66 100644 --- a/spring-boot-modules/spring-boot-data-2/README.md +++ b/spring-boot-modules/spring-boot-data-2/README.md @@ -1,4 +1,4 @@ ### Relevant Articles: - [Spring Boot: Customize the Jackson ObjectMapper](https://www.baeldung.com/spring-boot-customize-jackson-objectmapper) -- [Configuring a Hikari Connection Pool with Spring Boot](https://www.baeldung.com/spring-boot-hikari) +- [“HttpMessageNotWritableException: No converter found for return value of type”](https://www.baeldung.com/spring-no-converter-found) diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/NoConverterFoundApplication.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/NoConverterFoundApplication.java new file mode 100644 index 0000000000..7abfa29bf2 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/NoConverterFoundApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.noconverterfound; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class NoConverterFoundApplication { + + public static void main(String[] args) { + SpringApplication.run(NoConverterFoundApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java new file mode 100644 index 0000000000..21cb98710d --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/controller/StudentRestController.java @@ -0,0 +1,21 @@ +package com.baeldung.boot.noconverterfound.controller; + +import org.springframework.http.ResponseEntity; +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 com.baeldung.boot.noconverterfound.model.Student; + +@RestController +@RequestMapping(value = "/api") +public class StudentRestController { + + @GetMapping("/student/{id}") + public ResponseEntity get(@PathVariable("id") int id) { + // Custom logic + return ResponseEntity.ok(new Student(id, "John", "Wiliams", "AA")); + } + +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/model/Student.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/model/Student.java new file mode 100644 index 0000000000..94ece02f8f --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/noconverterfound/model/Student.java @@ -0,0 +1,53 @@ +package com.baeldung.boot.noconverterfound.model; + +public class Student { + + private int id; + private String firstName; + private String lastName; + private String grade; + + public Student() { + + } + + public Student(int id, String firstName, String lastName, String grade) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.grade = grade; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getGrade() { + return grade; + } + + public void setGrade(String grade) { + this.grade = grade; + } + +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/Book.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/Book.java new file mode 100644 index 0000000000..ff0a8a1bb3 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/Book.java @@ -0,0 +1,39 @@ +package com.baeldung.boot.readonlyrepository; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Book +{ + @Id + @GeneratedValue + private Long id; + private String author; + private String title; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/BookReadOnlyRepository.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/BookReadOnlyRepository.java new file mode 100644 index 0000000000..6327ef5a56 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/BookReadOnlyRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.boot.readonlyrepository; + +import java.util.List; + +public interface BookReadOnlyRepository extends ReadOnlyRepository { + + List findByAuthor(String author); + + List findByTitle(String title); +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepository.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepository.java new file mode 100644 index 0000000000..16f3fd429e --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepository.java @@ -0,0 +1,15 @@ +package com.baeldung.boot.readonlyrepository; + +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.Optional; + +@NoRepositoryBean +public interface ReadOnlyRepository extends Repository { + + Optional findById(ID id); + + List findAll(); +} diff --git a/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryApplication.java b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryApplication.java new file mode 100644 index 0000000000..f5a67197ad --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/main/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.readonlyrepository; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ReadOnlyRepositoryApplication { + + public static void main(String[] args) { + SpringApplication.run(ReadOnlyRepositoryApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java new file mode 100644 index 0000000000..f8ded91e65 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/noconverterfound/NoConverterFoundIntegrationTest.java @@ -0,0 +1,52 @@ +package com.baeldung.boot.noconverterfound; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.baeldung.boot.noconverterfound.controller.StudentRestController; + +@RunWith(SpringRunner.class) +@WebMvcTest(StudentRestController.class) +public class NoConverterFoundIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + /* Remove Getters from Student class to successfully run this test case + * @Test + public void whenGettersNotDefined_thenThrowException() throws Exception { + + String url = "/api/student/1"; + + this.mockMvc.perform(get(url)) + .andExpect(status().isInternalServerError()) + .andExpect(result -> assertThat(result.getResolvedException()) + .isInstanceOf(HttpMessageNotWritableException.class)) + .andExpect(result -> assertThat(result.getResolvedException().getMessage()) + .contains("No converter found for return value of type")); + + } + */ + + @Test + public void whenGettersAreDefined_thenReturnObject() throws Exception { + + String url = "/api/student/2"; + + this.mockMvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("John")); + + } + +} diff --git a/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/BookRepository.java b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/BookRepository.java new file mode 100644 index 0000000000..363b310c8c --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/BookRepository.java @@ -0,0 +1,6 @@ +package com.baeldung.boot.readonlyrepository; + +import org.springframework.data.repository.CrudRepository; + +public interface BookRepository extends BookReadOnlyRepository, CrudRepository { +} diff --git a/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryUnitTest.java b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryUnitTest.java new file mode 100644 index 0000000000..cdb6065f23 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-2/src/test/java/com/baeldung/boot/readonlyrepository/ReadOnlyRepositoryUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.boot.readonlyrepository; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import java.util.NoSuchElementException; + +@SpringBootTest( classes = ReadOnlyRepositoryApplication.class ) +public class ReadOnlyRepositoryUnitTest +{ + @Autowired + private BookRepository bookRepository; + + @Autowired + private BookReadOnlyRepository bookReadOnlyRepository; + + @Test + public void givenBooks_whenUsingReadOnlyRepository_thenGetThem() { + Book aChristmasCarolCharlesDickens = new Book(); + aChristmasCarolCharlesDickens.setTitle("A Christmas Carol"); + aChristmasCarolCharlesDickens.setAuthor("Charles Dickens"); + bookRepository.save(aChristmasCarolCharlesDickens); + + Book greatExpectationsCharlesDickens = new Book(); + greatExpectationsCharlesDickens.setTitle("Great Expectations"); + greatExpectationsCharlesDickens.setAuthor("Charles Dickens"); + bookRepository.save(greatExpectationsCharlesDickens); + + Book greatExpectationsKathyAcker = new Book(); + greatExpectationsKathyAcker.setTitle("Great Expectations"); + greatExpectationsKathyAcker.setAuthor("Kathy Acker"); + bookRepository.save(greatExpectationsKathyAcker); + + List charlesDickensBooks = bookReadOnlyRepository.findByAuthor("Charles Dickens"); + Assertions.assertEquals(2, charlesDickensBooks.size()); + + List greatExpectationsBooks = bookReadOnlyRepository.findByTitle("Great Expectations"); + Assertions.assertEquals(2, greatExpectationsBooks.size()); + + List allBooks = bookReadOnlyRepository.findAll(); + Assertions.assertEquals(3, allBooks.size()); + + Long bookId = allBooks.get(0).getId(); + Book book = bookReadOnlyRepository.findById(bookId).orElseThrow(NoSuchElementException::new); + Assertions.assertNotNull(book); + } +} diff --git a/spring-boot-modules/spring-boot-deployment/pom.xml b/spring-boot-modules/spring-boot-deployment/pom.xml index 94a4018103..ae546016f2 100644 --- a/spring-boot-modules/spring-boot-deployment/pom.xml +++ b/spring-boot-modules/spring-boot-deployment/pom.xml @@ -84,12 +84,6 @@ ${jquery.version} - - org.springframework.cloud - spring-cloud-context - ${springcloud.version} - - org.apache.httpcomponents httpclient @@ -195,9 +189,7 @@ 2.2 18.0 3.1.7 - 2.0.2.RELEASE 4.5.8 - 2.3.3.RELEASE diff --git a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/charencoding/controller/CharEncodingCheckControllerUnitTest.java b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/charencoding/controller/CharEncodingCheckControllerUnitTest.java index 6dcbfe390f..8467a25acd 100644 --- a/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/charencoding/controller/CharEncodingCheckControllerUnitTest.java +++ b/spring-boot-modules/spring-boot-mvc-3/src/test/java/com/baeldung/charencoding/controller/CharEncodingCheckControllerUnitTest.java @@ -1,34 +1,35 @@ package com.baeldung.charencoding.controller; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.filter.CharacterEncodingFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; -import org.junit.jupiter.api.Test; -import org.springframework.web.filter.CharacterEncodingFilter; +import static org.junit.jupiter.api.Assertions.assertEquals; class CharEncodingCheckControllerUnitTest { @Test void whenCharEncodingFilter_thenVerifyEncoding() throws ServletException, IOException { - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - FilterChain chain = mock(FilterChain.class); - + HttpServletRequest request = new MockHttpServletRequest(); + HttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = new MockFilterChain(); + CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); filter.setForceEncoding(true); - + filter.doFilter(request, response, chain); - - verify(request).setCharacterEncoding("UTF-8"); - verify(response).setCharacterEncoding("UTF-8"); + + assertEquals("UTF-8", request.getCharacterEncoding()); + assertEquals("UTF-8", response.getCharacterEncoding()); } } diff --git a/spring-boot-modules/spring-boot-mvc-jersey/README.md b/spring-boot-modules/spring-boot-mvc-jersey/README.md new file mode 100644 index 0000000000..192658c4a5 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/README.md @@ -0,0 +1,8 @@ +## Spring Boot Admin + +This module contains articles about Spring Boot: JAX-RS vs Spring + + +### Relevant Articles: + +- [REST API: JAX-RS vs Spring](https://www.baeldung.com/rest-api-jax-rs-vs-spring) diff --git a/spring-boot-modules/spring-boot-mvc-jersey/pom.xml b/spring-boot-modules/spring-boot-mvc-jersey/pom.xml new file mode 100644 index 0000000000..ab117c4d2e --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + ../ + + + spring-boot-mvc-jersey + 0.0.1-SNAPSHOT + pom + + spring-boot-mvc-jersey + + + spring-boot-jersey + spring-boot-mvc + + diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml new file mode 100644 index 0000000000..5756a27eca --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.4.2 + + + + com.baeldung.boot + spring-boot-jersey + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-jersey + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/JerseyApplication.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/JerseyApplication.java new file mode 100644 index 0000000000..b8ee0d0115 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/JerseyApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.jersey; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JerseyApplication { + + public static void main(String[] args) { + SpringApplication.run(JerseyApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/controllers/HelloController.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/controllers/HelloController.java new file mode 100644 index 0000000000..2774e458de --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/java/com/baeldung/boot/jersey/controllers/HelloController.java @@ -0,0 +1,20 @@ +package com.baeldung.boot.jersey.controllers; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/hello") +public class HelloController { + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response hello(@PathParam("name") String name) { + return Response.ok("Hello, " + name).build(); + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/resources/application.properties b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/resources/application.properties new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java new file mode 100644 index 0000000000..9bd6bbf029 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/JerseyApplicationIntegrationTests.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.jersey; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class JerseyApplicationIntegrationTests { + + @Test + void contextLoads() { + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/controllers/HelloControllerUnitTest.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/controllers/HelloControllerUnitTest.java new file mode 100644 index 0000000000..e0c48da5bd --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-jersey/src/test/java/com/baeldung/boot/jersey/controllers/HelloControllerUnitTest.java @@ -0,0 +1,27 @@ +package com.baeldung.boot.jersey.controllers; + +import javax.ws.rs.core.Response; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@Import(HelloController.class) +@ExtendWith(SpringExtension.class) +public class HelloControllerUnitTest { + + @Autowired + private HelloController helloController; + + @Test + public void whenHelloIsInvokedWithCaio_thenReturn200AsStatusAndHelloCaioAsBody() { + Response response = this.helloController.hello("Caio"); + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getEntity()).isEqualTo("Hello, Caio"); + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml new file mode 100644 index 0000000000..69d355e574 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.4.2 + + + + com.baeldung.boot + spring-boot-mvc + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/MvcApplication.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/MvcApplication.java new file mode 100644 index 0000000000..9c42c72fd7 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/MvcApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.mvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MvcApplication { + + public static void main(String[] args) { + SpringApplication.run(MvcApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/controllers/HelloController.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/controllers/HelloController.java new file mode 100644 index 0000000000..0b4c569ba9 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/java/com/baeldung/boot/mvc/controllers/HelloController.java @@ -0,0 +1,21 @@ +package com.baeldung.boot.mvc.controllers; + + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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; + +@RestController +@RequestMapping("/hello") +public class HelloController { + + @GetMapping(value = "/{name}", produces = MediaType.TEXT_PLAIN_VALUE) + public ResponseEntity hello(@PathVariable String name) { + return new ResponseEntity<>("Hello, " + name, HttpStatus.OK); + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/resources/application.properties b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/resources/application.properties new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java new file mode 100644 index 0000000000..d05cb97e47 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/MvcApplicationIntegrationTests.java @@ -0,0 +1,13 @@ +package com.baeldung.boot.mvc; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MvcApplicationIntegrationTests { + + @Test + void contextLoads() { + } + +} diff --git a/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/controllers/HelloControllerUnitTest.java b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/controllers/HelloControllerUnitTest.java new file mode 100644 index 0000000000..3bef9df4fd --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-jersey/spring-boot-mvc/src/test/java/com/baeldung/boot/mvc/controllers/HelloControllerUnitTest.java @@ -0,0 +1,27 @@ +package com.baeldung.boot.mvc.controllers; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@Import(HelloController.class) +@ExtendWith(SpringExtension.class) +public class HelloControllerUnitTest { + + @Autowired + private HelloController helloController; + + @Test + public void whenHelloIsInvokedWithCaio_thenReturn200AsStatusAndHelloCaioAsBody() { + ResponseEntity response = this.helloController.hello("Caio"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo("Hello, Caio"); + } + +} diff --git a/spring-boot-modules/spring-boot-properties-3/src/test/java/com/baeldung/boot/properties/multidocument/StagingMultidocumentFilesIntegrationTest.java b/spring-boot-modules/spring-boot-properties-3/src/test/java/com/baeldung/boot/properties/multidocument/StagingMultidocumentFilesIntegrationTest.java index 8040c93ee0..e02d1de272 100644 --- a/spring-boot-modules/spring-boot-properties-3/src/test/java/com/baeldung/boot/properties/multidocument/StagingMultidocumentFilesIntegrationTest.java +++ b/spring-boot-modules/spring-boot-properties-3/src/test/java/com/baeldung/boot/properties/multidocument/StagingMultidocumentFilesIntegrationTest.java @@ -2,6 +2,7 @@ package com.baeldung.boot.properties.multidocument; import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -21,6 +22,7 @@ public class StagingMultidocumentFilesIntegrationTest { private String baelRootProperty; @Test + @Disabled("Fix and update https://www.baeldung.com/spring-boot-yaml-vs-properties article") public void givenProductionProfileActive_whenApplicationStarts_thenDefaultPropertiesUser() { assertThat(baelCustomProperty).isEqualTo("stagingValue"); assertThat(baelRootProperty).isEqualTo("defaultRootLevelValue"); diff --git a/spring-boot-modules/spring-boot-runtime-2/README.md b/spring-boot-modules/spring-boot-runtime-2/README.md index f997f2473d..9f0d814d8d 100644 --- a/spring-boot-modules/spring-boot-runtime-2/README.md +++ b/spring-boot-modules/spring-boot-runtime-2/README.md @@ -3,4 +3,4 @@ This module contains articles about administering a Spring Boot runtime ### Relevant Articles: - - + - [Configure the Heap Size When Starting a Spring Boot Application](https://www.baeldung.com/spring-boot-heap-size) diff --git a/spring-boot-modules/spring-boot-springdoc/README.md b/spring-boot-modules/spring-boot-springdoc/README.md index 2447f30f6b..608e4afa2e 100644 --- a/spring-boot-modules/spring-boot-springdoc/README.md +++ b/spring-boot-modules/spring-boot-springdoc/README.md @@ -2,3 +2,4 @@ - [Documenting a Spring REST API Using OpenAPI 3.0](https://www.baeldung.com/spring-rest-openapi-documentation) - [Spring REST Docs vs OpenAPI](https://www.baeldung.com/spring-rest-docs-vs-openapi) +- [Hiding Endpoints From Swagger Documentation in Spring Boot](https://www.baeldung.com/spring-swagger-hiding-endpoints) diff --git a/spring-boot-rest-2/README.md b/spring-boot-rest-2/README.md new file mode 100644 index 0000000000..f09159198c --- /dev/null +++ b/spring-boot-rest-2/README.md @@ -0,0 +1,3 @@ +### Relevant Article: + +- [Get All Endpoints in Spring Boot](https://www.baeldung.com/spring-boot-get-all-endpoints) diff --git a/spring-boot-rest-2/pom.xml b/spring-boot-rest-2/pom.xml new file mode 100644 index 0000000000..d74c393f27 --- /dev/null +++ b/spring-boot-rest-2/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + com.baeldung.web + spring-boot-rest-2 + spring-boot-rest-2 + war + Spring Boot Rest Module + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/SpringBootRestApplication.java b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/SpringBootRestApplication.java new file mode 100644 index 0000000000..510e208f9e --- /dev/null +++ b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/SpringBootRestApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.endpoint; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootRestApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootRestApplication.class, args); + } +} diff --git a/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/controller/HelloController.java b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/controller/HelloController.java new file mode 100644 index 0000000000..732b298981 --- /dev/null +++ b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/controller/HelloController.java @@ -0,0 +1,15 @@ +package com.baeldung.endpoint.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping("/hello") + public ResponseEntity hello() { + return ResponseEntity.ok("hello baeldung"); + } + +} diff --git a/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/AnnotationDrivenEndpointsListener.java b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/AnnotationDrivenEndpointsListener.java new file mode 100644 index 0000000000..c57f6b5ecd --- /dev/null +++ b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/AnnotationDrivenEndpointsListener.java @@ -0,0 +1,27 @@ +package com.baeldung.endpoint.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.Map; + +@Configuration +public class AnnotationDrivenEndpointsListener { + private final Logger LOGGER = LoggerFactory.getLogger("AnnotationDrivenEndpointsListener.class"); + + @EventListener + public void handleContextRefresh(ContextRefreshedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = requestMappingHandlerMapping.getHandlerMethods(); + map.forEach((key, value) -> LOGGER.info("{} {}", key, value)); + } +} diff --git a/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/EndpointsListener.java b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/EndpointsListener.java new file mode 100644 index 0000000000..ae00fc3927 --- /dev/null +++ b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/listener/EndpointsListener.java @@ -0,0 +1,27 @@ +package com.baeldung.endpoint.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.Map; + +@Configuration +public class EndpointsListener implements ApplicationListener { + private final Logger LOGGER = LoggerFactory.getLogger("EndpointsListener.class"); + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + ApplicationContext applicationContext = event.getApplicationContext(); + RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = requestMappingHandlerMapping.getHandlerMethods(); + map.forEach((key, value) -> LOGGER.info("{} {}", key, value)); + } +} diff --git a/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/swagger/SpringFoxConfig.java b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/swagger/SpringFoxConfig.java new file mode 100644 index 0000000000..bd258122cd --- /dev/null +++ b/spring-boot-rest-2/src/main/java/com/baeldung/endpoint/swagger/SpringFoxConfig.java @@ -0,0 +1,21 @@ +package com.baeldung.endpoint.swagger; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +@Configuration +public class SpringFoxConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } +} diff --git a/spring-boot-rest-2/src/main/resources/application.properties b/spring-boot-rest-2/src/main/resources/application.properties new file mode 100644 index 0000000000..5046c9660f --- /dev/null +++ b/spring-boot-rest-2/src/main/resources/application.properties @@ -0,0 +1,2 @@ + +management.endpoints.web.exposure.include=mappings diff --git a/spring-boot-rest/src/main/java/com/baeldung/web/config/MyCustomErrorAttributes.java b/spring-boot-rest/src/main/java/com/baeldung/web/config/MyCustomErrorAttributes.java index 1948d5552f..5e776c0e29 100644 --- a/spring-boot-rest/src/main/java/com/baeldung/web/config/MyCustomErrorAttributes.java +++ b/spring-boot-rest/src/main/java/com/baeldung/web/config/MyCustomErrorAttributes.java @@ -2,6 +2,7 @@ package com.baeldung.web.config; import java.util.Map; +import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; @@ -10,8 +11,8 @@ import org.springframework.web.context.request.WebRequest; public class MyCustomErrorAttributes extends DefaultErrorAttributes { @Override - public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { - Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); + public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { + Map errorAttributes = super.getErrorAttributes(webRequest, options); errorAttributes.put("locale", webRequest.getLocale() .toString()); errorAttributes.remove("error"); diff --git a/spring-boot-rest/src/main/java/com/baeldung/web/config/MyErrorController.java b/spring-boot-rest/src/main/java/com/baeldung/web/config/MyErrorController.java index cf3f9c4dbd..05150716f6 100644 --- a/spring-boot-rest/src/main/java/com/baeldung/web/config/MyErrorController.java +++ b/spring-boot-rest/src/main/java/com/baeldung/web/config/MyErrorController.java @@ -4,7 +4,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; -import org.springframework.boot.autoconfigure.web.ErrorProperties; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.http.HttpStatus; @@ -16,13 +16,13 @@ import org.springframework.web.bind.annotation.RequestMapping; @Component public class MyErrorController extends BasicErrorController { - public MyErrorController(ErrorAttributes errorAttributes) { - super(errorAttributes, new ErrorProperties()); + public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) { + super(errorAttributes, serverProperties.getError()); } @RequestMapping(produces = MediaType.APPLICATION_XML_VALUE) public ResponseEntity> xmlError(HttpServletRequest request) { - Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_XML)); + Map body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.APPLICATION_XML)); body.put("xmlkey", "the XML response is different!"); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); diff --git a/spring-boot-rest/src/main/java/com/baeldung/web/controller/CustomerController.java b/spring-boot-rest/src/main/java/com/baeldung/web/controller/CustomerController.java index 2b7dc1eee1..79d3b972ff 100644 --- a/spring-boot-rest/src/main/java/com/baeldung/web/controller/CustomerController.java +++ b/spring-boot-rest/src/main/java/com/baeldung/web/controller/CustomerController.java @@ -50,7 +50,7 @@ public class CustomerController { } Link link = linkTo(methodOn(CustomerController.class).getOrdersForCustomer(customerId)).withSelfRel(); - CollectionModel result = new CollectionModel<>(orders, link); + CollectionModel result = CollectionModel.of(orders, link); return result; } @@ -72,7 +72,7 @@ public class CustomerController { } Link link = linkTo(CustomerController.class).withSelfRel(); - CollectionModel result = new CollectionModel<>(allCustomers, link); + CollectionModel result = CollectionModel.of(allCustomers, link); return result; } diff --git a/spring-boot-rest/src/main/resources/application.properties b/spring-boot-rest/src/main/resources/application.properties index 176deb4f49..1e985feed9 100644 --- a/spring-boot-rest/src/main/resources/application.properties +++ b/spring-boot-rest/src/main/resources/application.properties @@ -3,3 +3,4 @@ server.servlet.context-path=/spring-boot-rest ### Spring Boot default error handling configurations #server.error.whitelabel.enabled=false #server.error.include-stacktrace=always +server.error.include-message=always diff --git a/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerRequestIntegrationTest.java b/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerRequestIntegrationTest.java index a277d5a685..bb84b88bd9 100644 --- a/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerRequestIntegrationTest.java +++ b/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerRequestIntegrationTest.java @@ -33,7 +33,7 @@ public class ExamplePostControllerRequestIntegrationTest { @Before public void preTest() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); mockMvc = MockMvcBuilders .standaloneSetup(exampleController) .build(); diff --git a/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerResponseIntegrationTest.java b/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerResponseIntegrationTest.java index f8c70b0f4a..efc3310812 100644 --- a/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerResponseIntegrationTest.java +++ b/spring-boot-rest/src/test/java/com/baeldung/controllers/ExamplePostControllerResponseIntegrationTest.java @@ -34,7 +34,7 @@ public class ExamplePostControllerResponseIntegrationTest { @Before public void preTest() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); mockMvc = MockMvcBuilders .standaloneSetup(exampleController) .build(); diff --git a/spring-boot-rest/src/test/java/com/baeldung/web/StudentControllerIntegrationTest.java b/spring-boot-rest/src/test/java/com/baeldung/web/StudentControllerIntegrationTest.java index 54ac69ebeb..d2d9181797 100644 --- a/spring-boot-rest/src/test/java/com/baeldung/web/StudentControllerIntegrationTest.java +++ b/spring-boot-rest/src/test/java/com/baeldung/web/StudentControllerIntegrationTest.java @@ -8,12 +8,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.server.MediaTypeNotSupportedStatusException; import com.baeldung.web.controller.students.Student; import com.fasterxml.jackson.databind.ObjectMapper; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; diff --git a/spring-caching/pom.xml b/spring-caching/pom.xml index f58be35a76..9fa9ba0333 100644 --- a/spring-caching/pom.xml +++ b/spring-caching/pom.xml @@ -79,7 +79,6 @@ org.springframework.data spring-data-commons - 2.3.0.RELEASE diff --git a/spring-cloud/spring-cloud-config/docker/Dockerfile b/spring-cloud/spring-cloud-config/docker/Dockerfile deleted file mode 100644 index bdb37abf80..0000000000 --- a/spring-cloud/spring-cloud-config/docker/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM alpine:edge -MAINTAINER baeldung.com -RUN apk add --no-cache openjdk8 -COPY files/UnlimitedJCEPolicyJDK8/* /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/ diff --git a/spring-cloud/spring-cloud-config/docker/Dockerfile.client b/spring-cloud/spring-cloud-config/docker/Dockerfile.client deleted file mode 100644 index 5fbc0b98c0..0000000000 --- a/spring-cloud/spring-cloud-config/docker/Dockerfile.client +++ /dev/null @@ -1,6 +0,0 @@ -FROM alpine-java:base -MAINTAINER baeldung.com -RUN apk --no-cache add netcat-openbsd -COPY files/config-client.jar /opt/spring-cloud/lib/ -COPY files/config-client-entrypoint.sh /opt/spring-cloud/bin/ -RUN chmod 755 /opt/spring-cloud/bin/config-client-entrypoint.sh diff --git a/spring-cloud/spring-cloud-config/docker/Dockerfile.server b/spring-cloud/spring-cloud-config/docker/Dockerfile.server deleted file mode 100644 index 4f7bd751e8..0000000000 --- a/spring-cloud/spring-cloud-config/docker/Dockerfile.server +++ /dev/null @@ -1,9 +0,0 @@ -FROM alpine-java:base -MAINTAINER baeldung.com -COPY files/config-server.jar /opt/spring-cloud/lib/ -ENV SPRING_APPLICATION_JSON='{"spring": {"cloud": {"config": {"server": \ - {"git": {"uri": "/var/lib/spring-cloud/config-repo", "clone-on-start": true}}}}}}' -ENTRYPOINT ["/usr/bin/java"] -CMD ["-jar", "/opt/spring-cloud/lib/config-server.jar"] -VOLUME /var/lib/spring-cloud/config-repo -EXPOSE 8888 diff --git a/spring-cloud/spring-cloud-config/docker/config-client-entrypoint.sh b/spring-cloud/spring-cloud-config/docker/config-client-entrypoint.sh deleted file mode 100644 index 12352119fa..0000000000 --- a/spring-cloud/spring-cloud-config/docker/config-client-entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -while ! nc -z config-server 8888 ; do - echo "Waiting for upcoming Config Server" - sleep 2 -done - -java -jar /opt/spring-cloud/lib/config-client.jar diff --git a/spring-cloud/spring-cloud-config/docker/docker-compose.scale.yml b/spring-cloud/spring-cloud-config/docker/docker-compose.scale.yml deleted file mode 100644 index f74153bea3..0000000000 --- a/spring-cloud/spring-cloud-config/docker/docker-compose.scale.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: '2' -services: - config-server: - build: - context: . - dockerfile: Dockerfile.server - image: config-server:latest - expose: - - 8888 - networks: - - spring-cloud-network - volumes: - - spring-cloud-config-repo:/var/lib/spring-cloud/config-repo - logging: - driver: json-file - config-client: - build: - context: . - dockerfile: Dockerfile.client - image: config-client:latest - entrypoint: /opt/spring-cloud/bin/config-client-entrypoint.sh - environment: - SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:8888"}}}}' - expose: - - 8080 - ports: - - 8080 - networks: - - spring-cloud-network - links: - - config-server:config-server - depends_on: - - config-server - logging: - driver: json-file -networks: - spring-cloud-network: - driver: bridge -volumes: - spring-cloud-config-repo: - external: true diff --git a/spring-cloud/spring-cloud-config/docker/docker-compose.yml b/spring-cloud/spring-cloud-config/docker/docker-compose.yml deleted file mode 100644 index 74c71b651c..0000000000 --- a/spring-cloud/spring-cloud-config/docker/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ -version: '2' -services: - config-server: - container_name: config-server - build: - context: . - dockerfile: Dockerfile.server - image: config-server:latest - expose: - - 8888 - networks: - - spring-cloud-network - volumes: - - spring-cloud-config-repo:/var/lib/spring-cloud/config-repo - logging: - driver: json-file - config-client: - container_name: config-client - build: - context: . - dockerfile: Dockerfile.client - image: config-client:latest - entrypoint: /opt/spring-cloud/bin/config-client-entrypoint.sh - environment: - SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:8888"}}}}' - expose: - - 8080 - ports: - - 8080:8080 - networks: - - spring-cloud-network - links: - - config-server:config-server - depends_on: - - config-server - logging: - driver: json-file -networks: - spring-cloud-network: - driver: bridge -volumes: - spring-cloud-config-repo: - external: true diff --git a/spring-cloud/spring-cloud-docker/docker-compose-scale.yml b/spring-cloud/spring-cloud-docker/docker-compose-scale.yml new file mode 100644 index 0000000000..9035a16d7e --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-compose-scale.yml @@ -0,0 +1,23 @@ +version: '2' +services: + message-server: + build: + context: docker-message-server + dockerfile: Dockerfile + image: message-server:latest + ports: + - 18800-18888:8888 + networks: + - spring-cloud-network + product-server: + build: + context: docker-product-server + dockerfile: Dockerfile + image: product-server:latest + ports: + - 19900-19999:9999 + networks: + - spring-cloud-network +networks: + spring-cloud-network: + driver: bridge \ No newline at end of file diff --git a/spring-cloud/spring-cloud-docker/docker-compose.yml b/spring-cloud/spring-cloud-docker/docker-compose.yml new file mode 100644 index 0000000000..d485f93631 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-compose.yml @@ -0,0 +1,25 @@ +version: '2' +services: + message-server: + container_name: message-server + build: + context: docker-message-server + dockerfile: Dockerfile + image: message-server:latest + ports: + - 18888:8888 + networks: + - spring-cloud-network + product-server: + container_name: product-server + build: + context: docker-product-server + dockerfile: Dockerfile + image: product-server:latest + ports: + - 19999:9999 + networks: + - spring-cloud-network +networks: + spring-cloud-network: + driver: bridge \ No newline at end of file diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/Dockerfile b/spring-cloud/spring-cloud-docker/docker-message-server/Dockerfile new file mode 100644 index 0000000000..73f55c8923 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-message-server/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:8-jdk-alpine +MAINTAINER baeldung.com +COPY target/docker-message-server-1.0.0.jar message-server.jar +ENTRYPOINT ["java","-jar","/message-server.jar"] \ No newline at end of file diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/pom.xml b/spring-cloud/spring-cloud-docker/docker-message-server/pom.xml new file mode 100644 index 0000000000..fafd291c00 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-message-server/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + docker-message-server + docker-message-server + 1.0.0 + + + com.baeldung.spring.cloud + spring-cloud-docker + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerMessageController.java b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerMessageController.java new file mode 100644 index 0000000000..71d630da8f --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerMessageController.java @@ -0,0 +1,12 @@ +package com.baeldung.spring.cloud.config.docker; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DockerMessageController { + @GetMapping("/messages") + public String getMessage() { + return "Hello from Docker!"; + } +} diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java new file mode 100644 index 0000000000..38bd4d9541 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java @@ -0,0 +1,11 @@ +package com.baeldung.spring.cloud.config.docker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DockerServer { + public static void main(String[] args) { + SpringApplication.run(DockerServer.class, args); + } +} diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties new file mode 100644 index 0000000000..c4e658f9e2 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8888 diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/Dockerfile b/spring-cloud/spring-cloud-docker/docker-product-server/Dockerfile new file mode 100644 index 0000000000..80117e4ae1 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-product-server/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:8-jdk-alpine +MAINTAINER baeldung.com +COPY target/docker-product-server-1.0.0.jar product-server.jar +ENTRYPOINT ["java","-jar","/product-server.jar"] \ No newline at end of file diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/pom.xml b/spring-cloud/spring-cloud-docker/docker-product-server/pom.xml new file mode 100644 index 0000000000..a5bd6c50ea --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-product-server/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + docker-product-server + docker-product-server + 1.0.0 + + + com.baeldung.spring.cloud + spring-cloud-docker + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerProductController.java b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerProductController.java new file mode 100644 index 0000000000..15781f763b --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerProductController.java @@ -0,0 +1,12 @@ +package com.baeldung.spring.cloud.config.docker; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DockerProductController { + @GetMapping("/products") + public String getMessage() { + return "A brand new product"; + } +} diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java new file mode 100644 index 0000000000..38bd4d9541 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/java/com/baeldung/spring/cloud/config/docker/DockerServer.java @@ -0,0 +1,11 @@ +package com.baeldung.spring.cloud.config.docker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DockerServer { + public static void main(String[] args) { + SpringApplication.run(DockerServer.class, args); + } +} diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties new file mode 100644 index 0000000000..2c5fdc3545 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=9999 diff --git a/spring-cloud/spring-cloud-docker/pom.xml b/spring-cloud/spring-cloud-docker/pom.xml new file mode 100644 index 0000000000..a9d978c9d4 --- /dev/null +++ b/spring-cloud/spring-cloud-docker/pom.xml @@ -0,0 +1,28 @@ + + + + 4.0.0 + spring-cloud-docker + spring-cloud-docker + 1.0.0-SNAPSHOT + pom + + + com.baeldung.spring.cloud + spring-cloud + 1.0.0-SNAPSHOT + + + + 8 + 8 + + + + docker-message-server + docker-product-server + + + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-openfeign/README.md b/spring-cloud/spring-cloud-openfeign/README.md index 735903db72..bcfd769d0c 100644 --- a/spring-cloud/spring-cloud-openfeign/README.md +++ b/spring-cloud/spring-cloud-openfeign/README.md @@ -2,3 +2,4 @@ - [Introduction to Spring Cloud OpenFeign](https://www.baeldung.com/spring-cloud-openfeign) - [Differences Between Netflix Feign and OpenFeign](https://www.baeldung.com/netflix-feign-vs-openfeign) +- [File Upload With Open Feign](https://www.baeldung.com/java-feign-file-upload) diff --git a/spring-cloud/spring-cloud-openfeign/pom.xml b/spring-cloud/spring-cloud-openfeign/pom.xml index c1f3f2dc30..bdde46fe96 100644 --- a/spring-cloud/spring-cloud-openfeign/pom.xml +++ b/spring-cloud/spring-cloud-openfeign/pom.xml @@ -37,12 +37,24 @@ io.github.openfeign feign-okhttp - + org.springframework.boot spring-boot-starter-web + + + io.github.openfeign.form + feign-form + 3.8.0 + + + io.github.openfeign.form + feign-form-spring + 3.8.0 + + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java new file mode 100644 index 0000000000..943134213a --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/config/FeignSupportConfig.java @@ -0,0 +1,22 @@ +package com.baeldung.cloud.openfeign.fileupload.config; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.cloud.openfeign.support.SpringEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; + +public class FeignSupportConfig { + @Bean + public Encoder multipartFormEncoder() { + return new SpringFormEncoder(new SpringEncoder(new ObjectFactory() { + @Override + public HttpMessageConverters getObject() { + return new HttpMessageConverters(new RestTemplate().getMessageConverters()); + } + })); + } +} diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java new file mode 100644 index 0000000000..ebdf7ff6c8 --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/controller/FileController.java @@ -0,0 +1,28 @@ +package com.baeldung.cloud.openfeign.fileupload.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.baeldung.cloud.openfeign.fileupload.service.UploadService; + +@RestController +public class FileController { + + @Autowired + private UploadService service; + + @PostMapping(value = "/upload") + public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) { + return service.uploadFile(file); + } + + @PostMapping(value = "/upload-mannual-client") + public boolean handleFileUploadWithManualClient( + @RequestPart(value = "file") MultipartFile file) { + return service.uploadFileWithManualClient(file); + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java new file mode 100644 index 0000000000..63d17130e9 --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadClient.java @@ -0,0 +1,15 @@ +package com.baeldung.cloud.openfeign.fileupload.service; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +import com.baeldung.cloud.openfeign.fileupload.config.FeignSupportConfig; + +@FeignClient(name = "file", url = "http://localhost:8081", configuration = FeignSupportConfig.class) +public interface UploadClient { + @PostMapping(value = "/upload-file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + String fileUpload(@RequestPart(value = "file") MultipartFile file); +} diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadResource.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadResource.java new file mode 100644 index 0000000000..26e658a7f0 --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadResource.java @@ -0,0 +1,16 @@ +package com.baeldung.cloud.openfeign.fileupload.service; + +import org.springframework.web.multipart.MultipartFile; + +import feign.Headers; +import feign.Param; +import feign.RequestLine; +import feign.Response; + +public interface UploadResource { + + @RequestLine("POST /upload-file") + @Headers("Content-Type: multipart/form-data") + Response uploadFile(@Param("file") MultipartFile file); + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java new file mode 100644 index 0000000000..7dd7f5a89c --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/fileupload/service/UploadService.java @@ -0,0 +1,29 @@ +package com.baeldung.cloud.openfeign.fileupload.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import feign.Feign; +import feign.Response; +import feign.form.spring.SpringFormEncoder; + +@Service +public class UploadService { + private static final String HTTP_FILE_UPLOAD_URL = "http://localhost:8081"; + + @Autowired + private UploadClient client; + + public boolean uploadFileWithManualClient(MultipartFile file) { + UploadResource fileUploadResource = Feign.builder().encoder(new SpringFormEncoder()) + .target(UploadResource.class, HTTP_FILE_UPLOAD_URL); + Response response = fileUploadResource.uploadFile(file); + return response.status() == 200; + } + + public String uploadFile(MultipartFile file) { + return client.fileUpload(file); + } + +} \ No newline at end of file diff --git a/spring-cloud/spring-cloud-openfeign/src/main/resources/fileupload.txt b/spring-cloud/spring-cloud-openfeign/src/main/resources/fileupload.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/OpenFeignFileUploadLiveTest.java b/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/OpenFeignFileUploadLiveTest.java new file mode 100644 index 0000000000..f558e07491 --- /dev/null +++ b/spring-cloud/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/OpenFeignFileUploadLiveTest.java @@ -0,0 +1,50 @@ +package com.baeldung.cloud.openfeign; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.multipart.MultipartFile; + +import com.baeldung.cloud.openfeign.fileupload.service.UploadService; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class OpenFeignFileUploadLiveTest { + + @Autowired + private UploadService uploadService; + + private static String FILE_NAME = "fileupload.txt"; + + @Test + public void whenFeignBuilder_thenFileUploadSuccess() throws IOException { + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + File file = new File(classloader.getResource(FILE_NAME).getFile()); + Assert.assertTrue(file.exists()); + FileInputStream input = new FileInputStream(file); + MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), "text/plain", + IOUtils.toByteArray(input)); + Assert.assertTrue(uploadService.uploadFileWithManualClient(multipartFile)); + } + + @Test + public void whenAnnotatedFeignClient_thenFileUploadSuccess() throws IOException { + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + File file = new File(classloader.getResource(FILE_NAME).getFile()); + Assert.assertTrue(file.exists()); + FileInputStream input = new FileInputStream(file); + MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), "text/plain", + IOUtils.toByteArray(input)); + String uploadFile = uploadService.uploadFile(multipartFile); + Assert.assertNotNull(uploadFile); + } +} diff --git a/spring-cloud/spring-cloud-zuul/pom.xml b/spring-cloud/spring-cloud-zuul/pom.xml index 6035ba7e59..13834848fe 100644 --- a/spring-cloud/spring-cloud-zuul/pom.xml +++ b/spring-cloud/spring-cloud-zuul/pom.xml @@ -38,6 +38,11 @@ pom import + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + ${spring-cloud-netflix-zuul.version} + @@ -46,10 +51,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.cloud - spring-cloud-starter-netflix-zuul - com.h2database h2 @@ -79,8 +80,8 @@ - Hoxton.SR4 - 2.3.3.RELEASE + 2020.0.0 + 2.2.2.RELEASE diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml b/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml index b42d32b6b3..bb735a71ed 100644 --- a/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml +++ b/spring-cloud/spring-cloud-zuul/spring-zuul-rate-limiting/pom.xml @@ -19,6 +19,10 @@ spring-cloud-zuul-ratelimit ${rate.limit.version} + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + org.springframework.boot spring-boot-starter-data-jpa diff --git a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/pom.xml b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/pom.xml index 7978d9c77b..b7e1702558 100644 --- a/spring-cloud/spring-cloud-zuul/spring-zuul-ui/pom.xml +++ b/spring-cloud/spring-cloud-zuul/spring-zuul-ui/pom.xml @@ -13,6 +13,10 @@ + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-core-3/README.md b/spring-core-3/README.md index b6257cb9a4..7232ad71a1 100644 --- a/spring-core-3/README.md +++ b/spring-core-3/README.md @@ -11,4 +11,4 @@ This module contains articles about core Spring functionality - [Difference Between BeanFactory and ApplicationContext](https://www.baeldung.com/spring-beanfactory-vs-applicationcontext) - [A Spring Custom Annotation for a Better DAO](http://www.baeldung.com/spring-annotation-bean-pre-processor) - [Custom Scope in Spring](http://www.baeldung.com/spring-custom-scope) -- More articles: [[<-- prev]](/spring-core-2) +- More articles: [[<-- prev]](/spring-core-2) [[next -->]](/spring-core-4) diff --git a/spring-core-4/README.md b/spring-core-4/README.md index 03a6747c1d..3b2c3d4764 100644 --- a/spring-core-4/README.md +++ b/spring-core-4/README.md @@ -11,4 +11,4 @@ This module contains articles about core Spring functionality - [Using @Autowired in Abstract Classes](https://www.baeldung.com/spring-autowired-abstract-class) - [Constructor Injection in Spring with Lombok](https://www.baeldung.com/spring-injection-lombok) - [The Spring ApplicationContext](https://www.baeldung.com/spring-application-context) -- More articles: [[<-- prev]](/spring-core-3) +- More articles: [[<-- prev]](/spring-core-3) [[next -->]](/spring-core-5) diff --git a/spring-core-5/README.md b/spring-core-5/README.md new file mode 100644 index 0000000000..4109faca67 --- /dev/null +++ b/spring-core-5/README.md @@ -0,0 +1,8 @@ +## Spring Core + +This module contains articles about core Spring functionality + +## Relevant Articles: + +- [Spring @Component Annotation](https://www.baeldung.com/spring-component-annotation) +- More articles: [[<-- prev]](/spring-core-4) diff --git a/spring-core-5/pom.xml b/spring-core-5/pom.xml new file mode 100644 index 0000000000..743002c137 --- /dev/null +++ b/spring-core-5/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + spring-core-5 + spring-core-5 + + + com.baeldung + parent-spring-5 + 0.0.1-SNAPSHOT + ../parent-spring-5 + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot-starter.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-starter.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.version} + + + + + + 5.3.3 + 2.4.2 + 2.22.1 + + + diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/AmbiguousBean.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/AmbiguousBean.java new file mode 100644 index 0000000000..70e1f0a79a --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/AmbiguousBean.java @@ -0,0 +1,4 @@ +package com.baeldung.component.inscope; + +public interface AmbiguousBean { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanExample.java new file mode 100644 index 0000000000..d1b4cd34f1 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanExample.java @@ -0,0 +1,4 @@ +package com.baeldung.component.inscope; + +public class BeanExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplA.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplA.java new file mode 100644 index 0000000000..08e1661499 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplA.java @@ -0,0 +1,4 @@ +package com.baeldung.component.inscope; + +public class BeanImplA implements AmbiguousBean { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplB.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplB.java new file mode 100644 index 0000000000..8a45581532 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/BeanImplB.java @@ -0,0 +1,4 @@ +package com.baeldung.component.inscope; + +public class BeanImplB implements AmbiguousBean { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentApplication.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentApplication.java new file mode 100644 index 0000000000..604e7d6632 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentApplication.java @@ -0,0 +1,42 @@ +package com.baeldung.component.inscope; + +import com.baeldung.component.outsidescope.OutsideScopeBeanExample; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@SpringBootApplication +@ComponentScan({"com.baeldung.component.inscope", "com.baeldung.component.scannedscope"}) +@Configuration +public class ComponentApplication { + + @Value( "${ambiguous-bean}" ) + public String ambiguousBean; + + public static void main(String[] args) { + SpringApplication.run( ComponentApplication.class, args ); + } + + @Bean + public BeanExample beanExample() { + return new BeanExample(); + } + + @Bean + public OutsideScopeBeanExample outsideScopeBeanExample() { + return new OutsideScopeBeanExample(); + } + + @Bean + public AmbiguousBean ambiguousBean() { + if (ambiguousBean.equals("A")) { + return new BeanImplA(); + } + else { + return new BeanImplB(); + } + } +} \ No newline at end of file diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentExample.java new file mode 100644 index 0000000000..66d3e20d34 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/ComponentExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.inscope; + +import org.springframework.stereotype.Component; + +@Component +public class ComponentExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/ControllerExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/ControllerExample.java new file mode 100644 index 0000000000..773af2a113 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/ControllerExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.inscope; + +import org.springframework.stereotype.Controller; + +@Controller +public class ControllerExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponent.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponent.java new file mode 100644 index 0000000000..795a1b23fa --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponent.java @@ -0,0 +1,14 @@ +package com.baeldung.component.inscope; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomComponent { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponentExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponentExample.java new file mode 100644 index 0000000000..8009f8ec20 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/CustomComponentExample.java @@ -0,0 +1,5 @@ +package com.baeldung.component.inscope; + +@CustomComponent +public class CustomComponentExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/RepositoryExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/RepositoryExample.java new file mode 100644 index 0000000000..9297992904 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/RepositoryExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.inscope; + +import org.springframework.stereotype.Repository; + +@Repository +public class RepositoryExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/inscope/ServiceExample.java b/spring-core-5/src/main/java/com/baeldung/component/inscope/ServiceExample.java new file mode 100644 index 0000000000..e2db60504a --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/inscope/ServiceExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.inscope; + +import org.springframework.stereotype.Service; + +@Service +public class ServiceExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeBeanExample.java b/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeBeanExample.java new file mode 100644 index 0000000000..079c012862 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeBeanExample.java @@ -0,0 +1,4 @@ +package com.baeldung.component.outsidescope; + +public class OutsideScopeBeanExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeExample.java b/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeExample.java new file mode 100644 index 0000000000..5c7fff959d --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/outsidescope/OutsideScopeExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.outsidescope; + +import org.springframework.stereotype.Component; + +@Component +public class OutsideScopeExample { +} diff --git a/spring-core-5/src/main/java/com/baeldung/component/scannedscope/ScannedScopeExample.java b/spring-core-5/src/main/java/com/baeldung/component/scannedscope/ScannedScopeExample.java new file mode 100644 index 0000000000..59873634d6 --- /dev/null +++ b/spring-core-5/src/main/java/com/baeldung/component/scannedscope/ScannedScopeExample.java @@ -0,0 +1,7 @@ +package com.baeldung.component.scannedscope; + +import org.springframework.stereotype.Component; + +@Component +public class ScannedScopeExample { +} diff --git a/spring-core-5/src/main/resources/application.yml b/spring-core-5/src/main/resources/application.yml new file mode 100644 index 0000000000..5c09fdb8b0 --- /dev/null +++ b/spring-core-5/src/main/resources/application.yml @@ -0,0 +1 @@ +ambiguous-bean: 'A' \ No newline at end of file diff --git a/spring-core-5/src/test/java/com/baeldung/component/inscope/ComponentUnitTest.java b/spring-core-5/src/test/java/com/baeldung/component/inscope/ComponentUnitTest.java new file mode 100644 index 0000000000..09ce604d40 --- /dev/null +++ b/spring-core-5/src/test/java/com/baeldung/component/inscope/ComponentUnitTest.java @@ -0,0 +1,54 @@ +package com.baeldung.component.inscope; + +import com.baeldung.component.outsidescope.OutsideScopeBeanExample; +import com.baeldung.component.outsidescope.OutsideScopeExample; +import com.baeldung.component.scannedscope.ScannedScopeExample; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ExtendWith(SpringExtension.class) +public class ComponentUnitTest { + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void givenInScopeComponents_whenSearchingInApplicationContext_thenFindThem() { + assertNotNull(applicationContext.getBean(ControllerExample.class)); + assertNotNull(applicationContext.getBean(ServiceExample.class)); + assertNotNull(applicationContext.getBean(RepositoryExample.class)); + assertNotNull(applicationContext.getBean(ComponentExample.class)); + assertNotNull(applicationContext.getBean(CustomComponentExample.class)); + } + + @Test + public void givenOutsideScopeComponent_whenSearchingInApplicationContext_thenFail() { + assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(OutsideScopeExample.class)); + } + + @Test + public void givenScannedScopeComponent_whenSearchingInApplicationContext_thenFindIt() { + assertNotNull(applicationContext.getBean(ScannedScopeExample.class)); + } + + @Test + public void givenBeanComponents_whenSearchingInApplicationContext_thenFindThem() { + assertNotNull(applicationContext.getBean(BeanExample.class)); + assertNotNull(applicationContext.getBean(OutsideScopeBeanExample.class)); + } + + @Test + public void givenAmbiguousBeanSetToB_whenSearchingInApplicationContext_thenFindImplB() { + AmbiguousBean ambiguousBean = applicationContext.getBean(AmbiguousBean.class); + assertNotNull(ambiguousBean); + assertTrue(ambiguousBean instanceof BeanImplB); + } +} \ No newline at end of file diff --git a/spring-core-5/src/test/resources/application.yml b/spring-core-5/src/test/resources/application.yml new file mode 100644 index 0000000000..da23e59c24 --- /dev/null +++ b/spring-core-5/src/test/resources/application.yml @@ -0,0 +1 @@ +ambiguous-bean: 'B' \ No newline at end of file diff --git a/spring-data-rest/pom.xml b/spring-data-rest/pom.xml index dd96182264..bfbd66a280 100644 --- a/spring-data-rest/pom.xml +++ b/spring-data-rest/pom.xml @@ -35,7 +35,7 @@ org.springframework.data - spring-data-rest-hal-browser + spring-data-rest-hal-explorer org.springframework.boot @@ -99,7 +99,6 @@ com.baeldung.books.SpringDataRestApplication 1.0 - 2.3.3.RELEASE \ No newline at end of file diff --git a/spring-data-rest/src/main/java/com/baeldung/halbrowser/config/RestConfig.java b/spring-data-rest/src/main/java/com/baeldung/halbrowser/config/RestConfig.java index 73f7e0f26a..a322bf0027 100644 --- a/spring-data-rest/src/main/java/com/baeldung/halbrowser/config/RestConfig.java +++ b/spring-data-rest/src/main/java/com/baeldung/halbrowser/config/RestConfig.java @@ -4,11 +4,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; -import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter; +import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.validation.Validator; @Configuration -public class RestConfig extends RepositoryRestConfigurerAdapter { +public class RestConfig implements RepositoryRestConfigurer { //access to global validator @Autowired diff --git a/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/DynamicSchedulingConfig.java b/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/DynamicSchedulingConfig.java new file mode 100644 index 0000000000..b29f9ab0ce --- /dev/null +++ b/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/DynamicSchedulingConfig.java @@ -0,0 +1,45 @@ +package com.baeldung.scheduling.dynamic; + +import java.time.Instant; +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +@Configuration +@ComponentScan("com.baeldung.scheduling.dynamic") +@EnableScheduling +public class DynamicSchedulingConfig implements SchedulingConfigurer { + + @Autowired + private TickService tickService; + + @Bean + public Executor taskExecutor() { + return Executors.newSingleThreadScheduledExecutor(); + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + taskRegistrar.addTriggerTask( + () -> tickService.tick(), + context -> { + Optional lastCompletionTime = + Optional.ofNullable(context.lastCompletionTime()); + Instant nextExecutionTime = + lastCompletionTime.orElseGet(Date::new).toInstant() + .plusMillis(tickService.getDelay()); + return Date.from(nextExecutionTime); + } + ); + } + +} diff --git a/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/TickService.java b/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/TickService.java new file mode 100644 index 0000000000..5da56f33a9 --- /dev/null +++ b/spring-scheduling/src/main/java/com/baeldung/scheduling/dynamic/TickService.java @@ -0,0 +1,22 @@ +package com.baeldung.scheduling.dynamic; + +import org.springframework.stereotype.Service; + +@Service +public class TickService { + + private long delay = 0; + + public long getDelay() { + this.delay += 1000; + System.out.println("delaying " + this.delay + " milliseconds..."); + return this.delay; + } + + public void tick() { + final long now = System.currentTimeMillis() / 1000; + System.out + .println("schedule tasks with dynamic delay - " + now); + } + +} diff --git a/spring-scheduling/src/test/java/com/baeldung/scheduling/DynamicSchedulingIntegrationTest.java b/spring-scheduling/src/test/java/com/baeldung/scheduling/DynamicSchedulingIntegrationTest.java new file mode 100644 index 0000000000..2ff9cfaefa --- /dev/null +++ b/spring-scheduling/src/test/java/com/baeldung/scheduling/DynamicSchedulingIntegrationTest.java @@ -0,0 +1,19 @@ +package com.baeldung.scheduling; + +import com.baeldung.scheduling.dynamic.DynamicSchedulingConfig; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + DynamicSchedulingConfig.class}, loader = AnnotationConfigContextLoader.class) +public class DynamicSchedulingIntegrationTest { + + @Test + public void testTickServiceTick() throws InterruptedException { + Thread.sleep(6000); + } +} diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 99dea4bc67..096ffb9c3f 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -35,6 +35,7 @@ spring-security-legacy-oidc spring-security-oidc spring-security-okta + spring-security-saml spring-security-web-react spring-security-web-rest spring-security-web-rest-basic-auth diff --git a/spring-security-modules/spring-5-security/README.md b/spring-security-modules/spring-5-security/README.md index 1917d347fb..bad99c22d4 100644 --- a/spring-security-modules/spring-5-security/README.md +++ b/spring-security-modules/spring-5-security/README.md @@ -11,3 +11,4 @@ This module contains articles about Spring Security 5 - [Guide to the AuthenticationManagerResolver in Spring Security](https://www.baeldung.com/spring-security-authenticationmanagerresolver) - [Manual Logout With Spring Security](https://www.baeldung.com/spring-security-manual-logout) - [How to Disable Spring Security Logout Redirects](https://www.baeldung.com/spring-security-disable-logout-redirects) +- [Prevent Cross-Site Scripting (XSS) in a Spring Application](https://www.baeldung.com/spring-prevent-xss) diff --git a/spring-security-modules/spring-5-security/pom.xml b/spring-security-modules/spring-5-security/pom.xml index 09de91491c..d009115c92 100644 --- a/spring-security-modules/spring-5-security/pom.xml +++ b/spring-security-modules/spring-5-security/pom.xml @@ -46,6 +46,21 @@ spring-security-test test + + org.owasp.esapi + esapi + ${esapi.version} + + + org.jsoup + jsoup + 1.13.1 + + + commons-io + commons-io + 2.8.0 + diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Application.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Application.java new file mode 100644 index 0000000000..b463a7adc3 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Application.java @@ -0,0 +1,14 @@ +package com.baeldung.xss; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication +@EnableWebSecurity +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Person.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Person.java new file mode 100644 index 0000000000..1e7c02bae8 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/Person.java @@ -0,0 +1,36 @@ +package com.baeldung.xss; + +public class Person { + private String firstName; + private String lastName; + private int age; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person {" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + '}'; + } +} diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/PersonController.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/PersonController.java new file mode 100644 index 0000000000..8486e04e48 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/PersonController.java @@ -0,0 +1,31 @@ +package com.baeldung.xss; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.Map; + +@RestController +@RequestMapping("/personService") +public class PersonController { + + @PostMapping(value = "/person") + private ResponseEntity savePerson(@RequestHeader Map headers, + @RequestParam String param, @RequestBody Person body) { + ObjectNode response = JsonNodeFactory.instance.objectNode(); + headers.forEach((key, value) -> response.put(key, value)); + response.put("firstName", body.getFirstName()); + response.put("lastName", body.getLastName()); + response.put("age", body.getAge()); + response.put("param", param); + return new ResponseEntity(response.toString(), HttpStatus.OK); + } +} \ No newline at end of file diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/SecurityConf.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/SecurityConf.java new file mode 100644 index 0000000000..25d8026e4a --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/SecurityConf.java @@ -0,0 +1,25 @@ +package com.baeldung.xss; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConf extends WebSecurityConfigurerAdapter { + + @Override + public void configure(WebSecurity web) { + // Ignoring here is only for this example. Normally people would apply their own authentication/authorization policies + web.ignoring().antMatchers("/**"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .headers() + .xssProtection() + .and() + .contentSecurityPolicy("script-src 'self'"); + } +} diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSFilter.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSFilter.java new file mode 100644 index 0000000000..431ed4d120 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSFilter.java @@ -0,0 +1,44 @@ +package com.baeldung.xss; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.ServletException; +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class XSSFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request); + + String body = IOUtils.toString(wrappedRequest.getReader()); + if (!StringUtils.isBlank(body)) { + body = XSSUtils.stripXSS(body); + wrappedRequest.resetInputStream(body.getBytes()); + } + + chain.doFilter(wrappedRequest, response); + } + +} \ No newline at end of file diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSRequestWrapper.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSRequestWrapper.java new file mode 100644 index 0000000000..8fe4e20b5c --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSRequestWrapper.java @@ -0,0 +1,123 @@ +package com.baeldung.xss; + +import org.apache.commons.codec.Charsets; +import org.apache.commons.io.IOUtils; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import static com.baeldung.xss.XSSUtils.stripXSS; + + +public class XSSRequestWrapper extends HttpServletRequestWrapper { + + private byte[] rawData; + private HttpServletRequest request; + private ResettableServletInputStream servletStream; + + public XSSRequestWrapper(HttpServletRequest request) { + super(request); + this.request = request; + this.servletStream = new ResettableServletInputStream(); + } + + public void resetInputStream(byte[] newRawData) { + rawData = newRawData; + servletStream.stream = new ByteArrayInputStream(newRawData); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + if (rawData == null) { + rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.UTF_8); + servletStream.stream = new ByteArrayInputStream(rawData); + } + return servletStream; + } + + @Override + public BufferedReader getReader() throws IOException { + if (rawData == null) { + rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.UTF_8); + servletStream.stream = new ByteArrayInputStream(rawData); + } + return new BufferedReader(new InputStreamReader(servletStream)); + } + + private class ResettableServletInputStream extends ServletInputStream { + + private InputStream stream; + + @Override + public int read() throws IOException { + return stream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + } + + @Override + public String[] getParameterValues(String parameter) { + String[] values = super.getParameterValues(parameter); + if (values == null) { + return null; + } + int count = values.length; + String[] encodedValues = new String[count]; + for (int i = 0; i < count; i++) { + encodedValues[i] = stripXSS(values[i]); + } + return encodedValues; + } + + @Override + public String getParameter(String parameter) { + String value = super.getParameter(parameter); + return stripXSS(value); + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + return stripXSS(value); + } + + @Override + public Enumeration getHeaders(String name) { + List result = new ArrayList<>(); + Enumeration headers = super.getHeaders(name); + while (headers.hasMoreElements()) { + String header = headers.nextElement(); + String[] tokens = header.split(","); + for (String token : tokens) { + result.add(stripXSS(token)); + } + } + return Collections.enumeration(result); + } + +} diff --git a/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSUtils.java b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSUtils.java new file mode 100644 index 0000000000..51bcba8115 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/java/com/baeldung/xss/XSSUtils.java @@ -0,0 +1,19 @@ +package com.baeldung.xss; + +import org.jsoup.Jsoup; +import org.jsoup.safety.Whitelist; +import org.owasp.esapi.ESAPI; + +public class XSSUtils { + + public static String stripXSS(String value) { + if (value == null) { + return null; + } + value = ESAPI.encoder() + .canonicalize(value) + .replaceAll("\0", ""); + return Jsoup.clean(value, Whitelist.none()); + } + +} diff --git a/spring-security-modules/spring-5-security/src/main/resources/ESAPI.properties b/spring-security-modules/spring-5-security/src/main/resources/ESAPI.properties new file mode 100644 index 0000000000..a2746a4dbc --- /dev/null +++ b/spring-security-modules/spring-5-security/src/main/resources/ESAPI.properties @@ -0,0 +1,545 @@ +# +# OWASP Enterprise Security API (ESAPI) Properties file -- PRODUCTION Version +# +# This file is part of the Open Web Application Security Project (OWASP) +# Enterprise Security API (ESAPI) project. For details, please see +# https://owasp.org/www-project-enterprise-security-api/ +# +# Copyright (c) 2008,2009 - The OWASP Foundation +# +# DISCUSS: This may cause a major backwards compatibility issue, etc. but +# from a name space perspective, we probably should have prefaced +# all the property names with ESAPI or at least OWASP. Otherwise +# there could be problems is someone loads this properties file into +# the System properties. We could also put this file into the +# esapi.jar file (perhaps as a ResourceBundle) and then allow an external +# ESAPI properties be defined that would overwrite these defaults. +# That keeps the application's properties relatively simple as usually +# they will only want to override a few properties. If looks like we +# already support multiple override levels of this in the +# DefaultSecurityConfiguration class, but I'm suggesting placing the +# defaults in the esapi.jar itself. That way, if the jar is signed, +# we could detect if those properties had been tampered with. (The +# code to check the jar signatures is pretty simple... maybe 70-90 LOC, +# but off course there is an execution penalty (similar to the way +# that the separate sunjce.jar used to be when a class from it was +# first loaded). Thoughts? +############################################################################### +# +# WARNING: Operating system protection should be used to lock down the .esapi +# resources directory and all the files inside and all the directories all the +# way up to the root directory of the file system. Note that if you are using +# file-based implementations, that some files may need to be read-write as they +# get updated dynamically. +# +#=========================================================================== +# ESAPI Configuration +# +# If true, then print all the ESAPI properties set here when they are loaded. +# If false, they are not printed. Useful to reduce output when running JUnit tests. +# If you need to troubleshoot a properties related problem, turning this on may help. +# This is 'false' in the src/test/resources/.esapi version. It is 'true' by +# default for reasons of backward compatibility with earlier ESAPI versions. +ESAPI.printProperties=true + +# ESAPI is designed to be easily extensible. You can use the reference implementation +# or implement your own providers to take advantage of your enterprise's security +# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like: +# +# String ciphertext = +# ESAPI.encryptor().encrypt("Secret message"); // Deprecated in 2.0 +# CipherText cipherText = +# ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred +# +# Below you can specify the classname for the provider that you wish to use in your +# application. The only requirement is that it implement the appropriate ESAPI interface. +# This allows you to switch security implementations in the future without rewriting the +# entire application. +# +# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory +ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController +# FileBasedAuthenticator requires users.txt file in .esapi directory +ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator +ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder +ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor + +ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor +ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities +ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector +# Log4JFactory Requires log4j.xml or log4j.properties in classpath - http://www.laliluna.de/log4j-tutorial.html +# Note that this is now considered deprecated! +ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory +#ESAPI.Logger=org.owasp.esapi.logging.log4j.Log4JLogFactory +#ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory +# To use the new SLF4J logger in ESAPI (see GitHub issue #129), set +# ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory +# and do whatever other normal SLF4J configuration that you normally would do for your application. +ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer +ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator + +#=========================================================================== +# ESAPI Authenticator +# +Authenticator.AllowedLoginAttempts=3 +Authenticator.MaxOldPasswordHashes=13 +Authenticator.UsernameParameterName=username +Authenticator.PasswordParameterName=password +# RememberTokenDuration (in days) +Authenticator.RememberTokenDuration=14 +# Session Timeouts (in minutes) +Authenticator.IdleTimeoutDuration=20 +Authenticator.AbsoluteTimeoutDuration=120 + +#=========================================================================== +# ESAPI Encoder +# +# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks. +# Failure to canonicalize input is a very common mistake when implementing validation schemes. +# Canonicalization is automatic when using the ESAPI Validator, but you can also use the +# following code to canonicalize data. +# +# ESAPI.Encoder().canonicalize( "%22hello world"" ); +# +# Multiple encoding is when a single encoding format is applied multiple times. Allowing +# multiple encoding is strongly discouraged. +Encoder.AllowMultipleEncoding=false + +# Mixed encoding is when multiple different encoding formats are applied, or when +# multiple formats are nested. Allowing multiple encoding is strongly discouraged. +Encoder.AllowMixedEncoding=false + +# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs +# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or +# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important. +Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec + + +#=========================================================================== +# ESAPI Encryption +# +# The ESAPI Encryptor provides basic cryptographic functions with a simplified API. +# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor +# There is not currently any support for key rotation, so be careful when changing your key and salt as it +# will invalidate all signed, encrypted, and hashed data. +# +# WARNING: Not all combinations of algorithms and key lengths are supported. +# If you choose to use a key length greater than 128, you MUST download the +# unlimited strength policy files and install in the lib directory of your JRE/JDK. +# See http://java.sun.com/javase/downloads/index.jsp for more information. +# +# ***** IMPORTANT: Do NOT forget to replace these with your own values! ***** +# To calculate these values, you can run: +# java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor +# +#Encryptor.MasterKey= +#Encryptor.MasterSalt= + +# Provides the default JCE provider that ESAPI will "prefer" for its symmetric +# encryption and hashing. (That is it will look to this provider first, but it +# will defer to other providers if the requested algorithm is not implemented +# by this provider.) If left unset, ESAPI will just use your Java VM's current +# preferred JCE provider, which is generally set in the file +# "$JAVA_HOME/jre/lib/security/java.security". +# +# The main intent of this is to allow ESAPI symmetric encryption to be +# used with a FIPS 140-2 compliant crypto-module. For details, see the section +# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in +# the ESAPI 2.0 Symmetric Encryption User Guide, at: +# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html +# However, this property also allows you to easily use an alternate JCE provider +# such as "Bouncy Castle" without having to make changes to "java.security". +# See Javadoc for SecurityProviderLoader for further details. If you wish to use +# a provider that is not known to SecurityProviderLoader, you may specify the +# fully-qualified class name of the JCE provider class that implements +# java.security.Provider. If the name contains a '.', this is interpreted as +# a fully-qualified class name that implements java.security.Provider. +# +# NOTE: Setting this property has the side-effect of changing it in your application +# as well, so if you are using JCE in your application directly rather than +# through ESAPI (you wouldn't do that, would you? ;-), it will change the +# preferred JCE provider there as well. +# +# Default: Keeps the JCE provider set to whatever JVM sets it to. +Encryptor.PreferredJCEProvider= + +# AES is the most widely used and strongest encryption algorithm. This +# should agree with your Encryptor.CipherTransformation property. +# Warning: This property does not control the default reference implementation for +# ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped +# in the future. +# @deprecated +Encryptor.EncryptionAlgorithm=AES +# For ESAPI Java 2.0 - New encrypt / decrypt methods use this. +Encryptor.CipherTransformation=AES/CBC/PKCS5Padding + +# Applies to ESAPI 2.0 and later only! +# Comma-separated list of cipher modes that provide *BOTH* +# confidentiality *AND* message authenticity. (NIST refers to such cipher +# modes as "combined modes" so that's what we shall call them.) If any of these +# cipher modes are used then no MAC is calculated and stored +# in the CipherText upon encryption. Likewise, if one of these +# cipher modes is used with decryption, no attempt will be made +# to validate the MAC contained in the CipherText object regardless +# of whether it contains one or not. Since the expectation is that +# these cipher modes support support message authenticity already, +# injecting a MAC in the CipherText object would be at best redundant. +# +# Note that as of JDK 1.5, the SunJCE provider does not support *any* +# of these cipher modes. Of these listed, only GCM and CCM are currently +# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports +# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other +# padding modes. +Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC + +# Applies to ESAPI 2.0 and later only! +# Additional cipher modes allowed for ESAPI 2.0 encryption. These +# cipher modes are in _addition_ to those specified by the property +# 'Encryptor.cipher_modes.combined_modes'. +# Note: We will add support for streaming modes like CFB & OFB once +# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod' +# (probably in ESAPI 2.1). +# DISCUSS: Better name? +Encryptor.cipher_modes.additional_allowed=CBC + +# Default key size to use for cipher specified by Encryptor.EncryptionAlgorithm. +# Note that this MUST be a valid key size for the algorithm being used +# (as specified by Encryptor.EncryptionAlgorithm). So for example, if AES is used, +# it must be 128, 192, or 256. If DESede is chosen, then it must be either 112 or 168. +# +# Note that 128-bits is almost always sufficient and for AES it appears to be more +# somewhat more resistant to related key attacks than is 256-bit AES.) +# +# Defaults to 128-bits if left blank. +# +# NOTE: If you use a key size > 128-bits, then you MUST have the JCE Unlimited +# Strength Jurisdiction Policy files installed!!! +# +Encryptor.EncryptionKeyLength=128 + +# This is the _minimum_ key size (in bits) that we allow with ANY symmetric +# cipher for doing encryption. (There is no minimum for decryption.) +# +# Generally, if you only use one algorithm, this should be set the same as +# the Encryptor.EncryptionKeyLength property. +Encryptor.MinEncryptionKeyLength=128 + +# Because 2.x uses CBC mode by default, it requires an initialization vector (IV). +# (All cipher modes except ECB require an IV.) There are two choices: we can either +# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While +# the IV does not need to be hidden from adversaries, it is important that the +# adversary not be allowed to choose it. Also, random IVs are generally much more +# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes +# such as CFB and OFB use a different IV for each encryption with a given key so +# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random +# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and +# uncomment the Encryptor.fixedIV. +# +# Valid values: random|fixed|specified 'specified' not yet implemented; planned for 2.3 +# 'fixed' is deprecated as of 2.2 +# and will be removed in 2.3. +Encryptor.ChooseIVMethod=random + + +# If you choose to use a fixed IV, then you must place a fixed IV here that +# is known to all others who are sharing your secret key. The format should +# be a hex string that is the same length as the cipher block size for the +# cipher algorithm that you are using. The following is an *example* for AES +# from an AES test vector for AES-128/CBC as described in: +# NIST Special Publication 800-38A (2001 Edition) +# "Recommendation for Block Cipher Modes of Operation". +# (Note that the block size for AES is 16 bytes == 128 bits.) +# +# @Deprecated -- fixed IVs are deprecated as of the 2.2 release and support +# will be removed in the next release (tentatively, 2.3). +# If you MUST use this, at least replace this IV with one +# that your legacy application was using. +Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f + +# Whether or not CipherText should use a message authentication code (MAC) with it. +# This prevents an adversary from altering the IV as well as allowing a more +# fool-proof way of determining the decryption failed because of an incorrect +# key being supplied. This refers to the "separate" MAC calculated and stored +# in CipherText, not part of any MAC that is calculated as a result of a +# "combined mode" cipher mode. +# +# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also +# set this property to false. That is because ESAPI takes the master key and +# derives 2 keys from it--a key for the MAC and a key for encryption--and +# because ESAPI is not itself FIPS 140-2 verified such intermediary aterations +# to keys from FIPS approved sources would have the effect of making your FIPS +# approved key generation and thus your FIPS approved JCE provider unapproved! +# More details in +# documentation/esapi4java-core-2.0-readme-crypto-changes.html +# documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html +# You have been warned. +Encryptor.CipherText.useMAC=true + +# Whether or not the PlainText object may be overwritten and then marked +# eligible for garbage collection. If not set, this is still treated as 'true'. +Encryptor.PlainText.overwrite=true + +# Do not use DES except in a legacy situations. 56-bit is way too small key size. +#Encryptor.EncryptionKeyLength=56 +#Encryptor.MinEncryptionKeyLength=56 +#Encryptor.EncryptionAlgorithm=DES + +# TripleDES is considered strong enough for most purposes. +# Note: There is also a 112-bit version of DESede. Using the 168-bit version +# requires downloading the special jurisdiction policy from Sun. +#Encryptor.EncryptionKeyLength=168 +#Encryptor.MinEncryptionKeyLength=112 +#Encryptor.EncryptionAlgorithm=DESede + +Encryptor.HashAlgorithm=SHA-512 +Encryptor.HashIterations=1024 +Encryptor.DigitalSignatureAlgorithm=SHA1withDSA +Encryptor.DigitalSignatureKeyLength=1024 +Encryptor.RandomAlgorithm=SHA1PRNG +Encryptor.CharacterEncoding=UTF-8 + +# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function +# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and +# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for +# the MAC, mostly to keep the overall size at a minimum.) +# +# Currently supported choices for JDK 1.5 and 1.6 are: +# HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and +# HmacSHA512 (512 bits). +# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though +# the JDKs support it. See the ESAPI 2.0 Symmetric Encryption User Guide +# further details. +Encryptor.KDF.PRF=HmacSHA256 +#=========================================================================== +# ESAPI HttpUtilties +# +# The HttpUtilities provide basic protections to HTTP requests and responses. Primarily these methods +# protect against malicious data from attackers, such as unprintable characters, escaped characters, +# and other simple attacks. The HttpUtilities also provides utility methods for dealing with cookies, +# headers, and CSRF tokens. +# +# Default file upload location (remember to escape backslashes with \\) +HttpUtilities.UploadDir=C:\\ESAPI\\testUpload +HttpUtilities.UploadTempDir=C:\\temp +# Force flags on cookies, if you use HttpUtilities to set cookies +HttpUtilities.ForceHttpOnlySession=false +HttpUtilities.ForceSecureSession=false +HttpUtilities.ForceHttpOnlyCookies=true +HttpUtilities.ForceSecureCookies=true +# Maximum size of HTTP header key--the validator regex may have additional values. +HttpUtilities.MaxHeaderNameSize=256 +# Maximum size of HTTP header value--the validator regex may have additional values. +HttpUtilities.MaxHeaderValueSize=4096 +# Maximum size of JSESSIONID for the application--the validator regex may have additional values. +HttpUtilities.HTTPJSESSIONIDLENGTH=50 +# Maximum length of a URL (see https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers) +HttpUtilities.URILENGTH=2000 +# Maximum length of a redirect +HttpUtilities.maxRedirectLength=512 +# Maximum length for an http scheme +HttpUtilities.HTTPSCHEMELENGTH=10 +# Maximum length for an http host +HttpUtilities.HTTPHOSTLENGTH=100 +# Maximum length for an http path +HttpUtilities.HTTPPATHLENGTH=150 +#Maximum length for a context path +HttpUtilities.contextPathLength=150 +#Maximum length for an httpServletPath +HttpUtilities.HTTPSERVLETPATHLENGTH=100 +#Maximum length for an http query parameter name +HttpUtilities.httpQueryParamNameLength=100 +#Maximum length for an http query parameter -- old default was 2000, but that's the max length for a URL... +HttpUtilities.httpQueryParamValueLength=500 +# File upload configuration +HttpUtilities.ApprovedUploadExtensions=.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.rtf,.txt,.jpg,.png +HttpUtilities.MaxUploadFileBytes=500000000 +# Using UTF-8 throughout your stack is highly recommended. That includes your database driver, +# container, and any other technologies you may be using. Failure to do this may expose you +# to Unicode transcoding injection attacks. Use of UTF-8 does not hinder internationalization. +HttpUtilities.ResponseContentType=text/html; charset=UTF-8 +# This is the name of the cookie used to represent the HTTP session +# Typically this will be the default "JSESSIONID" +HttpUtilities.HttpSessionIdName=JSESSIONID +#Sets whether or not we will overwrite http status codes to 200. +HttpUtilities.OverwriteStatusCodes=true +#Sets the application's base character encoding. This is forked from the Java Encryptor property. +HttpUtilities.CharacterEncoding=UTF-8 + +#=========================================================================== +# ESAPI Executor +# CHECKME - This should be made OS independent. Don't use unsafe defaults. +# # Examples only -- do NOT blindly copy! +# For Windows: +# Executor.WorkingDirectory=C:\\Windows\\Temp +# Executor.ApprovedExecutables=C:\\Windows\\System32\\cmd.exe,C:\\Windows\\System32\\runas.exe +# For *nux, MacOS: +# Executor.WorkingDirectory=/tmp +# Executor.ApprovedExecutables=/bin/bash +Executor.WorkingDirectory= +Executor.ApprovedExecutables= + + +#=========================================================================== +# ESAPI Logging +# Set the application name if these logs are combined with other applications +Logger.ApplicationName=ExampleApplication +# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true +Logger.LogEncodingRequired=false +# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments. +Logger.LogApplicationName=true +# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments. +Logger.LogServerIP=true +# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you +# want to place it in a specific directory. +Logger.LogFileName=ESAPI_logging_file +# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000) +Logger.MaxLogFileSize=10000000 +# Determines whether ESAPI should log the user info. +Logger.UserInfo=true +# Determines whether ESAPI should log the session id and client IP. +Logger.ClientInfo=true + +#=========================================================================== +# ESAPI Intrusion Detection +# +# Each event has a base to which .count, .interval, and .action are added +# The IntrusionException will fire if we receive "count" events within "interval" seconds +# The IntrusionDetector is configurable to take the following actions: log, logout, and disable +# (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable +# +# Custom Events +# Names must start with "event." as the base +# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here +# You can also disable intrusion detection completely by changing +# the following parameter to true +# +IntrusionDetector.Disable=false +# +IntrusionDetector.event.test.count=2 +IntrusionDetector.event.test.interval=10 +IntrusionDetector.event.test.actions=disable,log + +# Exception Events +# All EnterpriseSecurityExceptions are registered automatically +# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException +# Use the fully qualified classname of the exception as the base + +# any intrusion is an attack +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1 +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1 +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout + +# for test purposes +# CHECKME: Shouldn't there be something in the property name itself that designates +# that these are for testing??? +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10 +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5 +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout + +# rapid validation errors indicate scans or attacks in progress +# org.owasp.esapi.errors.ValidationException.count=10 +# org.owasp.esapi.errors.ValidationException.interval=10 +# org.owasp.esapi.errors.ValidationException.actions=log,logout + +# sessions jumping between hosts indicates session hijacking +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2 +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10 +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout + + +#=========================================================================== +# ESAPI Validation +# +# The ESAPI Validator works on regular expressions with defined names. You can define names +# either here, or you may define application specific patterns in a separate file defined below. +# This allows enterprises to specify both organizational standards as well as application specific +# validation rules. +# +# Use '\p{L}' (without the quotes) within the character class to match +# any Unicode LETTER. You can also use a range, like: \u00C0-\u017F +# You can also use any of the regex flags as documented at +# https://docs.oracle.com/javase/tutorial/essential/regex/pattern.html, e.g. (?u) +# +Validator.ConfigurationFile=validation.properties + +# Validators used by ESAPI +Validator.AccountName=^[a-zA-Z0-9]{3,20}$ +Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$ +Validator.RoleName=^[a-z]{1,20}$ + +#the word TEST below should be changed to your application +#name - only relative URL's are supported +Validator.Redirect=^\\/test.*$ + +# Global HTTP Validation Rules +# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=] +Validator.HTTPScheme=^(http|https)$ +Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$ +Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$ +Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$ +# Note that headerName and Value length is also configured in the HTTPUtilities section +Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,256}$ +Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ +Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ +Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ +Validator.HTTPURL=^.*$ +Validator.HTTPJSESSIONID=^[A-Z0-9]{10,32}$ + + +# Contributed by Fraenku@gmx.ch +# Github Issue 126 https://github.com/ESAPI/esapi-java-legacy/issues/126 +Validator.HTTPParameterName=^[a-zA-Z0-9_\\-]{1,32}$ +Validator.HTTPParameterValue=^[\\p{L}\\p{N}.\\-/+=_ !$*?@]{0,1000}$ +Validator.HTTPContextPath=^/[a-zA-Z0-9.\\-_]*$ +Validator.HTTPQueryString=^([a-zA-Z0-9_\\-]{1,32}=[\\p{L}\\p{N}.\\-/+=_ !$*?@%]*&?)*$ +Validator.HTTPURI=^/([a-zA-Z0-9.\\-_]*/?)*$ + + +# Validation of file related input +Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ +Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ + +# Validation of dates. Controls whether or not 'lenient' dates are accepted. +# See DataFormat.setLenient(boolean flag) for further details. +Validator.AcceptLenientDates=false + +# ~~~~~ Important Note ~~~~~ +# This is a workaround to make sure that a commit to address GitHub issue #509 +# doesn't accidentally break someone's production code. So essentially what we +# are doing is to reverting back to the previous possibly buggy (by +# documentation intent at least), but, by now, expected legacy behavior. +# Prior to the code changes for issue #509, if invalid / malicious HTML input was +# observed, AntiSamy would simply attempt to sanitize (cleanse) it and it would +# only be logged. However, the code change made ESAPI comply with its +# documentation, which stated that a ValidationException should be thrown in +# such cases. Unfortunately, changing this behavior--especially when no one is +# 100% certain that the documentation was correct--could break existing code +# using ESAPI so after a lot of debate, issue #521 was created to restore the +# previous behavior, but still allow the documented behavior. (We did this +# because it wasn't really causing an security issues since AntiSamy would clean +# it up anyway and we value backward compatibility as long as it doesn't clearly +# present security vulnerabilities.) +# More defaults about this are written up under GitHub issue #521 and +# the pull request it references. Future major releases of ESAPI (e.g., ESAPI 3.x) +# will not support this previous behavior, but it will remain for ESAPI 2.x. +# Set this to 'throw' if you want the originally intended behavior of throwing +# that was fixed via issue #509. Set to 'clean' if you want want the HTML input +# sanitized instead. +# +# Possible values: +# clean -- Use the legacy behavior where unsafe HTML input is logged and the +# sanitized (i.e., clean) input as determined by AntiSamy and your +# AntiSamy rules is returned. This is the default behavior if this +# new property is not found. +# throw -- The new, presumably correct and originally intended behavior where +# a ValidationException is thrown when unsafe HTML input is +# encountered. +# +#Validator.HtmlValidationAction=clean +Validator.HtmlValidationAction=throw + +# With the fix for #310 to enable loading antisamy-esapi.xml from the classpath +# also an enhancement was made to be able to use a different filename for the configuration. +# You don't have to configure the filename here, but in that case the code will keep looking for antisamy-esapi.xml. +# This is the default behaviour of ESAPI. +# +#Validator.HtmlValidationConfigurationFile=antisamy-esapi.xml \ No newline at end of file diff --git a/spring-security-modules/spring-5-security/src/test/java/com/baeldung/xss/PersonControllerUnitTest.java b/spring-security-modules/spring-5-security/src/test/java/com/baeldung/xss/PersonControllerUnitTest.java new file mode 100644 index 0000000000..4e278ebf16 --- /dev/null +++ b/spring-security-modules/spring-5-security/src/test/java/com/baeldung/xss/PersonControllerUnitTest.java @@ -0,0 +1,64 @@ +package com.baeldung.xss; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.*; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import java.io.IOException; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class PersonControllerUnitTest { + + @LocalServerPort + int randomServerPort; + + @Test + public void givenRequestIsSuspicious_whenRequestIsPost_thenResponseIsClean() + throws IOException { + // given + String createPersonUrl; + RestTemplate restTemplate; + HttpHeaders headers; + UriComponentsBuilder builder; + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode personJsonObject = JsonNodeFactory.instance.objectNode(); + createPersonUrl = "http://localhost:" + randomServerPort + "/personService/person"; + restTemplate = new RestTemplate(); + headers = new HttpHeaders(); + + // when + personJsonObject.put("id", 1); + personJsonObject.put("firstName", "baeldung "); + personJsonObject.put("lastName", "baeldung click me!"); + + builder = UriComponentsBuilder.fromHttpUrl(createPersonUrl) + .queryParam("param", ""); + headers.add("header_4", "

Your search for 'flowers '"); + HttpEntity request = new HttpEntity<>(personJsonObject.toString(), headers); + + ResponseEntity personResultAsJsonStr = restTemplate.exchange(builder.toUriString(), + HttpMethod.POST, request, String.class); + JsonNode root = objectMapper.readTree(personResultAsJsonStr.getBody()); + + // then + assertThat(root.get("firstName").textValue()).isEqualTo("baeldung "); + assertThat(root.get("lastName").textValue()).isEqualTo("baeldung click me!"); + assertThat(root.get("param").textValue()).isEmpty(); + assertThat(root.get("header_1").textValue()).isEmpty(); + assertThat(root.get("header_2").textValue()).isEmpty(); + assertThat(root.get("header_3").textValue()).isEmpty(); + assertThat(root.get("header_4").textValue()).isEqualTo("Your search for 'flowers '"); + } +} diff --git a/spring-security-modules/spring-security-saml/README.md b/spring-security-modules/spring-security-saml/README.md new file mode 100644 index 0000000000..271b29632e --- /dev/null +++ b/spring-security-modules/spring-security-saml/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [A Guide to SAML with Spring Security](https://www.baeldung.com/spring-security-saml) diff --git a/spring-security-modules/spring-security-saml/pom.xml b/spring-security-modules/spring-security-saml/pom.xml new file mode 100644 index 0000000000..561582045a --- /dev/null +++ b/spring-security-modules/spring-security-saml/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + spring-security-saml + 1.0-SNAPSHOT + spring-security-saml + war + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + Shibboleth + Shibboleth + https://build.shibboleth.net/nexus/content/repositories/releases/ + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.security.extensions + spring-security-saml2-core + ${saml2-core.spring.version} + + + + + spring-security-saml + + + src/main/resources + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + + + + 1.0.10.RELEASE + + diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java new file mode 100644 index 0000000000..39eaa46424 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java @@ -0,0 +1,11 @@ +package com.baeldung.saml; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String... args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java new file mode 100644 index 0000000000..b35a72763d --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java @@ -0,0 +1,28 @@ +package com.baeldung.saml.authentication; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; +import org.springframework.security.saml.SAMLAuthenticationProvider; +import org.springframework.security.saml.SAMLCredential; + +public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider { + + @Override + public Collection getEntitlements(SAMLCredential credential, Object userDetail) { + + if(userDetail instanceof ExpiringUsernameAuthenticationToken) { + List authorities = new ArrayList(); + authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities()); + return authorities; + + } else { + return Collections.emptyList(); + } + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java new file mode 100644 index 0000000000..378db478cf --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java @@ -0,0 +1,226 @@ +package com.baeldung.saml.config; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.util.resource.ResourceException; +import org.opensaml.xml.parse.StaticBasicParserPool; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.security.saml.*; +import org.springframework.security.saml.context.SAMLContextProviderImpl; +import org.springframework.security.saml.key.JKSKeyManager; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.log.SAMLDefaultLogger; +import org.springframework.security.saml.metadata.CachingMetadataManager; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml.processor.*; +import org.springframework.security.saml.util.VelocityFactory; +import org.springframework.security.saml.websso.*; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; + +import com.baeldung.saml.authentication.CustomSAMLAuthenticationProvider; + +@Configuration +public class SamlSecurityConfig { + + @Value("${saml.keystore.location}") + private String samlKeystoreLocation; + + @Value("${saml.keystore.password}") + private String samlKeystorePassword; + + @Value("${saml.keystore.alias}") + private String samlKeystoreAlias; + + @Value("${saml.idp}") + private String defaultIdp; + + @Bean(initMethod = "initialize") + public StaticBasicParserPool parserPool() { + return new StaticBasicParserPool(); + } + + @Bean + public SAMLAuthenticationProvider samlAuthenticationProvider() { + return new CustomSAMLAuthenticationProvider(); + } + + @Bean + public SAMLContextProviderImpl contextProvider() { + return new SAMLContextProviderImpl(); + } + + @Bean + public static SAMLBootstrap samlBootstrap() { + return new SAMLBootstrap(); + } + + @Bean + public SAMLDefaultLogger samlLogger() { + return new SAMLDefaultLogger(); + } + + @Bean + public WebSSOProfileConsumer webSSOprofileConsumer() { + return new WebSSOProfileConsumerImpl(); + } + + @Bean + @Qualifier("hokWebSSOprofileConsumer") + public WebSSOProfileConsumerHoKImpl hokWebSSOProfileConsumer() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfile webSSOprofile() { + return new WebSSOProfileImpl(); + } + + @Bean + public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfileECPImpl ecpProfile() { + return new WebSSOProfileECPImpl(); + } + + @Bean + public SingleLogoutProfile logoutProfile() { + return new SingleLogoutProfileImpl(); + } + + @Bean + public KeyManager keyManager() { + DefaultResourceLoader loader = new DefaultResourceLoader(); + Resource storeFile = loader.getResource(samlKeystoreLocation); + Map passwords = new HashMap<>(); + passwords.put(samlKeystoreAlias, samlKeystorePassword); + return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias); + } + + @Bean + public WebSSOProfileOptions defaultWebSSOProfileOptions() { + WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); + webSSOProfileOptions.setIncludeScoping(false); + return webSSOProfileOptions; + } + + @Bean + public SAMLEntryPoint samlEntryPoint() { + SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint(); + samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions()); + return samlEntryPoint; + } + + @Bean + public ExtendedMetadata extendedMetadata() { + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setIdpDiscoveryEnabled(false); + extendedMetadata.setSignMetadata(false); + return extendedMetadata; + } + + @Bean + @Qualifier("okta") + public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException { + File metadata = null; + try { + metadata = new File("./src/main/resources/saml/metadata/sso.xml"); + } catch (Exception e) { + e.printStackTrace(); + } + FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata); + provider.setParserPool(parserPool()); + return new ExtendedMetadataDelegate(provider, extendedMetadata()); + } + + @Bean + @Qualifier("metadata") + public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException { + List providers = new ArrayList<>(); + providers.add(oktaExtendedMetadataProvider()); + CachingMetadataManager metadataManager = new CachingMetadataManager(providers); + metadataManager.setDefaultIDP(defaultIdp); + return metadataManager; + } + + @Bean + @Qualifier("saml") + public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { + SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler(); + successRedirectHandler.setDefaultTargetUrl("/home"); + return successRedirectHandler; + } + + @Bean + @Qualifier("saml") + public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { + SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); + failureHandler.setUseForward(true); + failureHandler.setDefaultFailureUrl("/error"); + return failureHandler; + } + + @Bean + public SimpleUrlLogoutSuccessHandler successLogoutHandler() { + SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler(); + successLogoutHandler.setDefaultTargetUrl("/"); + return successLogoutHandler; + } + + @Bean + public SecurityContextLogoutHandler logoutHandler() { + SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); + logoutHandler.setInvalidateHttpSession(true); + logoutHandler.setClearAuthentication(true); + return logoutHandler; + } + + @Bean + public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() { + return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler()); + } + + @Bean + public SAMLLogoutFilter samlLogoutFilter() { + return new SAMLLogoutFilter(successLogoutHandler(), + new LogoutHandler[] { logoutHandler() }, + new LogoutHandler[] { logoutHandler() }); + } + + @Bean + public HTTPPostBinding httpPostBinding() { + return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine()); + } + + @Bean + public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() { + return new HTTPRedirectDeflateBinding(parserPool()); + } + + @Bean + public SAMLProcessorImpl processor() { + ArrayList bindings = new ArrayList<>(); + bindings.add(httpRedirectDeflateBinding()); + bindings.add(httpPostBinding()); + return new SAMLProcessorImpl(bindings); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java new file mode 100644 index 0000000000..297c391823 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java @@ -0,0 +1,152 @@ +package com.baeldung.saml.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.saml.*; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.*; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.channel.ChannelProcessingFilter; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${saml.sp}") + private String samlAudience; + + @Autowired + @Qualifier("saml") + private SavedRequestAwareAuthenticationSuccessHandler samlAuthSuccessHandler; + + @Autowired + @Qualifier("saml") + private SimpleUrlAuthenticationFailureHandler samlAuthFailureHandler; + + @Autowired + private SAMLEntryPoint samlEntryPoint; + + @Autowired + private SAMLLogoutFilter samlLogoutFilter; + + @Autowired + private SAMLLogoutProcessingFilter samlLogoutProcessingFilter; + + @Bean + public SAMLDiscovery samlDiscovery() { + SAMLDiscovery idpDiscovery = new SAMLDiscovery(); + return idpDiscovery; + } + + @Autowired + private SAMLAuthenticationProvider samlAuthenticationProvider; + + @Autowired + private ExtendedMetadata extendedMetadata; + + @Autowired + private KeyManager keyManager; + + public MetadataGenerator metadataGenerator() { + MetadataGenerator metadataGenerator = new MetadataGenerator(); + metadataGenerator.setEntityId(samlAudience); + metadataGenerator.setExtendedMetadata(extendedMetadata); + metadataGenerator.setIncludeDiscoveryExtension(false); + metadataGenerator.setKeyManager(keyManager); + return metadataGenerator; + } + + @Bean + public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception { + SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter(); + samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager()); + samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(samlAuthSuccessHandler); + samlWebSSOProcessingFilter.setAuthenticationFailureHandler(samlAuthFailureHandler); + return samlWebSSOProcessingFilter; + } + + @Bean + public FilterChainProxy samlFilter() throws Exception { + List chains = new ArrayList<>(); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), + samlWebSSOProcessingFilter())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), + samlDiscovery())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), + samlEntryPoint)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), + samlLogoutFilter)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), + samlLogoutProcessingFilter)); + return new FilterChainProxy(chains); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public MetadataGeneratorFilter metadataGeneratorFilter() { + return new MetadataGeneratorFilter(metadataGenerator()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf() + .disable(); + + http + .httpBasic() + .authenticationEntryPoint(samlEntryPoint); + + http + .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class) + .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class) + .addFilterBefore(samlFilter(), CsrfFilter.class); + + http + .authorizeRequests() + .antMatchers("/").permitAll() + .anyRequest().authenticated(); + + http + .logout() + .addLogoutHandler((request, response, authentication) -> { + try { + response.sendRedirect("/saml/logout"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(samlAuthenticationProvider); + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java new file mode 100644 index 0000000000..e77933b8f3 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java @@ -0,0 +1,35 @@ +package com.baeldung.saml.controller; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class HomeController { + + @RequestMapping("/") + public String index() { + return "index"; + } + + @GetMapping(value = "/auth") + public String handleSamlAuth() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + return "redirect:/home"; + } else { + return "/"; + } + } + + @RequestMapping("/home") + public String home(Model model) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + model.addAttribute("username", authentication.getPrincipal()); + return "home"; + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/resources/application.properties b/spring-security-modules/spring-security-saml/src/main/resources/application.properties new file mode 100644 index 0000000000..f9d6a5df3c --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/application.properties @@ -0,0 +1,6 @@ +saml.keystore.location=classpath:/saml/samlKeystore.jks +saml.keystore.password= +saml.keystore.alias= + +saml.idp= +saml.sp=http://localhost:8080/saml/metadata \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml new file mode 100644 index 0000000000..2d3258de12 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml @@ -0,0 +1,44 @@ + + + + + + + MIIDpDCCAoygAwIBAgIGAXGiSQ7ZMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05MjY2NjYxHDAaBgkqhkiG9w0BCQEW + DWluZm9Ab2t0YS5jb20wHhcNMjAwNDIyMTQyNjA5WhcNMzAwNDIyMTQyNzA5WjCBkjELMAkGA1UE + BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV + BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTI2NjY2MRwwGgYJ + KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA + g1rQYYqeVx2gl/UUnLJzp5hrm06VOILJB9hIUmNqXgWV3UjzDq/zX0KW8MENjsO7+S8a+LLnYRkb + N5egH9FSt8AHtB1pmfXDtpUQmWe9yJbNxbCISoc6XzCmaRw3HRv9pK5SciIutciz9lvFaHMWAWtP + MmQSKdhMet52tuf6sTy4ODeXjyMnD9q5QOKww1SJ678wjHbGRRhNvCxvTSAH33sa4oNCf2RvP9hp + NiJRcYW9yLZXmZArPQOuAx5PIXfHhK2e4ac39YO4fgO7gwU5TZ+vL7o6iEmd9tk44PrND0ZV5yzZ + +Y33Hiun3fIiZu/nZZGUjm4k4exl8JJpwrVTHQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcfHcL + 2DjTjZGoANF4dPpGXTYdVnL/XzGiLS+3LR/HDrEz/EqsHouF40RnzdZ7Ax7RReKBYCUUqHpSE+LU + ductz2ANguzyseGEn72I4Ym4ytQWnFyTXeW+xI9CoCLGfOUhT1hlKjsu/qNM8qwKFPWkzQp7mDN8 + S9MGhsnbiyeD/lceAEKw16Os73/sX2j7F+43WVCYRDCRB8pRIPfcqYLXUIUSstQlwEvCF7HyeO4+ + jxKHA1tp9Cpmj7/VD9TE3fyvrbVmfjTbKjF7/0wYQNfbHDDko0ratDMAizG5/d3i9wk9KbGCHSxT + ph5nl1pdjKgAYPK0iNDnGCZbGKzXOrqV + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks new file mode 100644 index 0000000000..7f3a5850d9 Binary files /dev/null and b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks differ diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html new file mode 100644 index 0000000000..7223ee43fd --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html @@ -0,0 +1,13 @@ + + + +

Something went wrong
+
+ +

+ An error occurred +

+
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html new file mode 100644 index 0000000000..c66e92c1f0 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html @@ -0,0 +1,13 @@ + + + +Baeldung Spring Security SAML: Home + + +

Welcome!
You are successfully logged in!

+

You are logged as null.

+ + Logout + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html new file mode 100644 index 0000000000..7999c2fded --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html @@ -0,0 +1,10 @@ + + + +Baeldung Spring Security SAML + + +

Welcome to Baeldung Spring Security SAML

+ Login + + \ No newline at end of file diff --git a/spring-security-modules/spring-session/spring-session-jdbc/README.md b/spring-security-modules/spring-session/spring-session-jdbc/README.md index a31ee044e8..6af3f53137 100644 --- a/spring-security-modules/spring-session/spring-session-jdbc/README.md +++ b/spring-security-modules/spring-session/spring-session-jdbc/README.md @@ -3,5 +3,3 @@ This module contains articles about Spring Session with JDBC. ### Relevant Articles: - -- [Spring Session with JDBC](https://www.baeldung.com/spring-session-jdbc) diff --git a/spring-web-modules/spring-mvc-basics-4/README.md b/spring-web-modules/spring-mvc-basics-4/README.md index d0bca4a303..211564a363 100644 --- a/spring-web-modules/spring-mvc-basics-4/README.md +++ b/spring-web-modules/spring-mvc-basics-4/README.md @@ -5,7 +5,7 @@ The "REST With Spring" Classes: https://bit.ly/restwithspring ### Relevant Articles: - [Quick Guide to Spring Controllers](https://www.baeldung.com/spring-controllers) -- [Model, ModelMap, and ModelView in Spring MVC](https://www.baeldung.com/spring-mvc-model-model-map-model-view) +- [Model, ModelMap, and ModelAndView in Spring MVC](https://www.baeldung.com/spring-mvc-model-model-map-model-view) - [Spring Web Contexts](https://www.baeldung.com/spring-web-contexts) - [Spring Optional Path variables](https://www.baeldung.com/spring-optional-path-variables) - [JSON Parameters with Spring MVC](https://www.baeldung.com/spring-mvc-send-json-parameters) diff --git a/spring-web-modules/spring-mvc-java-2/README.md b/spring-web-modules/spring-mvc-java-2/README.md index 41ff7bdf7c..74d8cca312 100644 --- a/spring-web-modules/spring-mvc-java-2/README.md +++ b/spring-web-modules/spring-mvc-java-2/README.md @@ -6,4 +6,4 @@ - [A Quick Guide to Spring MVC Matrix Variables](https://www.baeldung.com/spring-mvc-matrix-variables) - [Converting a Spring MultipartFile to a File](https://www.baeldung.com/spring-multipartfile-to-file) - [Testing a Spring Multipart POST Request](https://www.baeldung.com/spring-multipart-post-request-test) -- [Spring @Pathvariable Annotation](https://www.baeldung.com/spring-pathvariable) +- [Spring @PathVariable Annotation](https://www.baeldung.com/spring-pathvariable) diff --git a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerIntegrationTest.java b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerIntegrationTest.java index 3d34a46791..7b9da5707d 100644 --- a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerIntegrationTest.java +++ b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerIntegrationTest.java @@ -1,17 +1,13 @@ package com.baeldung.web.controller; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; - -import javax.servlet.ServletContext; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import com.baeldung.spring.web.config.WebConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -20,31 +16,34 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import com.baeldung.spring.web.config.WebConfig; +import javax.servlet.ServletContext; -@RunWith(SpringJUnit4ClassRunner.class) +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {WebConfig.class}) @WebAppConfiguration -@ContextConfiguration(classes = { WebConfig.class, WebConfig.class }) public class GreetControllerIntegrationTest { @Autowired - private WebApplicationContext wac; + private WebApplicationContext webApplicationContext; private MockMvc mockMvc; private static final String CONTENT_TYPE = "application/json"; - @Before - public void setup() throws Exception { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + @BeforeEach + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); } @Test public void givenWac_whenServletContext_thenItProvidesGreetController() { - final ServletContext servletContext = wac.getServletContext(); - Assert.assertNotNull(servletContext); - Assert.assertTrue(servletContext instanceof MockServletContext); - Assert.assertNotNull(wac.getBean("greetController")); + final ServletContext servletContext = webApplicationContext.getServletContext(); + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("greetController")); } @Test @@ -54,8 +53,12 @@ public class GreetControllerIntegrationTest { @Test public void givenGreetURI_whenMockMVC_thenVerifyResponse() throws Exception { - final MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/greet")).andDo(print()).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!")).andReturn(); - Assert.assertEquals(CONTENT_TYPE, mvcResult.getResponse().getContentType()); + final MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/greet")) + .andDo(print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!")) + .andReturn(); + assertEquals(CONTENT_TYPE, mvcResult.getResponse().getContentType()); } @Test diff --git a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerRealIntegrationTest.java b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerRealIntegrationTest.java index 05c6313e76..825520526e 100644 --- a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerRealIntegrationTest.java +++ b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerRealIntegrationTest.java @@ -1,18 +1,15 @@ package com.baeldung.web.controller; import io.restassured.RestAssured; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; import static io.restassured.RestAssured.given; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "server.servlet.context-path=/"}) public class GreetControllerRealIntegrationTest { @@ -20,7 +17,7 @@ public class GreetControllerRealIntegrationTest { @LocalServerPort private int port; - @Before + @BeforeEach public void setUp() { RestAssured.port = port; } @@ -28,7 +25,7 @@ public class GreetControllerRealIntegrationTest { @Test public void givenGreetURI_whenSendingReq_thenVerifyResponse() { given().get("/greet") - .then() - .statusCode(200); + .then() + .statusCode(200); } } diff --git a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerUnitTest.java b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerUnitTest.java index eacd256438..ecc55e8da2 100644 --- a/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerUnitTest.java +++ b/spring-web-modules/spring-mvc-java/src/test/java/com/baeldung/web/controller/GreetControllerUnitTest.java @@ -1,25 +1,22 @@ package com.baeldung.web.controller; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; - -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + public class GreetControllerUnitTest { private MockMvc mockMvc; private static final String CONTENT_TYPE = "application/json"; - @Before - public void setup() { + @BeforeEach + void setUp() { this.mockMvc = MockMvcBuilders.standaloneSetup(new GreetController()).build(); } @@ -50,12 +47,12 @@ public class GreetControllerUnitTest { @Test public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() throws Exception { - this.mockMvc.perform(MockMvcRequestBuilders.post("/greetWithPost")).andDo(print()).andExpect(status().isOk()).andExpect(content().contentType(CONTENT_TYPE)).andExpect(jsonPath("$.message").value("Hello World!!!")); + this.mockMvc.perform(post("/greetWithPost")).andDo(print()).andExpect(status().isOk()).andExpect(content().contentType(CONTENT_TYPE)).andExpect(jsonPath("$.message").value("Hello World!!!")); } @Test public void givenGreetURIWithPostAndFormData_whenMockMVC_thenVerifyResponse() throws Exception { - this.mockMvc.perform(MockMvcRequestBuilders.post("/greetWithPostAndFormData").param("id", "1").param("name", "John Doe")).andDo(print()).andExpect(status().isOk()).andExpect(content().contentType(CONTENT_TYPE)) + this.mockMvc.perform(post("/greetWithPostAndFormData").param("id", "1").param("name", "John Doe")).andDo(print()).andExpect(status().isOk()).andExpect(content().contentType(CONTENT_TYPE)) .andExpect(jsonPath("$.message").value("Hello World John Doe!!!")).andExpect(jsonPath("$.id").value(1)); } } diff --git a/spring-web-modules/spring-mvc-views/pom.xml b/spring-web-modules/spring-mvc-views/pom.xml index 2c3be5a33e..2e80a60966 100644 --- a/spring-web-modules/spring-mvc-views/pom.xml +++ b/spring-web-modules/spring-mvc-views/pom.xml @@ -11,7 +11,7 @@ com.baeldung parent-spring-5 0.0.1-SNAPSHOT - parent-spring-5/pom.xml + ../../parent-spring-5 diff --git a/spring-web-modules/spring-rest-http-2/pom.xml b/spring-web-modules/spring-rest-http-2/pom.xml index 6aa8be365c..0ea3fd6840 100644 --- a/spring-web-modules/spring-rest-http-2/pom.xml +++ b/spring-web-modules/spring-rest-http-2/pom.xml @@ -19,6 +19,10 @@ org.springframework.boot spring-boot-starter-web
+ + org.springframework.boot + spring-boot-starter-webflux + io.springfox springfox-swagger2 @@ -29,6 +33,19 @@ springfox-swagger-ui ${swagger2.version} + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + io.github.resilience4j + resilience4j-timelimiter + 1.6.1 + diff --git a/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/RequestTimeoutRestController.java b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/RequestTimeoutRestController.java new file mode 100644 index 0000000000..d425737bab --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/RequestTimeoutRestController.java @@ -0,0 +1,61 @@ +package com.baeldung.requesttimeout; + +import com.baeldung.requesttimeout.domain.Book; +import com.baeldung.requesttimeout.domain.BookRepository; +import io.github.resilience4j.timelimiter.TimeLimiter; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; + +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + +@RestController +public class RequestTimeoutRestController { + + private final BookRepository bookRepository; + private final WebClient webClient; + + public RequestTimeoutRestController(BookRepository bookRepository, WebClient webClient) { + this.bookRepository = bookRepository; + this.webClient = webClient; + } + + @GetMapping("/author/transactional") + @Transactional(timeout = 1) + public String getWithTransactionTimeout(@RequestParam String title) { + return getAuthor(title); + } + + private final TimeLimiter ourTimeLimiter = TimeLimiter.of(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(500)).build()); + @GetMapping("/author/resilience4j") + public Callable getWithResilience4jTimeLimiter(@RequestParam String title) { + return TimeLimiter.decorateFutureSupplier(ourTimeLimiter, () -> CompletableFuture.supplyAsync(() -> getAuthor(title))); + } + + @GetMapping("/author/mvc-request-timeout") + public Callable getWithMvcRequestTimeout(@RequestParam String title) { + return () -> getAuthor(title); + } + + @GetMapping("/author/webclient") + public String getWithWebClient(@RequestParam String title) { + return webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/author/transactional") + .queryParam("title", title) + .build()) + .retrieve() + .bodyToMono(String.class) + .block(); + } + + private String getAuthor(String title) { + bookRepository.wasteTime(); + return bookRepository.findById(title).map(Book::getAuthor).orElse("No book found for this title."); + } +} diff --git a/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/configuration/WebClientConfiguration.java b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/configuration/WebClientConfiguration.java new file mode 100644 index 0000000000..52b3573411 --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/configuration/WebClientConfiguration.java @@ -0,0 +1,21 @@ +package com.baeldung.requesttimeout.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + +import java.time.Duration; + +@Configuration +public class WebClientConfiguration { + + @Bean + public WebClient webClient() { + return WebClient.builder() + .baseUrl("http://localhost:8080") + .clientConnector(new ReactorClientHttpConnector(HttpClient.create().responseTimeout(Duration.ofMillis(250)))) + .build(); + } +} diff --git a/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/Book.java b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/Book.java new file mode 100644 index 0000000000..846bfb2cec --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/Book.java @@ -0,0 +1,28 @@ +package com.baeldung.requesttimeout.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class Book { + + @Id + private String title; + private String author; + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } +} diff --git a/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/BookRepository.java b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/BookRepository.java new file mode 100644 index 0000000000..8ecab0f1d2 --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/main/java/com/baeldung/requesttimeout/domain/BookRepository.java @@ -0,0 +1,14 @@ +package com.baeldung.requesttimeout.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookRepository extends JpaRepository { + + default int wasteTime() { + int i = Integer.MIN_VALUE; + while(i < Integer.MAX_VALUE) { + i++; + } + return i; + } +} diff --git a/spring-web-modules/spring-rest-http-2/src/main/resources/application.properties b/spring-web-modules/spring-rest-http-2/src/main/resources/application.properties new file mode 100644 index 0000000000..ff4af943ec --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.mvc.async.request-timeout=750 \ No newline at end of file diff --git a/spring-web-modules/spring-rest-http-2/src/test/com/baeldung/requesttimeout/RequestTimeoutUnitTest.java b/spring-web-modules/spring-rest-http-2/src/test/com/baeldung/requesttimeout/RequestTimeoutUnitTest.java new file mode 100644 index 0000000000..da7d40d53c --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/test/com/baeldung/requesttimeout/RequestTimeoutUnitTest.java @@ -0,0 +1,46 @@ +package com.baeldung.requesttimeout; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; + +@SpringBootTest +@RunWith(SpringRunner.class) +public class RequestTimeoutTests { + + private static final WebClient WEB_CLIENT = WebClient.builder().baseUrl("http://localhost:8080").build(); + + @Test(expected = WebClientRequestException.class) + public void givenTransactionTimeout_whenTimeExpires_thenReceiveException() { + getAuthor("transactional"); + } + + @Test(expected = WebClientRequestException.class) + public void givenResilience4jTimeLimiter_whenTimeExpires_thenReceiveException() { + getAuthor("resilience4j"); + } + + @Test(expected = WebClientRequestException.class) + public void givenMvcRequestTimeout_whenTimeExpires_thenReceiveException() { + getAuthor("mvc-request-timeout"); + } + + @Test(expected = WebClientRequestException.class) + public void givenWebClientTimeout_whenTimeExpires_thenReceiveException() { + getAuthor("webclient"); + } + + private void getAuthor(String authorPath) { + WEB_CLIENT.get() + .uri(uriBuilder -> uriBuilder + .path("/author/" + authorPath) + .queryParam("title", "title") + .build()) + .retrieve() + .bodyToMono(String.class) + .block(); + } +} diff --git a/spring-web-modules/spring-rest-http-2/src/test/resources/application.properties b/spring-web-modules/spring-rest-http-2/src/test/resources/application.properties new file mode 100644 index 0000000000..ff4af943ec --- /dev/null +++ b/spring-web-modules/spring-rest-http-2/src/test/resources/application.properties @@ -0,0 +1 @@ +spring.mvc.async.request-timeout=750 \ No newline at end of file diff --git a/spring-web-modules/spring-resttemplate-2/README.md b/spring-web-modules/spring-resttemplate-2/README.md index 54fad5e01b..ace7ae817b 100644 --- a/spring-web-modules/spring-resttemplate-2/README.md +++ b/spring-web-modules/spring-resttemplate-2/README.md @@ -10,5 +10,4 @@ This module contains articles about Spring RestTemplate - [RestTemplate Post Request with JSON](https://www.baeldung.com/spring-resttemplate-post-json) - [How to Compress Requests Using the Spring RestTemplate](https://www.baeldung.com/spring-resttemplate-compressing-requests) - [Get list of JSON objects with Spring RestTemplate](https://www.baeldung.com/spring-resttemplate-json-list) -- [A Guide To Spring Redirects](https://www.baeldung.com/spring-redirect-and-forward) - [Spring RestTemplate Exception: “Not enough variables available to expand”](https://www.baeldung.com/spring-not-enough-variables-available) diff --git a/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectController.java b/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectController.java index 321f3be3ef..1d77a07bea 100644 --- a/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectController.java +++ b/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectController.java @@ -64,5 +64,11 @@ public class RedirectController { public ModelAndView redirectedPostToPost() { return new ModelAndView("redirection"); } - + + @RequestMapping(value="/forwardWithParams", method = RequestMethod.GET) + public ModelAndView forwardWithParams(HttpServletRequest request) { + request.setAttribute("param1", "one"); + request.setAttribute("param2", "two"); + return new ModelAndView("forward:/forwardedWithParams"); + } } \ No newline at end of file diff --git a/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectParamController.java b/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectParamController.java new file mode 100644 index 0000000000..abe268b435 --- /dev/null +++ b/spring-web-modules/spring-resttemplate-2/src/main/java/com/baeldung/sampleapp/web/controller/redirect/RedirectParamController.java @@ -0,0 +1,25 @@ +package com.baeldung.sampleapp.web.controller.redirect; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +@RequestMapping("/") +public class RedirectParamController { + + @RequestMapping(value = "/forwardedWithParams", method = RequestMethod.GET) + public RedirectView forwardedWithParams(final RedirectAttributes redirectAttributes, HttpServletRequest request) { + + redirectAttributes.addAttribute("param1", request.getAttribute("param1")); + redirectAttributes.addAttribute("param2", request.getAttribute("param2")); + + redirectAttributes.addAttribute("attribute", "forwardedWithParams"); + return new RedirectView("redirectedUrl"); + + } +} \ No newline at end of file diff --git a/stripe/README.md b/stripe/README.md index 9e41dcf945..36f0d6e3f3 100644 --- a/stripe/README.md +++ b/stripe/README.md @@ -5,4 +5,4 @@ This module contains articles about Stripe ### Relevant articles - [Introduction to the Stripe API for Java](https://www.baeldung.com/java-stripe-api) - +- [Viewing Contents of a JAR File](https://www.baeldung.com/java-view-jar-contents) diff --git a/testing-modules/README.md b/testing-modules/README.md deleted file mode 100644 index c6098d1210..0000000000 --- a/testing-modules/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Testing Modules - -This is an aggregator module containing multiple modules focused on testing libraries. diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml index 0416423239..fd4a13d026 100644 --- a/testing-modules/pom.xml +++ b/testing-modules/pom.xml @@ -31,7 +31,7 @@ rest-assured rest-testing selenium-junit-testng - spring-testing + spring-testing spring-testing-2 test-containers testing-assertions @@ -41,9 +41,10 @@ junit-5-advanced xmlunit-2 junit-4 - testing-libraries - testing-libraries-2 + testing-libraries + testing-libraries-2 powermock + zerocode diff --git a/testing-modules/spring-testing/pom.xml b/testing-modules/spring-testing/pom.xml index 9e0c986bb2..74d55d4c08 100644 --- a/testing-modules/spring-testing/pom.xml +++ b/testing-modules/spring-testing/pom.xml @@ -125,7 +125,7 @@ 3.1.6 5.7.0 1.7.0 - 5.3.0 + 5.3.4 4.0.1 2.1.1 diff --git a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ContextPropertySourceResolverIntegrationTest.java b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ContextPropertySourceResolverIntegrationTest.java index 9b692edd7b..9481c54b48 100644 --- a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ContextPropertySourceResolverIntegrationTest.java +++ b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ContextPropertySourceResolverIntegrationTest.java @@ -1,15 +1,14 @@ package com.baeldung.overrideproperties; import com.baeldung.overrideproperties.resolver.PropertySourceResolver; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(SpringRunner.class) +@SpringBootTest @ContextConfiguration(initializers = PropertyOverrideContextInitializer.class, classes = Application.class) public class ContextPropertySourceResolverIntegrationTest { diff --git a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ProfilePropertySourceResolverIntegrationTest.java b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ProfilePropertySourceResolverIntegrationTest.java index 815b628f0a..77b882d3a2 100644 --- a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ProfilePropertySourceResolverIntegrationTest.java +++ b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/ProfilePropertySourceResolverIntegrationTest.java @@ -1,18 +1,17 @@ package com.baeldung.overrideproperties; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import com.baeldung.overrideproperties.resolver.PropertySourceResolver; -@RunWith(SpringRunner.class) +import static org.junit.jupiter.api.Assertions.assertEquals; + @SpringBootTest @ActiveProfiles("test") @EnableWebMvc diff --git a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/PropertyOverrideContextInitializer.java b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/PropertyOverrideContextInitializer.java index a8c4267c5c..994a8a4288 100644 --- a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/PropertyOverrideContextInitializer.java +++ b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/PropertyOverrideContextInitializer.java @@ -12,6 +12,6 @@ public class PropertyOverrideContextInitializer implements ApplicationContextIni public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(configurableApplicationContext, "example.firstProperty=" + PROPERTY_FIRST_VALUE); - TestPropertySourceUtils.addPropertiesFilesToEnvironment(configurableApplicationContext, "context-override-application.properties"); + TestPropertySourceUtils.addPropertiesFilesToEnvironment(configurableApplicationContext, "classpath:context-override-application.properties"); } } diff --git a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/SpringBootPropertySourceResolverIntegrationTest.java b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/SpringBootPropertySourceResolverIntegrationTest.java index d00aa51e6c..bb08d701d9 100644 --- a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/SpringBootPropertySourceResolverIntegrationTest.java +++ b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/SpringBootPropertySourceResolverIntegrationTest.java @@ -1,16 +1,16 @@ package com.baeldung.overrideproperties; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import com.baeldung.overrideproperties.resolver.PropertySourceResolver; -@RunWith(SpringRunner.class) +import static org.junit.jupiter.api.Assertions.assertEquals; + @SpringBootTest(properties = { "example.firstProperty=annotation" }) @EnableWebMvc public class SpringBootPropertySourceResolverIntegrationTest { @@ -23,8 +23,8 @@ public class SpringBootPropertySourceResolverIntegrationTest { final String firstProperty = propertySourceResolver.getFirstProperty(); final String secondProperty = propertySourceResolver.getSecondProperty(); - Assert.assertEquals("annotation", firstProperty); - Assert.assertEquals("file", secondProperty); + assertEquals("annotation", firstProperty); + assertEquals("file", secondProperty); } } \ No newline at end of file diff --git a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/TestResourcePropertySourceResolverIntegrationTest.java b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/TestResourcePropertySourceResolverIntegrationTest.java index dc15851277..673742124a 100644 --- a/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/TestResourcePropertySourceResolverIntegrationTest.java +++ b/testing-modules/spring-testing/src/test/java/com/baeldung/overrideproperties/TestResourcePropertySourceResolverIntegrationTest.java @@ -1,17 +1,16 @@ package com.baeldung.overrideproperties; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import com.baeldung.overrideproperties.resolver.PropertySourceResolver; -@RunWith(SpringRunner.class) +import static org.junit.jupiter.api.Assertions.assertEquals; + @SpringBootTest @EnableWebMvc public class TestResourcePropertySourceResolverIntegrationTest { diff --git a/testing-modules/zerocode/README.md b/testing-modules/zerocode/README.md new file mode 100644 index 0000000000..a0a844c63d --- /dev/null +++ b/testing-modules/zerocode/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Introduction to ZeroCode](https://www.baeldung.com/zerocode-intro) diff --git a/testing-modules/zerocode/pom.xml b/testing-modules/zerocode/pom.xml new file mode 100644 index 0000000000..9d765e6cb4 --- /dev/null +++ b/testing-modules/zerocode/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + testing-modules + com.baeldung + 1.0.0-SNAPSHOT + + + zerocode + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + + org.jsmart + zerocode-tdd + 1.3.27 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + it + + + + + pre-integration-test + + start + + + ${skip.it} + + + + post-integration-test + + stop + + + ${skip.it} + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0-M5 + + ${skip.it} + + + + org.apache.maven.surefire + surefire-junit47 + 3.0.0-M5 + + + + + + integration-test + verify + + + + + + + + + UTF-8 + 8 + 8 + 2.4.2 + true + + + diff --git a/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/User.java b/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/User.java new file mode 100644 index 0000000000..3a2a853220 --- /dev/null +++ b/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/User.java @@ -0,0 +1,31 @@ +package com.baeldung.zerocode; + +public class User { + private String id; + private String firstName; + private String lastName; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/ZerocodeApplication.java b/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/ZerocodeApplication.java new file mode 100644 index 0000000000..3218e97400 --- /dev/null +++ b/testing-modules/zerocode/src/main/java/com/baeldung/zerocode/ZerocodeApplication.java @@ -0,0 +1,38 @@ +package com.baeldung.zerocode; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +@SpringBootApplication +@RestController +@RequestMapping("/api/users") +public class ZerocodeApplication { + private List users = new ArrayList<>(); + + public static void main(String[] args) { + SpringApplication.run(ZerocodeApplication.class, args); + } + + @PostMapping + public ResponseEntity create(@RequestBody User user) { + if (!StringUtils.hasText(user.getFirstName())) { + return new ResponseEntity("firstName can't be empty!", HttpStatus.BAD_REQUEST); + } + if (!StringUtils.hasText(user.getLastName())) { + return new ResponseEntity("lastName can't be empty!", HttpStatus.BAD_REQUEST); + } + user.setId(UUID.randomUUID() + .toString()); + users.add(user); + return new ResponseEntity(user, HttpStatus.CREATED); + } + +} diff --git a/testing-modules/zerocode/src/test/java/com/baeldung/zerocode/rest/UserEndpointIT.java b/testing-modules/zerocode/src/test/java/com/baeldung/zerocode/rest/UserEndpointIT.java new file mode 100644 index 0000000000..cc461fd0fc --- /dev/null +++ b/testing-modules/zerocode/src/test/java/com/baeldung/zerocode/rest/UserEndpointIT.java @@ -0,0 +1,18 @@ +package com.baeldung.zerocode.rest; + +import org.jsmart.zerocode.core.domain.Scenario; +import org.jsmart.zerocode.core.domain.TargetEnv; +import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(ZeroCodeUnitRunner.class) +@TargetEnv("rest_api.properties") +public class UserEndpointIT { + + @Test + @Scenario("rest/user_create_test.json") + public void test_user_creation_endpoint() { + } + +} diff --git a/testing-modules/zerocode/src/test/resources/rest/user_create_test.json b/testing-modules/zerocode/src/test/resources/rest/user_create_test.json new file mode 100644 index 0000000000..0e8ee66196 --- /dev/null +++ b/testing-modules/zerocode/src/test/resources/rest/user_create_test.json @@ -0,0 +1,39 @@ +{ + "scenarioName": "test user creation endpoint", + "steps": [ + { + "name": "test_successful_creation", + "url": "/api/users", + "method": "POST", + "request": { + "body": { + "firstName": "John", + "lastName": "Doe" + } + }, + "verify": { + "status": 201, + "body": { + "id": "$NOT.NULL", + "firstName": "John", + "lastName": "Doe" + } + } + }, + { + "name": "test_firstname_validation", + "url": "/api/users", + "method": "POST", + "request": { + "body": { + "firstName": "", + "lastName": "Doe" + } + }, + "assertions": { + "status": 400, + "rawBody": "firstName can't be empty!" + } + } + ] +} diff --git a/testing-modules/zerocode/src/test/resources/rest_api.properties b/testing-modules/zerocode/src/test/resources/rest_api.properties new file mode 100644 index 0000000000..724042ade7 --- /dev/null +++ b/testing-modules/zerocode/src/test/resources/rest_api.properties @@ -0,0 +1,3 @@ +web.application.endpoint.host=http://localhost +web.application.endpoint.port=8080 +web.application.endpoint.context= \ No newline at end of file