diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparators.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparators.java new file mode 100644 index 0000000000..376b196aa1 --- /dev/null +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparators.java @@ -0,0 +1,30 @@ +package com.baeldung.algorithms.stringsortingbynumber; + +import java.util.Comparator; + +public final class NaturalOrderComparators { + + private static final String DIGIT_AND_DECIMAL_REGEX = "[^\\d.]"; + + private NaturalOrderComparators() { + throw new AssertionError("Let's keep this static"); + } + + public static Comparator createNaturalOrderRegexComparator() { + return Comparator.comparingDouble(NaturalOrderComparators::parseStringToNumber); + } + + private static double parseStringToNumber(String input){ + + final String digitsOnly = input.replaceAll(DIGIT_AND_DECIMAL_REGEX, ""); + + if("".equals(digitsOnly)) return 0; + + try{ + return Double.parseDouble(digitsOnly); + }catch (NumberFormatException nfe){ + return 0; + } + } + +} diff --git a/algorithms-miscellaneous-3/src/test/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparatorsUnitTest.java b/algorithms-miscellaneous-3/src/test/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparatorsUnitTest.java new file mode 100644 index 0000000000..549151bc93 --- /dev/null +++ b/algorithms-miscellaneous-3/src/test/java/com/baeldung/algorithms/stringsortingbynumber/NaturalOrderComparatorsUnitTest.java @@ -0,0 +1,79 @@ +package com.baeldung.algorithms.stringsortingbynumber; + +import com.baeldung.algorithms.stringsortingbynumber.NaturalOrderComparators; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class NaturalOrderComparatorsUnitTest { + + @Test + public void givenSimpleStringsContainingIntsAndDoubles_whenSortedByRegex_checkSortingCorrect() { + + List testStrings = Arrays.asList("a1", "b3", "c4", "d2.2", "d2.4", "d2.3d"); + + testStrings.sort(NaturalOrderComparators.createNaturalOrderRegexComparator()); + + List expected = Arrays.asList("a1", "d2.2", "d2.3d", "d2.4", "b3", "c4"); + + assertEquals(expected, testStrings); + + + } + + @Test + public void givenSimpleStringsContainingIntsAndDoublesWithAnInvalidNumber_whenSortedByRegex_checkSortingCorrect() { + + List testStrings = Arrays.asList("a1", "b3", "c4", "d2.2", "d2.4", "d2.3.3d"); + + testStrings.sort(NaturalOrderComparators.createNaturalOrderRegexComparator()); + + List expected = Arrays.asList("d2.3.3d", "a1", "d2.2", "d2.4", "b3", "c4"); + + assertEquals(expected, testStrings); + + + } + + @Test + public void givenAllForseenProblems_whenSortedByRegex_checkSortingCorrect() { + + List testStrings = Arrays.asList("a1", "b3", "c4", "d2.2", "d2.f4", "d2.3.3d"); + + testStrings.sort(NaturalOrderComparators.createNaturalOrderRegexComparator()); + + List expected = Arrays.asList("d2.3.3d", "a1", "d2.2", "d2.f4", "b3", "c4"); + + assertEquals(expected, testStrings); + + + } + + @Test + public void givenComplexStringsContainingSeparatedNumbers_whenSortedByRegex_checkNumbersCondensedAndSorted() { + + List testStrings = Arrays.asList("a1b2c5", "b3ght3.2", "something65.thensomething5"); //125, 33.2, 65.5 + + List expected = Arrays.asList("b3ght3.2", "something65.thensomething5", "a1b2c5" ); + + testStrings.sort(NaturalOrderComparators.createNaturalOrderRegexComparator()); + + assertEquals(expected, testStrings); + + } + + @Test + public void givenStringsNotContainingNumbers_whenSortedByRegex_checkOrderNotChanged() { + + List testStrings = Arrays.asList("a", "c", "d", "e"); + List expected = new ArrayList<>(testStrings); + + testStrings.sort(NaturalOrderComparators.createNaturalOrderRegexComparator()); + + assertEquals(expected, testStrings); + } +} \ No newline at end of file diff --git a/algorithms-sorting/src/main/java/com/baeldung/algorithms/shellsort/ShellSort.java b/algorithms-sorting/src/main/java/com/baeldung/algorithms/shellsort/ShellSort.java new file mode 100644 index 0000000000..c130e2d866 --- /dev/null +++ b/algorithms-sorting/src/main/java/com/baeldung/algorithms/shellsort/ShellSort.java @@ -0,0 +1,20 @@ +package com.baeldung.algorithms.shellsort; + +public class ShellSort { + + public static void sort(int arrayToSort[]) { + int n = arrayToSort.length; + + for (int gap = n / 2; gap > 0; gap /= 2) { + for (int i = gap; i < n; i++) { + int key = arrayToSort[i]; + int j = i; + while (j >= gap && arrayToSort[j - gap] > key) { + arrayToSort[j] = arrayToSort[j - gap]; + j -= gap; + } + arrayToSort[j] = key; + } + } + } +} diff --git a/algorithms-sorting/src/test/java/com/baeldung/algorithms/shellsort/ShellSortUnitTest.java b/algorithms-sorting/src/test/java/com/baeldung/algorithms/shellsort/ShellSortUnitTest.java new file mode 100644 index 0000000000..91a27c41d0 --- /dev/null +++ b/algorithms-sorting/src/test/java/com/baeldung/algorithms/shellsort/ShellSortUnitTest.java @@ -0,0 +1,17 @@ +package com.baeldung.algorithms.shellsort; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +public class ShellSortUnitTest { + + @Test + public void givenUnsortedArray_whenShellSort_thenSortedAsc() { + int[] input = {41, 15, 82, 5, 65, 19, 32, 43, 8}; + ShellSort.sort(input); + int[] expected = {5, 8, 15, 19, 32, 41, 43, 65, 82}; + assertArrayEquals("the two arrays are not equal", expected, input); + } +} diff --git a/axon/pom.xml b/axon/pom.xml index 2b9ac1fcdd..3d30cceb83 100644 --- a/axon/pom.xml +++ b/axon/pom.xml @@ -18,12 +18,6 @@ org.axonframework axon-spring-boot-starter ${axon.version} - - - org.axonframework - axon-server-connector - - @@ -58,7 +52,7 @@ - 4.0.3 + 4.1.2 \ No newline at end of file diff --git a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java b/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java index b37b2fdd66..4ef02e6b54 100644 --- a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java +++ b/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java @@ -13,6 +13,7 @@ import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; import com.baeldung.axon.coreapi.events.OrderPlacedEvent; import com.baeldung.axon.coreapi.events.OrderShippedEvent; +import com.baeldung.axon.coreapi.exceptions.UnconfirmedOrderException; @Aggregate public class OrderAggregate { @@ -34,7 +35,7 @@ public class OrderAggregate { @CommandHandler public void handle(ShipOrderCommand command) { if (!orderConfirmed) { - throw new IllegalStateException("Cannot ship an order which has not been confirmed yet."); + throw new UnconfirmedOrderException(); } apply(new OrderShippedEvent(orderId)); @@ -43,12 +44,12 @@ public class OrderAggregate { @EventSourcingHandler public void on(OrderPlacedEvent event) { this.orderId = event.getOrderId(); - orderConfirmed = false; + this.orderConfirmed = false; } @EventSourcingHandler public void on(OrderConfirmedEvent event) { - orderConfirmed = true; + this.orderConfirmed = true; } protected OrderAggregate() { diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/UnconfirmedOrderException.java b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/UnconfirmedOrderException.java new file mode 100644 index 0000000000..1873bc6893 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/UnconfirmedOrderException.java @@ -0,0 +1,8 @@ +package com.baeldung.axon.coreapi.exceptions; + +public class UnconfirmedOrderException extends IllegalStateException { + + public UnconfirmedOrderException() { + super("Cannot ship an order which has not been confirmed yet."); + } +} diff --git a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java b/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java index d4cf3d999b..a37f0111ed 100644 --- a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java +++ b/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.axonframework.config.ProcessingGroup; import org.axonframework.eventhandling.EventHandler; import org.axonframework.queryhandling.QueryHandler; import org.springframework.stereotype.Service; @@ -16,6 +17,7 @@ import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; import com.baeldung.axon.coreapi.queries.OrderedProduct; @Service +@ProcessingGroup("ordered-products") public class OrderedProductsEventHandler { private final Map orderedProducts = new HashMap<>(); diff --git a/axon/src/main/resources/application.properties b/axon/src/main/resources/application.properties new file mode 100644 index 0000000000..7c51eb8e1e --- /dev/null +++ b/axon/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=Order Management Service \ No newline at end of file diff --git a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java index 9beedbaa19..aaefe49fb1 100644 --- a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java +++ b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java @@ -2,6 +2,7 @@ package com.baeldung.axon.commandmodel; import java.util.UUID; +import com.baeldung.axon.coreapi.exceptions.UnconfirmedOrderException; import org.axonframework.test.aggregate.AggregateTestFixture; import org.axonframework.test.aggregate.FixtureConfiguration; import org.junit.*; @@ -41,12 +42,12 @@ public class OrderAggregateUnitTest { } @Test - public void givenOrderPlacedEvent_whenShipOrderCommand_thenShouldThrowIllegalStateException() { + public void givenOrderPlacedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() { String orderId = UUID.randomUUID().toString(); String product = "Deluxe Chair"; fixture.given(new OrderPlacedEvent(orderId, product)) .when(new ShipOrderCommand(orderId)) - .expectException(IllegalStateException.class); + .expectException(UnconfirmedOrderException.class); } @Test diff --git a/core-java-arrays/src/main/java/com/baeldung/array/looping/LoopDiagonally.java b/core-java-arrays/src/main/java/com/baeldung/array/looping/LoopDiagonally.java new file mode 100644 index 0000000000..71e2840f45 --- /dev/null +++ b/core-java-arrays/src/main/java/com/baeldung/array/looping/LoopDiagonally.java @@ -0,0 +1,45 @@ +package com.baeldung.array.looping; + +public class LoopDiagonally { + + + public String loopDiagonally(String[][] twoDArray) { + + int length = twoDArray.length; + int diagonalLines = (length + length) - 1; + int itemsInDiagonal = 0; + int midPoint = (diagonalLines / 2) + 1; + StringBuilder output = new StringBuilder(); + + for (int i = 1; i <= diagonalLines; i++) { + + StringBuilder items = new StringBuilder(); + int rowIndex; + int columnIndex; + + if (i <= midPoint) { + itemsInDiagonal++; + for (int j = 0; j < itemsInDiagonal; j++) { + rowIndex = (i - j) - 1; + columnIndex = j; + items.append(twoDArray[rowIndex][columnIndex]); + } + } else { + itemsInDiagonal--; + for (int j = 0; j < itemsInDiagonal; j++) { + rowIndex = (length - 1) - j; + columnIndex = (i - length) + j; + items.append(twoDArray[rowIndex][columnIndex]); + } + } + + if (i != diagonalLines) { + output.append(items).append(" "); + } else { + output.append(items); + } + } + + return output.toString(); + } +} diff --git a/core-java-arrays/src/test/java/com/baeldung/array/looping/LoopDiagonallyTest.java b/core-java-arrays/src/test/java/com/baeldung/array/looping/LoopDiagonallyTest.java new file mode 100644 index 0000000000..df6079270b --- /dev/null +++ b/core-java-arrays/src/test/java/com/baeldung/array/looping/LoopDiagonallyTest.java @@ -0,0 +1,20 @@ +package com.baeldung.array.looping; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class LoopDiagonallyTest { + + @Test + public void twoArrayIsLoopedDiagonallyAsExpected() { + + LoopDiagonally loopDiagonally = new LoopDiagonally(); + String[][] twoDArray = {{"a", "b", "c"}, + {"d", "e", "f"}, + {"g", "h", "i"}}; + + String output = loopDiagonally.loopDiagonally(twoDArray); + assertEquals("a db gec hf i", output); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-arrays/src/main/java/com/baeldung/array/SortedArrayChecker.java b/core-java-modules/core-java-arrays/src/main/java/com/baeldung/array/SortedArrayChecker.java index ec612fd53b..78a9a8f4d1 100644 --- a/core-java-modules/core-java-arrays/src/main/java/com/baeldung/array/SortedArrayChecker.java +++ b/core-java-modules/core-java-arrays/src/main/java/com/baeldung/array/SortedArrayChecker.java @@ -2,7 +2,10 @@ package com.baeldung.array; import com.baeldung.arraycopy.model.Employee; +import java.util.Comparator; + public class SortedArrayChecker { + boolean isSorted(int[] array, int length) { if (array == null || length < 2) return true; @@ -22,7 +25,7 @@ public class SortedArrayChecker { return true; } - boolean isSorted(String[] array, int length) { + boolean isSorted(Comparable[] array, int length) { if (array == null || length < 2) return true; @@ -32,40 +35,31 @@ public class SortedArrayChecker { return isSorted(array, length - 1); } -boolean isSorted(String[] array) { - for (int i = 0; i < array.length - 1; ++i) { - if (array[i].compareTo(array[i + 1]) > 0) - return false; - } - - return true; -} - - boolean isSortedByName(Employee[] array) { + boolean isSorted(Comparable[] array) { for (int i = 0; i < array.length - 1; ++i) { - if (array[i].getName().compareTo(array[i + 1].getName()) > 0) + if (array[i].compareTo(array[i + 1]) > 0) return false; } return true; } -boolean isSortedByAge(Employee[] array) { - for (int i = 0; i < array.length - 1; ++i) { - if (array[i].getAge() > (array[i + 1].getAge())) - return false; + boolean isSorted(Object[] array, Comparator comparator) { + for (int i = 0; i < array.length - 1; ++i) { + if (comparator.compare(array[i], (array[i + 1])) > 0) + return false; + } + + return true; } - return true; -} - - boolean isSortedByAge(Employee[] array, int length) { + boolean isSorted(Object[] array, Comparator comparator, int length) { if (array == null || length < 2) return true; - if (array[length - 2].getAge() > array[length - 1].getAge()) + if (comparator.compare(array[length - 2], array[length - 1]) > 0) return false; - return isSortedByAge(array, length - 1); + return isSorted(array, comparator, length - 1); } } diff --git a/core-java-modules/core-java-arrays/src/test/java/com/baeldung/array/SortedArrayCheckerUnitTest.java b/core-java-modules/core-java-arrays/src/test/java/com/baeldung/array/SortedArrayCheckerUnitTest.java index 29866a3c22..7971e0eab7 100644 --- a/core-java-modules/core-java-arrays/src/test/java/com/baeldung/array/SortedArrayCheckerUnitTest.java +++ b/core-java-modules/core-java-arrays/src/test/java/com/baeldung/array/SortedArrayCheckerUnitTest.java @@ -4,32 +4,33 @@ import com.baeldung.arraycopy.model.Employee; import org.junit.Before; import org.junit.Test; +import java.util.Comparator; + import static org.assertj.core.api.Assertions.assertThat; -class SortedArrayCheckerUnitTest { - +public class SortedArrayCheckerUnitTest { private static final int[] INTEGER_SORTED = {1, 3, 5, 7, 9}; private static final int[] INTEGER_NOT_SORTED = {1, 3, 11, 7}; private static final String[] STRING_SORTED = {"abc", "cde", "fgh"}; private static final String[] STRING_NOT_SORTED = {"abc", "fgh", "cde", "ijk"}; - private final Employee[] EMPLOYEES_SORTED_BY_NAME = { + private static final Employee[] EMPLOYEES_SORTED_BY_NAME = { new Employee(1, "Carlos", 26), new Employee(2, "Daniel", 31), new Employee(3, "Marta", 27)}; - private final Employee[] EMPLOYEES_NOT_SORTED_BY_NAME = { + private static final Employee[] EMPLOYEES_NOT_SORTED_BY_NAME = { new Employee(1, "Daniel", 31), new Employee(2, "Carlos", 26), new Employee(3, "Marta", 27)}; - private final Employee[] EMPLOYEES_SORTED_BY_AGE = { + private static final Employee[] EMPLOYEES_SORTED_BY_AGE = { new Employee(1, "Carlos", 26), new Employee(2, "Marta", 27), new Employee(3, "Daniel", 31)}; - private final Employee[] EMPLOYEES_NOT_SORTED_BY_AGE = { + private static final Employee[] EMPLOYEES_NOT_SORTED_BY_AGE = { new Employee(1, "Marta", 27), new Employee(2, "Carlos", 26), new Employee(3, "Daniel", 31)}; @@ -61,13 +62,18 @@ class SortedArrayCheckerUnitTest { @Test public void givenEmployeeArray_thenReturnIfItIsSortedOrNot() { - assertThat(sortedArrayChecker.isSortedByName(EMPLOYEES_SORTED_BY_NAME)).isEqualTo(true); - assertThat(sortedArrayChecker.isSortedByName(EMPLOYEES_NOT_SORTED_BY_NAME)).isEqualTo(false); + assertThat(sortedArrayChecker.isSorted(EMPLOYEES_SORTED_BY_NAME, Comparator.comparing(Employee::getName))).isEqualTo(true); + assertThat(sortedArrayChecker.isSorted(EMPLOYEES_NOT_SORTED_BY_NAME, Comparator.comparing(Employee::getName))).isEqualTo(false); - assertThat(sortedArrayChecker.isSortedByAge(EMPLOYEES_SORTED_BY_AGE)).isEqualTo(true); - assertThat(sortedArrayChecker.isSortedByAge(EMPLOYEES_NOT_SORTED_BY_AGE)).isEqualTo(false); + assertThat(sortedArrayChecker.isSorted(EMPLOYEES_SORTED_BY_AGE, Comparator.comparingInt(Employee::getAge))).isEqualTo(true); + assertThat(sortedArrayChecker.isSorted(EMPLOYEES_NOT_SORTED_BY_AGE, Comparator.comparingInt(Employee::getAge))).isEqualTo(false); - assertThat(sortedArrayChecker.isSortedByAge(EMPLOYEES_SORTED_BY_AGE, EMPLOYEES_SORTED_BY_AGE.length)).isEqualTo(true); - assertThat(sortedArrayChecker.isSortedByAge(EMPLOYEES_NOT_SORTED_BY_AGE, EMPLOYEES_NOT_SORTED_BY_AGE.length)).isEqualTo(false); + assertThat(sortedArrayChecker + .isSorted(EMPLOYEES_SORTED_BY_AGE, Comparator.comparingInt(Employee::getAge), EMPLOYEES_SORTED_BY_AGE.length)) + .isEqualTo(true); + assertThat(sortedArrayChecker + .isSorted(EMPLOYEES_NOT_SORTED_BY_AGE, Comparator.comparingInt(Employee::getAge), EMPLOYEES_NOT_SORTED_BY_AGE.length)) + .isEqualTo(false); } + } \ No newline at end of file diff --git a/core-java-modules/core-java-networking-2/.gitignore b/core-java-modules/core-java-networking-2/.gitignore new file mode 100644 index 0000000000..374c8bf907 --- /dev/null +++ b/core-java-modules/core-java-networking-2/.gitignore @@ -0,0 +1,25 @@ +*.class + +0.* + +#folders# +/target +/neoDb* +/data +/src/main/webapp/WEB-INF/classes +*/META-INF/* +.resourceCache + +# Packaged files # +*.jar +*.war +*.ear + +# Files generated by integration tests +backup-pom.xml +/bin/ +/temp + +#IntelliJ specific +.idea/ +*.iml \ No newline at end of file diff --git a/core-java-modules/core-java-networking-2/pom.xml b/core-java-modules/core-java-networking-2/pom.xml new file mode 100644 index 0000000000..8a26f6ab9f --- /dev/null +++ b/core-java-modules/core-java-networking-2/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + core-java-networking-2 + core-java-networking-2 + jar + + + com.baeldung.core-java-modules + core-java-modules + 1.0.0-SNAPSHOT + + + + + + + core-java-networking-2 + + diff --git a/core-java-modules/core-java-networking-2/src/main/java/com/baeldung/url/UrlChecker.java b/core-java-modules/core-java-networking-2/src/main/java/com/baeldung/url/UrlChecker.java new file mode 100644 index 0000000000..b99e74f8bf --- /dev/null +++ b/core-java-modules/core-java-networking-2/src/main/java/com/baeldung/url/UrlChecker.java @@ -0,0 +1,25 @@ +package com.baeldung.url; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class UrlChecker { + + public int getResponseCodeForURL(String address) throws IOException { + return getResponseCodeForURLUsing(address, "GET"); + } + + public int getResponseCodeForURLUsingHead(String address) throws IOException { + return getResponseCodeForURLUsing(address, "HEAD"); + } + + private int getResponseCodeForURLUsing(String address, String method) throws IOException { + HttpURLConnection.setFollowRedirects(false); // Set follow redirects to false + final URL url = new URL(address); + HttpURLConnection huc = (HttpURLConnection) url.openConnection(); + huc.setRequestMethod(method); + return huc.getResponseCode(); + } + +} diff --git a/core-java-modules/core-java-networking-2/src/test/java/com/baeldung/url/UrlCheckerUnitTest.java b/core-java-modules/core-java-networking-2/src/test/java/com/baeldung/url/UrlCheckerUnitTest.java new file mode 100644 index 0000000000..5e295e65a0 --- /dev/null +++ b/core-java-modules/core-java-networking-2/src/test/java/com/baeldung/url/UrlCheckerUnitTest.java @@ -0,0 +1,39 @@ +package com.baeldung.url; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.junit.Test; + +public class UrlCheckerUnitTest { + + @Test + public void givenValidUrl_WhenUsingHEAD_ThenReturn200() throws IOException { + UrlChecker tester = new UrlChecker(); + int responseCode = tester.getResponseCodeForURLUsingHead("http://www.example.com"); + assertEquals(200, responseCode); + } + + @Test + public void givenInvalidIUrl_WhenUsingHEAD_ThenReturn404() throws IOException { + UrlChecker tester = new UrlChecker(); + int responseCode = tester.getResponseCodeForURLUsingHead("http://www.example.com/unkownurl"); + assertEquals(404, responseCode); + } + + @Test + public void givenValidUrl_WhenUsingGET_ThenReturn200() throws IOException { + UrlChecker tester = new UrlChecker(); + int responseCode = tester.getResponseCodeForURL("http://www.example.com"); + assertEquals(200, responseCode); + } + + @Test + public void givenInvalidIUrl_WhenUsingGET_ThenReturn404() throws IOException { + UrlChecker tester = new UrlChecker(); + int responseCode = tester.getResponseCodeForURL("http://www.example.com/unkownurl"); + assertEquals(404, responseCode); + } + +} diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 11a1003460..2dca62005d 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -17,6 +17,7 @@ pre-jpms core-java-exceptions core-java-optional + core-java-networking-2 diff --git a/java-strings-2/src/test/java/com/baeldung/string/changecase/ToLowerCaseUnitTest.java b/java-strings-2/src/test/java/com/baeldung/string/changecase/ToLowerCaseUnitTest.java new file mode 100644 index 0000000000..c395b61068 --- /dev/null +++ b/java-strings-2/src/test/java/com/baeldung/string/changecase/ToLowerCaseUnitTest.java @@ -0,0 +1,29 @@ +package com.baeldung.string.changecase; + +import static org.junit.Assert.assertEquals; + +import java.util.Locale; + +import org.junit.Test; + +public class ToLowerCaseUnitTest { + + private static final Locale TURKISH = new Locale("tr"); + private String name = "John Doe"; + private String foreignUppercase = "\u0049"; + + @Test + public void givenMixedCaseString_WhenToLowerCase_ThenResultIsLowerCase() { + assertEquals("john doe", name.toLowerCase()); + } + + @Test + public void givenForeignString_WhenToLowerCaseWithoutLocale_ThenResultIsLowerCase() { + assertEquals("\u0069", foreignUppercase.toLowerCase()); + } + + @Test + public void givenForeignString_WhenToLowerCaseWithLocale_ThenResultIsLowerCase() { + assertEquals("\u0131", foreignUppercase.toLowerCase(TURKISH)); + } +} diff --git a/java-strings-2/src/test/java/com/baeldung/string/changecase/ToUpperCaseUnitTest.java b/java-strings-2/src/test/java/com/baeldung/string/changecase/ToUpperCaseUnitTest.java new file mode 100644 index 0000000000..1807f854b2 --- /dev/null +++ b/java-strings-2/src/test/java/com/baeldung/string/changecase/ToUpperCaseUnitTest.java @@ -0,0 +1,29 @@ +package com.baeldung.string.changecase; + +import static org.junit.Assert.assertEquals; + +import java.util.Locale; + +import org.junit.Test; + +public class ToUpperCaseUnitTest { + + private static final Locale TURKISH = new Locale("tr"); + private String name = "John Doe"; + private String foreignLowercase = "\u0069"; + + @Test + public void givenMixedCaseString_WhenToUpperCase_ThenResultIsUpperCase() { + assertEquals("JOHN DOE", name.toUpperCase()); + } + + @Test + public void givenForeignString_WhenToUpperCaseWithoutLocale_ThenResultIsUpperCase() { + assertEquals("\u0049", foreignLowercase.toUpperCase()); + } + + @Test + public void givenForeignString_WhenToUpperCaseWithLocale_ThenResultIsUpperCase() { + assertEquals("\u0130", foreignLowercase.toUpperCase(TURKISH)); + } +} diff --git a/libraries-io/README.md b/libraries-io/README.md index f8ea2f1be8..90095a2f23 100644 --- a/libraries-io/README.md +++ b/libraries-io/README.md @@ -1,4 +1,5 @@ ### Relevant Articles: +- [Transferring a File Through SFTP in Java](https://www.baeldung.com/java-file-sftp) diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/AuthorizationEndpoint.java b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/AuthorizationEndpoint.java index 10e78a2dfd..ba5e1ec359 100644 --- a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/AuthorizationEndpoint.java +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/AuthorizationEndpoint.java @@ -121,8 +121,8 @@ public class AuthorizationEndpoint { String redirectUri = originalParams.getFirst("resolved_redirect_uri"); StringBuilder sb = new StringBuilder(redirectUri); - String approbationStatus = params.getFirst("approbation_status"); - if ("NO".equals(approbationStatus)) { + String approvalStatus = params.getFirst("approval_status"); + if ("NO".equals(approvalStatus)) { URI location = UriBuilder.fromUri(sb.toString()) .queryParam("error", "User doesn't approved the request.") .queryParam("error_description", "User doesn't approved the request.") diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/TokenEndpoint.java b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/TokenEndpoint.java index f39bb2ea2d..0ea12da16e 100644 --- a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/TokenEndpoint.java +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/api/TokenEndpoint.java @@ -15,15 +15,14 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import java.util.Arrays; import java.util.Base64; -import java.util.Collections; import java.util.List; -import java.util.Objects; @Path("token") public class TokenEndpoint { - List supportedGrantTypes = Collections.singletonList("authorization_code"); + List supportedGrantTypes = Arrays.asList("authorization_code", "refresh_token"); @Inject private AppDataRepository appDataRepository; @@ -39,36 +38,36 @@ public class TokenEndpoint { //Check grant_type params String grantType = params.getFirst("grant_type"); - Objects.requireNonNull(grantType, "grant_type params is required"); - if (!supportedGrantTypes.contains(grantType)) { - JsonObject error = Json.createObjectBuilder() - .add("error", "unsupported_grant_type") - .add("error_description", "grant type should be one of :" + supportedGrantTypes) - .build(); - return Response.status(Response.Status.BAD_REQUEST) - .entity(error).build(); + if (grantType == null || grantType.isEmpty()) + return responseError("Invalid_request", "grant_type is required", Response.Status.BAD_REQUEST); + if (!supportedGrantTypes.contains(grantType)) { + return responseError("unsupported_grant_type", "grant_type should be one of :" + supportedGrantTypes, Response.Status.BAD_REQUEST); } //Client Authentication String[] clientCredentials = extract(authHeader); + if (clientCredentials.length != 2) { + return responseError("Invalid_request", "Bad Credentials client_id/client_secret", Response.Status.BAD_REQUEST); + } String clientId = clientCredentials[0]; - String clientSecret = clientCredentials[1]; Client client = appDataRepository.getClient(clientId); - if (client == null || clientSecret == null || !clientSecret.equals(client.getClientSecret())) { - JsonObject error = Json.createObjectBuilder() - .add("error", "invalid_client") - .build(); - return Response.status(Response.Status.UNAUTHORIZED) - .entity(error).build(); + if (client == null) { + return responseError("Invalid_request", "Invalid client_id", Response.Status.BAD_REQUEST); + } + String clientSecret = clientCredentials[1]; + if (!clientSecret.equals(client.getClientSecret())) { + return responseError("Invalid_request", "Invalid client_secret", Response.Status.UNAUTHORIZED); } AuthorizationGrantTypeHandler authorizationGrantTypeHandler = authorizationGrantTypeHandlers.select(NamedLiteral.of(grantType)).get(); JsonObject tokenResponse = null; try { tokenResponse = authorizationGrantTypeHandler.createAccessToken(clientId, params); + } catch (WebApplicationException e) { + return e.getResponse(); } catch (Exception e) { - e.printStackTrace(); + return responseError("Invalid_request", "Can't get token", Response.Status.INTERNAL_SERVER_ERROR); } return Response.ok(tokenResponse) @@ -81,6 +80,15 @@ public class TokenEndpoint { if (authHeader != null && authHeader.startsWith("Basic ")) { return new String(Base64.getDecoder().decode(authHeader.substring(6))).split(":"); } - return null; + return new String[]{}; + } + + private Response responseError(String error, String errorDescription, Response.Status status) { + JsonObject errorResponse = Json.createObjectBuilder() + .add("error", error) + .add("error_description", errorDescription) + .build(); + return Response.status(status) + .entity(errorResponse).build(); } } diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AbstractGrantTypeHandler.java b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AbstractGrantTypeHandler.java new file mode 100644 index 0000000000..324bacb33f --- /dev/null +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AbstractGrantTypeHandler.java @@ -0,0 +1,87 @@ +package com.baeldung.oauth2.authorization.server.handler; + +import com.baeldung.oauth2.authorization.server.PEMKeyUtils; +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.microprofile.config.Config; + +import javax.inject.Inject; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +public abstract class AbstractGrantTypeHandler implements AuthorizationGrantTypeHandler { + + //Always RSA 256, but could be parametrized + protected JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(); + + @Inject + protected Config config; + + //30 min + protected Long expiresInMin = 30L; + + protected JWSVerifier getJWSVerifier() throws Exception { + String verificationkey = config.getValue("verificationkey", String.class); + String pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString(verificationkey); + RSAKey rsaPublicKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPublicKey); + return new RSASSAVerifier(rsaPublicKey); + } + + protected JWSSigner getJwsSigner() throws Exception { + String signingkey = config.getValue("signingkey", String.class); + String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString(signingkey); + RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey); + return new RSASSASigner(rsaKey.toRSAPrivateKey()); + } + + protected String getAccessToken(String clientId, String subject, String approvedScope) throws Exception { + //4. Signing + JWSSigner jwsSigner = getJwsSigner(); + + Instant now = Instant.now(); + //Long expiresInMin = 30L; + Date expirationTime = Date.from(now.plus(expiresInMin, ChronoUnit.MINUTES)); + + //3. JWT Payload or claims + JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() + .issuer("http://localhost:9080") + .subject(subject) + .claim("upn", subject) + .claim("client_id", clientId) + .audience("http://localhost:9280") + .claim("scope", approvedScope) + .claim("groups", Arrays.asList(approvedScope.split(" "))) + .expirationTime(expirationTime) // expires in 30 minutes + .notBeforeTime(Date.from(now)) + .issueTime(Date.from(now)) + .jwtID(UUID.randomUUID().toString()) + .build(); + SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims); + signedJWT.sign(jwsSigner); + return signedJWT.serialize(); + } + + protected String getRefreshToken(String clientId, String subject, String approvedScope) throws Exception { + JWSSigner jwsSigner = getJwsSigner(); + Instant now = Instant.now(); + //6.Build refresh token + JWTClaimsSet refreshTokenClaims = new JWTClaimsSet.Builder() + .subject(subject) + .claim("client_id", clientId) + .claim("scope", approvedScope) + //refresh token for 1 day. + .expirationTime(Date.from(now.plus(1, ChronoUnit.DAYS))) + .build(); + SignedJWT signedRefreshToken = new SignedJWT(jwsHeader, refreshTokenClaims); + signedRefreshToken.sign(jwsSigner); + return signedRefreshToken.serialize(); + } +} diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AuthorizationCodeGrantTypeHandler.java b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AuthorizationCodeGrantTypeHandler.java index 889c7fcea2..78128aead6 100644 --- a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AuthorizationCodeGrantTypeHandler.java +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/AuthorizationCodeGrantTypeHandler.java @@ -1,18 +1,7 @@ package com.baeldung.oauth2.authorization.server.handler; -import com.baeldung.oauth2.authorization.server.PEMKeyUtils; import com.baeldung.oauth2.authorization.server.model.AuthorizationCode; -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import org.eclipse.microprofile.config.Config; -import javax.inject.Inject; import javax.inject.Named; import javax.json.Json; import javax.json.JsonObject; @@ -20,22 +9,14 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MultivaluedMap; -import java.time.Instant; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Date; -import java.util.UUID; @Named("authorization_code") -public class AuthorizationCodeGrantTypeHandler implements AuthorizationGrantTypeHandler { +public class AuthorizationCodeGrantTypeHandler extends AbstractGrantTypeHandler { @PersistenceContext private EntityManager entityManager; - @Inject - private Config config; - @Override public JsonObject createAccessToken(String clientId, MultivaluedMap params) throws Exception { //1. code is required @@ -58,42 +39,16 @@ public class AuthorizationCodeGrantTypeHandler implements AuthorizationGrantType throw new WebApplicationException("invalid_grant"); } - //JWT Header - JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(); - - Instant now = Instant.now(); - Long expiresInMin = 30L; - Date expiresIn = Date.from(now.plus(expiresInMin, ChronoUnit.MINUTES)); - //3. JWT Payload or claims - JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() - .issuer("http://localhost:9080") - .subject(authorizationCode.getUserId()) - .claim("upn", authorizationCode.getUserId()) - .audience("http://localhost:9280") - .claim("scope", authorizationCode.getApprovedScopes()) - .claim("groups", Arrays.asList(authorizationCode.getApprovedScopes().split(" "))) - .expirationTime(expiresIn) // expires in 30 minutes - .notBeforeTime(Date.from(now)) - .issueTime(Date.from(now)) - .jwtID(UUID.randomUUID().toString()) - .build(); - SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims); - - //4. Signing - String signingkey = config.getValue("signingkey", String.class); - String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString(signingkey); - RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey); - signedJWT.sign(new RSASSASigner(rsaKey.toRSAPrivateKey())); - - //5. Finally the JWT access token - String accessToken = signedJWT.serialize(); + String accessToken = getAccessToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes()); + String refreshToken = getRefreshToken(clientId, authorizationCode.getUserId(), authorizationCode.getApprovedScopes()); return Json.createObjectBuilder() .add("token_type", "Bearer") .add("access_token", accessToken) .add("expires_in", expiresInMin * 60) .add("scope", authorizationCode.getApprovedScopes()) + .add("refresh_token", refreshToken) .build(); } } diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/RefreshTokenGrantTypeHandler.java b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/RefreshTokenGrantTypeHandler.java new file mode 100644 index 0000000000..63e3552353 --- /dev/null +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/java/com/baeldung/oauth2/authorization/server/handler/RefreshTokenGrantTypeHandler.java @@ -0,0 +1,72 @@ +package com.baeldung.oauth2.authorization.server.handler; + +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jwt.SignedJWT; + +import javax.inject.Named; +import javax.json.Json; +import javax.json.JsonObject; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +@Named("refresh_token") +public class RefreshTokenGrantTypeHandler extends AbstractGrantTypeHandler { + + @Override + public JsonObject createAccessToken(String clientId, MultivaluedMap params) throws Exception { + String refreshToken = params.getFirst("refresh_token"); + if (refreshToken == null || "".equals(refreshToken)) { + throw new WebApplicationException("invalid_grant"); + } + + //Decode refresh token + SignedJWT signedRefreshToken = SignedJWT.parse(refreshToken); + JWSVerifier verifier = getJWSVerifier(); + + if (!signedRefreshToken.verify(verifier)) { + throw new WebApplicationException("Invalid refresh token."); + } + if (!(new Date().before(signedRefreshToken.getJWTClaimsSet().getExpirationTime()))) { + throw new WebApplicationException("Refresh token expired."); + } + String refreshTokenClientId = signedRefreshToken.getJWTClaimsSet().getStringClaim("client_id"); + if (!clientId.equals(refreshTokenClientId)) { + throw new WebApplicationException("Invalid client_id."); + } + + //At this point, the refresh token is valid and not yet expired + //So create a new access token from it. + String subject = signedRefreshToken.getJWTClaimsSet().getSubject(); + String approvedScopes = signedRefreshToken.getJWTClaimsSet().getStringClaim("scope"); + + String requestedScopes = params.getFirst("scope"); + if (requestedScopes != null && !requestedScopes.isEmpty()) { + Set rScopes = new HashSet(Arrays.asList(requestedScopes.split(" "))); + Set aScopes = new HashSet(Arrays.asList(approvedScopes.split(" "))); + if (!aScopes.containsAll(rScopes)) { + JsonObject error = Json.createObjectBuilder() + .add("error", "Invalid_request") + .add("error_description", "Requested scopes should be a subset of the original scopes.") + .build(); + Response response = Response.status(Response.Status.BAD_REQUEST).entity(error).build(); + throw new WebApplicationException(response); + } + } else { + requestedScopes = approvedScopes; + } + + String accessToken = getAccessToken(clientId, subject, requestedScopes); + return Json.createObjectBuilder() + .add("token_type", "Bearer") + .add("access_token", accessToken) + .add("expires_in", expiresInMin * 60) + .add("scope", requestedScopes) + .add("refresh_token", refreshToken) + .build(); + } +} diff --git a/oauth2-framework-impl/oauth2-authorization-server/src/main/webapp/authorize.jsp b/oauth2-framework-impl/oauth2-authorization-server/src/main/webapp/authorize.jsp index 1004b5b8b7..41b0582c03 100644 --- a/oauth2-framework-impl/oauth2-authorization-server/src/main/webapp/authorize.jsp +++ b/oauth2-framework-impl/oauth2-authorization-server/src/main/webapp/authorize.jsp @@ -41,8 +41,8 @@ - - + + diff --git a/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/AbstractServlet.java b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/AbstractServlet.java new file mode 100644 index 0000000000..7059c4f7e1 --- /dev/null +++ b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/AbstractServlet.java @@ -0,0 +1,23 @@ +package com.baeldung.oauth2.client; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Base64; + +public abstract class AbstractServlet extends HttpServlet { + + protected void dispatch(String location, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + RequestDispatcher requestDispatcher = request.getRequestDispatcher(location); + requestDispatcher.forward(request, response); + } + + protected String getAuthorizationHeaderValue(String clientId, String clientSecret) { + String token = clientId + ":" + clientSecret; + String encodedString = Base64.getEncoder().encodeToString(token.getBytes()); + return "Basic " + encodedString; + } +} diff --git a/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/CallbackServlet.java b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/CallbackServlet.java index 87aa8bc668..e72877076c 100644 --- a/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/CallbackServlet.java +++ b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/CallbackServlet.java @@ -4,10 +4,8 @@ import org.eclipse.microprofile.config.Config; import javax.inject.Inject; import javax.json.JsonObject; -import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.client.Client; @@ -18,10 +16,9 @@ import javax.ws.rs.core.Form; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import java.io.IOException; -import java.util.Base64; @WebServlet(urlPatterns = "/callback") -public class CallbackServlet extends HttpServlet { +public class CallbackServlet extends AbstractServlet { @Inject private Config config; @@ -29,6 +26,9 @@ public class CallbackServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String clientId = config.getValue("client.clientId", String.class); + String clientSecret = config.getValue("client.clientSecret", String.class); + //Error: String error = request.getParameter("error"); if (error != null) { @@ -53,24 +53,15 @@ public class CallbackServlet extends HttpServlet { form.param("code", code); form.param("redirect_uri", config.getValue("client.redirectUri", String.class)); - JsonObject tokenResponse = target.request(MediaType.APPLICATION_JSON_TYPE) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue()) - .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), JsonObject.class); - - request.getSession().setAttribute("tokenResponse", tokenResponse); + try { + JsonObject tokenResponse = target.request(MediaType.APPLICATION_JSON_TYPE) + .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue(clientId, clientSecret)) + .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), JsonObject.class); + request.getSession().setAttribute("tokenResponse", tokenResponse); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + request.setAttribute("error", ex.getMessage()); + } dispatch("/", request, response); } - - private void dispatch(String location, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - RequestDispatcher requestDispatcher = request.getRequestDispatcher(location); - requestDispatcher.forward(request, response); - } - - private String getAuthorizationHeaderValue() { - String clientId = config.getValue("client.clientId", String.class); - String clientSecret = config.getValue("client.clientSecret", String.class); - String token = clientId + ":" + clientSecret; - String encodedString = Base64.getEncoder().encodeToString(token.getBytes()); - return "Basic " + encodedString; - } } diff --git a/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/RefreshTokenServlet.java b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/RefreshTokenServlet.java new file mode 100644 index 0000000000..a519a53070 --- /dev/null +++ b/oauth2-framework-impl/oauth2-client/src/main/java/com/baeldung/oauth2/client/RefreshTokenServlet.java @@ -0,0 +1,57 @@ +package com.baeldung.oauth2.client; + +import org.eclipse.microprofile.config.Config; + +import javax.inject.Inject; +import javax.json.JsonObject; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; + +@WebServlet(urlPatterns = "/refreshtoken") +public class RefreshTokenServlet extends AbstractServlet { + + @Inject + private Config config; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String clientId = config.getValue("client.clientId", String.class); + String clientSecret = config.getValue("client.clientSecret", String.class); + + JsonObject actualTokenResponse = (JsonObject) request.getSession().getAttribute("tokenResponse"); + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(config.getValue("provider.tokenUri", String.class)); + + Form form = new Form(); + form.param("grant_type", "refresh_token"); + form.param("refresh_token", actualTokenResponse.getString("refresh_token")); + + String scope = request.getParameter("scope"); + if (scope != null && !scope.isEmpty()) { + form.param("scope", scope); + } + + Response jaxrsResponse = target.request(MediaType.APPLICATION_JSON_TYPE) + .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeaderValue(clientId, clientSecret)) + .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Response.class); + JsonObject tokenResponse = jaxrsResponse.readEntity(JsonObject.class); + if (jaxrsResponse.getStatus() == 200) { + request.getSession().setAttribute("tokenResponse", tokenResponse); + } else { + request.setAttribute("error", tokenResponse.getString("error_description", "error!")); + } + dispatch("/", request, response); + } +} diff --git a/oauth2-framework-impl/oauth2-client/src/main/webapp/index.jsp b/oauth2-framework-impl/oauth2-client/src/main/webapp/index.jsp index 23fec70f33..ccb74df228 100644 --- a/oauth2-framework-impl/oauth2-client/src/main/webapp/index.jsp +++ b/oauth2-framework-impl/oauth2-client/src/main/webapp/index.jsp @@ -10,6 +10,7 @@ body { margin: 0px; } + input[type=text], input[type=password] { width: 75%; padding: 4px 0px; @@ -17,6 +18,7 @@ border: 1px solid #502bcc; box-sizing: border-box; } + .container-error { padding: 16px; border: 1px solid #cc102a; @@ -25,6 +27,7 @@ margin-left: 25px; margin-bottom: 25px; } + .container { padding: 16px; border: 1px solid #130ecc; @@ -81,8 +84,20 @@
  • access_token: ${tokenResponse.getString("access_token")}
  • scope: ${tokenResponse.getString("scope")}
  • Expires in (s): ${tokenResponse.getInt("expires_in")}
  • +
  • refresh_token: ${tokenResponse.getString("refresh_token")}
  • + + +

    OAuth2 Resource Server Call


    @@ -90,7 +105,6 @@
  • Read Protected Resource
  • Write Protected Resource
  • -
    diff --git a/parent-boot-performance/README.md b/parent-boot-performance/README.md deleted file mode 100644 index fce9e101da..0000000000 --- a/parent-boot-performance/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a parent module for projects that want to take advantage of the latest Spring Boot improvements/features. \ No newline at end of file diff --git a/parent-boot-performance/pom.xml b/parent-boot-performance/pom.xml deleted file mode 100644 index b7d12e2ba4..0000000000 --- a/parent-boot-performance/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - 4.0.0 - parent-boot-performance - 0.0.1-SNAPSHOT - parent-boot-performance - pom - Parent for all modules that want to take advantage of the latest Spring Boot improvements/features. Current version: 2.2 - - - com.baeldung - parent-modules - 1.0.0-SNAPSHOT - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - - - - - io.rest-assured - rest-assured - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - ${start-class} - - - - - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - - - - - thin-jar - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.springframework.boot.experimental - spring-boot-thin-layout - ${thin.version} - - - - - - - - - - 3.1.0 - - 1.0.21.RELEASE - 2.2.0.M3 - - diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml index 390bcc9d51..849c904604 100644 --- a/persistence-modules/pom.xml +++ b/persistence-modules/pom.xml @@ -58,5 +58,6 @@ spring-jpa spring-persistence-simple jpa-hibernate-cascade-type + r2dbc diff --git a/persistence-modules/r2dbc/.gitignore b/persistence-modules/r2dbc/.gitignore new file mode 100644 index 0000000000..a2a3040aa8 --- /dev/null +++ b/persistence-modules/r2dbc/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/persistence-modules/r2dbc/.mvn/wrapper/MavenWrapperDownloader.java b/persistence-modules/r2dbc/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000..72308aa479 --- /dev/null +++ b/persistence-modules/r2dbc/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,114 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.jar b/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..01e6799737 Binary files /dev/null and b/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.jar differ diff --git a/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.properties b/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..cd0d451ccd --- /dev/null +++ b/persistence-modules/r2dbc/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip diff --git a/persistence-modules/r2dbc/README.md b/persistence-modules/r2dbc/README.md new file mode 100644 index 0000000000..02d3cbf5fc --- /dev/null +++ b/persistence-modules/r2dbc/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: +- [R2DBC – Reactive Relational Database Connectivity](https://www.baeldung.com/r2dbc) + diff --git a/persistence-modules/r2dbc/mvnw b/persistence-modules/r2dbc/mvnw new file mode 100644 index 0000000000..8b9da3b8b6 --- /dev/null +++ b/persistence-modules/r2dbc/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/persistence-modules/r2dbc/mvnw.cmd b/persistence-modules/r2dbc/mvnw.cmd new file mode 100644 index 0000000000..fef5a8f7f9 --- /dev/null +++ b/persistence-modules/r2dbc/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/persistence-modules/r2dbc/pom.xml b/persistence-modules/r2dbc/pom.xml new file mode 100644 index 0000000000..388abafdaf --- /dev/null +++ b/persistence-modules/r2dbc/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + org.baeldung.examples.r2dbc + r2dbc-example + 0.0.1-SNAPSHOT + r2dbc-example + Sample R2DBC Project + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + + io.r2dbc + r2dbc-h2 + 0.8.0.M8 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-devtools + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/Account.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/Account.java new file mode 100644 index 0000000000..65bf33168e --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/Account.java @@ -0,0 +1,68 @@ +package org.baeldung.examples.r2dbc; + +import java.math.BigDecimal; + +public class Account { + + private Long id; + private String iban; + private BigDecimal balance; + + + public Account() {} + + public Account(Long id, String iban, BigDecimal balance) { + this.id = id; + this.iban = iban; + this.balance = balance; + } + + public Account(Long id, String iban, Double balance) { + this.id = id; + this.iban = iban; + this.balance = new BigDecimal(balance); + } + + /** + * @return the id + */ + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the iban + */ + public String getIban() { + return iban; + } + + /** + * @param iban the iban to set + */ + public void setIban(String iban) { + this.iban = iban; + } + + /** + * @return the balance + */ + public BigDecimal getBalance() { + return balance; + } + + /** + * @param balance the balance to set + */ + public void setBalance(BigDecimal balance) { + this.balance = balance; + } + +} diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/AccountResource.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/AccountResource.java new file mode 100644 index 0000000000..90816a7522 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/AccountResource.java @@ -0,0 +1,56 @@ +/** + * + */ +package org.baeldung.examples.r2dbc; + +import java.math.BigDecimal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Philippe + * + */ +@RestController +public class AccountResource { + + private final ReactiveAccountDao accountDao; + + public AccountResource(ReactiveAccountDao accountDao) { + this.accountDao = accountDao; + } + + @GetMapping("/accounts/{id}") + public Mono> getAccount(@PathVariable("id") Long id) { + + return accountDao.findById(id) + .map(acc -> new ResponseEntity<>(acc, HttpStatus.OK)) + .switchIfEmpty(Mono.just(new ResponseEntity<>(null, HttpStatus.NOT_FOUND))); + } + + @GetMapping("/accounts") + public Flux getAllAccounts() { + return accountDao.findAll(); + } + + @PostMapping("/accounts") + public Mono> postAccount(@RequestBody Account account) { + return accountDao.createAccount(account) + .map(acc -> new ResponseEntity<>(acc, HttpStatus.CREATED)) + .log(); + } + +} diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/DatasourceConfig.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/DatasourceConfig.java new file mode 100644 index 0000000000..5b695f39f5 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/DatasourceConfig.java @@ -0,0 +1,65 @@ +package org.baeldung.examples.r2dbc; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import io.netty.util.internal.StringUtil; +import io.r2dbc.spi.ConnectionFactories; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryOptions; +import io.r2dbc.spi.ConnectionFactoryOptions.Builder; +import io.r2dbc.spi.Option; +import reactor.core.publisher.Flux; + +import static io.r2dbc.spi.ConnectionFactoryOptions.*; + + +@Configuration +public class DatasourceConfig { + + @Bean + public ConnectionFactory connectionFactory(R2DBCConfigurationProperties properties) { + + ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(properties.getUrl()); + Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); + if ( !StringUtil.isNullOrEmpty(properties.getUser())) { + ob = ob.option(USER, properties.getUser()); + } + + if ( !StringUtil.isNullOrEmpty(properties.getPassword())) { + ob = ob.option(PASSWORD, properties.getPassword()); + } + + ConnectionFactory cf = ConnectionFactories.get(ob.build()); + return cf; + } + + + @Bean + public CommandLineRunner initDatabase(ConnectionFactory cf) { + + return (args) -> + Flux.from(cf.create()) + .flatMap(c -> + Flux.from(c.createBatch() + .add("drop table if exists Account") + .add("create table Account(" + + "id IDENTITY(1,1)," + + "iban varchar(80) not null," + + "balance DECIMAL(18,2) not null)") + .add("insert into Account(iban,balance)" + + "values('BR430120980198201982',100.00)") + .add("insert into Account(iban,balance)" + + "values('BR430120998729871000',250.00)") + .execute()) + .doFinally((st) -> c.close()) + ) + .log() + .blockLast(); + } + + +} + diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2DBCConfigurationProperties.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2DBCConfigurationProperties.java new file mode 100644 index 0000000000..02733ebed9 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2DBCConfigurationProperties.java @@ -0,0 +1,58 @@ +package org.baeldung.examples.r2dbc; + +import javax.validation.constraints.NotEmpty; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "r2dbc") +public class R2DBCConfigurationProperties { + + @NotEmpty + private String url; + + private String user; + private String password; + + /** + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * @param url the url to set + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * @return the user + */ + public String getUser() { + return user; + } + + /** + * @param user the user to set + */ + public void setUser(String user) { + this.user = user; + } + + /** + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2dbcExampleApplication.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2dbcExampleApplication.java new file mode 100644 index 0000000000..1147936e2d --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/R2dbcExampleApplication.java @@ -0,0 +1,17 @@ +package org.baeldung.examples.r2dbc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties(R2DBCConfigurationProperties.class) +public class R2dbcExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(R2dbcExampleApplication.class, args); + } + + + +} diff --git a/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/ReactiveAccountDao.java b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/ReactiveAccountDao.java new file mode 100644 index 0000000000..f9717d4b9b --- /dev/null +++ b/persistence-modules/r2dbc/src/main/java/org/baeldung/examples/r2dbc/ReactiveAccountDao.java @@ -0,0 +1,76 @@ +package org.baeldung.examples.r2dbc; + +import java.math.BigDecimal; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Component +public class ReactiveAccountDao { + + private ConnectionFactory connectionFactory; + + public ReactiveAccountDao(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public Mono findById(long id) { + + return Mono.from(connectionFactory.create()) + .flatMap(c -> Mono.from(c.createStatement("select id,iban,balance from Account where id = $1") + .bind("$1", id) + .execute()) + .doFinally((st) -> close(c))) + .map(result -> result.map((row, meta) -> + new Account(row.get("id", Long.class), + row.get("iban", String.class), + row.get("balance", BigDecimal.class)))) + .flatMap( p -> Mono.from(p)); + } + + public Flux findAll() { + + return Mono.from(connectionFactory.create()) + .flatMap((c) -> Mono.from(c.createStatement("select id,iban,balance from Account") + .execute()) + .doFinally((st) -> close(c))) + .flatMapMany(result -> Flux.from(result.map((row, meta) -> { + Account acc = new Account(); + acc.setId(row.get("id", Long.class)); + acc.setIban(row.get("iban", String.class)); + acc.setBalance(row.get("balance", BigDecimal.class)); + return acc; + }))); + } + + public Mono createAccount(Account account) { + + return Mono.from(connectionFactory.create()) + .flatMap(c -> Mono.from(c.beginTransaction()) + .then(Mono.from(c.createStatement("insert into Account(iban,balance) values($1,$2)") + .bind("$1", account.getIban()) + .bind("$2", account.getBalance()) + .returnGeneratedValues("id") + .execute())) + .map(result -> result.map((row, meta) -> + new Account(row.get("id", Long.class), + account.getIban(), + account.getBalance()))) + .flatMap(pub -> Mono.from(pub)) + .delayUntil(r -> c.commitTransaction()) + .doFinally((st) -> c.close())); + + } + + private Mono close(Connection connection) { + return Mono.from(connection.close()) + .then(Mono.empty()); + } + +} diff --git a/persistence-modules/r2dbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/persistence-modules/r2dbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..55eb5659a7 --- /dev/null +++ b/persistence-modules/r2dbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,5 @@ +{"properties": [{ + "name": "r2dbc", + "type": "org.baeldung.examples.r2dbc.R2DBCConfigurationProperties", + "description": "R2DBC Connection properties" +}]} \ No newline at end of file diff --git a/persistence-modules/r2dbc/src/main/resources/application.yml b/persistence-modules/r2dbc/src/main/resources/application.yml new file mode 100644 index 0000000000..bb47c7261c --- /dev/null +++ b/persistence-modules/r2dbc/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + application: + name: r2dbc-test + +# R2DBC URL +r2dbc: + url: r2dbc:h2:mem://./testdb + + + + + + diff --git a/persistence-modules/r2dbc/src/test/java/org/baeldung/examples/r2dbc/R2dbcExampleApplicationTests.java b/persistence-modules/r2dbc/src/test/java/org/baeldung/examples/r2dbc/R2dbcExampleApplicationTests.java new file mode 100644 index 0000000000..a1d433847b --- /dev/null +++ b/persistence-modules/r2dbc/src/test/java/org/baeldung/examples/r2dbc/R2dbcExampleApplicationTests.java @@ -0,0 +1,102 @@ +package org.baeldung.examples.r2dbc; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.hamcrest.core.IsNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import io.r2dbc.spi.ConnectionFactory; +import reactor.core.publisher.Flux; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class R2dbcExampleApplicationTests { + + + @Autowired + private WebTestClient webTestClient; + + @Autowired + ConnectionFactory cf; + + @Before + public void initDatabase() { + + Flux.from(cf.create()) + .flatMap(c -> + c.createBatch() + .add("drop table if exists Account") + .add("create table Account(id IDENTITY(1,1), iban varchar(80) not null, balance DECIMAL(18,2) not null)") + .add("insert into Account(iban,balance) values ( 'BR430120980198201982', 100.00 ) ") + .add("insert into Account(iban,balance) values ( 'BR430120998729871000', 250.00 ) ") + .execute() + ) + .log() + .blockLast(); + } + + @Test + public void givenExistingAccountId_whenGetAccount_thenReturnExistingAccountInfo() { + + webTestClient + .get() + .uri("/accounts/1") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .isOk() + .expectBody(Account.class) + .value((acc) -> { + assertThat(acc.getId(),is(1l)); + }); + } + + @Test + public void givenDatabaseHasSomeAccounts_whenGetAccount_thenReturnExistingAccounts() { + + webTestClient + .get() + .uri("/accounts") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .isOk() + .expectBody(List.class) + .value((accounts) -> { + assertThat(accounts.size(),not(is(0))); + }); + } + + + @Test + public void givenNewAccountData_whenPostAccount_thenReturnNewAccountInfo() { + + webTestClient + .post() + .uri("/accounts") + .syncBody(new Account(null,"BR4303010298012098", 151.00 )) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is2xxSuccessful() + .expectBody(Account.class) + .value((acc) -> { + assertThat(acc.getId(),is(notNullValue())); + }); + + } + +} diff --git a/persistence-modules/r2dbc/src/test/resources/application.yml b/persistence-modules/r2dbc/src/test/resources/application.yml new file mode 100644 index 0000000000..8925116e4a --- /dev/null +++ b/persistence-modules/r2dbc/src/test/resources/application.yml @@ -0,0 +1,6 @@ +# R2DBC Test configuration +r2dbc: + url: r2dbc:h2:mem://./testdb + + + diff --git a/persistence-modules/spring-boot-mysql/.gitignore b/persistence-modules/spring-boot-mysql/.gitignore new file mode 100644 index 0000000000..96136ab255 --- /dev/null +++ b/persistence-modules/spring-boot-mysql/.gitignore @@ -0,0 +1,5 @@ +/target/ +.settings/ +.classpath +.project +.mvn/ \ No newline at end of file diff --git a/persistence-modules/spring-boot-mysql/pom.xml b/persistence-modules/spring-boot-mysql/pom.xml new file mode 100644 index 0000000000..6be58332aa --- /dev/null +++ b/persistence-modules/spring-boot-mysql/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + spring-boot-mysql + 0.1.0 + spring-boot-mysql + + + parent-boot-2 + com.baeldung + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + 2.1.5.RELEASE + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-devtools + true + + + mysql + mysql-connector-java + + + + + spring-boot-mysql-timezone + + + src/main/resources + true + + + + + + + + + UTF-8 + 8.0.12 + + + diff --git a/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Application.java b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Application.java new file mode 100644 index 0000000000..d69f66fc7a --- /dev/null +++ b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Application.java @@ -0,0 +1,20 @@ +package com.baeldung.boot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import javax.annotation.PostConstruct; +import java.util.TimeZone; + +@SpringBootApplication +public class Application { + + @PostConstruct + void started() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Controller.java b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Controller.java new file mode 100644 index 0000000000..48864269fe --- /dev/null +++ b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/Controller.java @@ -0,0 +1,27 @@ +package com.baeldung.boot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +public class Controller { + + @Autowired + UserRepository userRepository; + + @GetMapping + public User get() { + User user = new User(); + userRepository.save(user); + return user; + } + + @GetMapping("/find") + public List find() { + List users = userRepository.findAll(); + return users; + } +} diff --git a/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/User.java b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/User.java new file mode 100644 index 0000000000..55cb97dec2 --- /dev/null +++ b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/User.java @@ -0,0 +1,40 @@ +package com.baeldung.boot; + + +import org.springframework.data.annotation.CreatedDate; + +import javax.persistence.*; +import java.util.Date; + +@Entity +public class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + @Column + private String name; + + @CreatedDate + private Date createdDate = new Date(); + + public Date getCreatedDate() { + return createdDate; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/UserRepository.java b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/UserRepository.java new file mode 100644 index 0000000000..3c50c30cce --- /dev/null +++ b/persistence-modules/spring-boot-mysql/src/main/java/com/baeldung/boot/UserRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.boot; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.io.Serializable; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/persistence-modules/spring-boot-mysql/src/main/resources/application.yml b/persistence-modules/spring-boot-mysql/src/main/resources/application.yml new file mode 100644 index 0000000000..5404555d49 --- /dev/null +++ b/persistence-modules/spring-boot-mysql/src/main/resources/application.yml @@ -0,0 +1,14 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/test?useLegacyDatetimeCode=false + username: root + password: + + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL5Dialect + jdbc: + time_zone: UTC \ No newline at end of file diff --git a/pom.xml b/pom.xml index b888994a43..c46a9a5d5a 100644 --- a/pom.xml +++ b/pom.xml @@ -561,7 +561,7 @@ tensorflow-java spring-boot-flowable spring-security-kerberos - + oauth2-framework-impl @@ -614,6 +614,7 @@ spring-5-data-reactive spring-5-mvc spring-5-reactive + spring-5-reactive-2 spring-5-reactive-client spring-5-reactive-oauth spring-5-reactive-security @@ -663,22 +664,23 @@ spring-boot-vue spring-boot-libraries - spring-cloud - spring-cloud-bus + + + - spring-cloud-data-flow + spring-core spring-core-2 spring-cucumber spring-data-rest - spring-data-rest-querydsl + spring-dispatcher-servlet spring-drools spring-ehcache - spring-ejb + spring-exceptions spring-freemarker @@ -741,7 +743,7 @@ spring-security-mvc-ldap spring-security-mvc-login spring-security-mvc-persisted-remember-me - spring-security-mvc-session + spring-security-mvc spring-security-mvc-socket spring-security-openid @@ -752,7 +754,7 @@ spring-security-stormpath spring-security-thymeleaf spring-security-x509 - spring-session + spring-sleuth spring-soap spring-social-login @@ -763,10 +765,10 @@ spring-thymeleaf - spring-vault + spring-vertx - spring-webflux-amqp + spring-zuul @@ -825,6 +827,7 @@ spring-5 spring-5-data-reactive spring-5-reactive + spring-5-reactive-2 spring-5-reactive-client spring-5-reactive-security spring-5-security @@ -916,7 +919,7 @@ spring-security-mvc-digest-auth spring-security-mvc-ldap spring-security-mvc-persisted-remember-me - spring-security-mvc-session + spring-security-mvc spring-security-mvc-socket spring-security-rest spring-security-sso @@ -1241,6 +1244,7 @@ rsocket rxjava rxjava-2 + oauth2-framework-impl @@ -1285,6 +1289,7 @@ spring-5-data-reactive spring-5-mvc spring-5-reactive + spring-5-reactive-2 spring-5-reactive-client spring-5-reactive-oauth spring-5-reactive-security @@ -1407,7 +1412,7 @@ spring-security-mvc-ldap spring-security-mvc-login spring-security-mvc-persisted-remember-me - spring-security-mvc-session + spring-security-mvc spring-security-mvc-socket spring-security-openid @@ -1456,7 +1461,6 @@ wicket xml - xmlunit-2 xstream diff --git a/quarkus/src/test/java/com/baeldung/quarkus/HelloResourceTest.java b/quarkus/src/test/java/com/baeldung/quarkus/HelloResourceUnitTest.java similarity index 91% rename from quarkus/src/test/java/com/baeldung/quarkus/HelloResourceTest.java rename to quarkus/src/test/java/com/baeldung/quarkus/HelloResourceUnitTest.java index 3d0fff7562..a5ed99f585 100644 --- a/quarkus/src/test/java/com/baeldung/quarkus/HelloResourceTest.java +++ b/quarkus/src/test/java/com/baeldung/quarkus/HelloResourceUnitTest.java @@ -7,7 +7,7 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; @QuarkusTest -public class HelloResourceTest { +public class HelloResourceUnitTest { @Test public void testHelloEndpoint() { diff --git a/quarkus/src/test/java/com/baeldung/quarkus/NativeHelloResourceIT.java b/quarkus/src/test/java/com/baeldung/quarkus/NativeHelloResourceIT.java index 4b0606f588..9ada64b6a5 100644 --- a/quarkus/src/test/java/com/baeldung/quarkus/NativeHelloResourceIT.java +++ b/quarkus/src/test/java/com/baeldung/quarkus/NativeHelloResourceIT.java @@ -3,7 +3,7 @@ package com.baeldung.quarkus; import io.quarkus.test.junit.SubstrateTest; @SubstrateTest -public class NativeHelloResourceIT extends HelloResourceTest { +public class NativeHelloResourceIT extends HelloResourceUnitTest { // Execute the same tests but in native mode. } \ No newline at end of file diff --git a/spring-5-reactive-2/.gitignore b/spring-5-reactive-2/.gitignore new file mode 100644 index 0000000000..dec013dfa4 --- /dev/null +++ b/spring-5-reactive-2/.gitignore @@ -0,0 +1,12 @@ +#folders# +.idea +/target +/neoDb* +/data +/src/main/webapp/WEB-INF/classes +*/META-INF/* + +# Packaged files # +*.jar +*.war +*.ear \ No newline at end of file diff --git a/spring-5-reactive-2/README.md b/spring-5-reactive-2/README.md new file mode 100644 index 0000000000..bbb45e9f8c --- /dev/null +++ b/spring-5-reactive-2/README.md @@ -0,0 +1 @@ +## Spring 5 Reactive Project diff --git a/spring-5-reactive-2/pom.xml b/spring-5-reactive-2/pom.xml new file mode 100644 index 0000000000..668e0e5cf9 --- /dev/null +++ b/spring-5-reactive-2/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + com.baeldung + spring-5-reactive-2 + 0.0.1-SNAPSHOT + spring-5-reactive-2 + jar + spring 5 sample project about new features + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.baeldung.webclient.WebClientApplication + JAR + + + + + + diff --git a/spring-5-reactive-2/src/main/java/com/baeldung/webclient/Tweet.java b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/Tweet.java new file mode 100644 index 0000000000..8d294955f3 --- /dev/null +++ b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/Tweet.java @@ -0,0 +1,13 @@ +package com.baeldung.webclient; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Tweet { + private String text; + private String username; +} diff --git a/spring-5-reactive-2/src/main/java/com/baeldung/webclient/TweetsSlowServiceController.java b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/TweetsSlowServiceController.java new file mode 100644 index 0000000000..fecaca25ef --- /dev/null +++ b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/TweetsSlowServiceController.java @@ -0,0 +1,20 @@ +package com.baeldung.webclient; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.List; + +@RestController +public class TweetsSlowServiceController { + + @GetMapping("/slow-service-tweets") + private List getAllTweets() throws Exception { + Thread.sleep(2000L); // delay + return Arrays.asList( + new Tweet("RestTemplate rules", "@user1"), + new Tweet("WebClient is better", "@user2"), + new Tweet("OK, both are useful", "@user1")); + } +} diff --git a/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebClientApplication.java b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebClientApplication.java new file mode 100644 index 0000000000..751e3a9487 --- /dev/null +++ b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebClientApplication.java @@ -0,0 +1,11 @@ +package com.baeldung.webclient; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebClientApplication { + public static void main(String[] args) { + SpringApplication.run(WebClientApplication.class, args); + } +} diff --git a/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebController.java b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebController.java new file mode 100644 index 0000000000..73f5724819 --- /dev/null +++ b/spring-5-reactive-2/src/main/java/com/baeldung/webclient/WebController.java @@ -0,0 +1,60 @@ +package com.baeldung.webclient; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; + +import java.util.List; + +@Slf4j +@RestController +public class WebController { + + private static final int DEFAULT_PORT = 8080; + + @Setter + private int serverPort = DEFAULT_PORT; + + @GetMapping("/tweets-blocking") + public List getTweetsBlocking() { + log.info("Starting BLOCKING Controller!"); + final String uri = getSlowServiceUri(); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity> response = restTemplate.exchange( + uri, HttpMethod.GET, null, + new ParameterizedTypeReference>(){}); + + List result = response.getBody(); + result.forEach(tweet -> log.info(tweet.toString())); + log.info("Exiting BLOCKING Controller!"); + return result; + } + + @GetMapping(value = "/tweets-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux getTweetsNonBlocking() { + log.info("Starting NON-BLOCKING Controller!"); + Flux tweetFlux = WebClient.create() + .get() + .uri(getSlowServiceUri()) + .retrieve() + .bodyToFlux(Tweet.class); + + tweetFlux.subscribe(tweet -> log.info(tweet.toString())); + log.info("Exiting NON-BLOCKING Controller!"); + return tweetFlux; + } + + private String getSlowServiceUri() { + return "http://localhost:" + serverPort + "/slow-service-tweets"; + } + +} diff --git a/spring-5-reactive-2/src/test/java/com/baeldung/webclient/WebControllerIntegrationTest.java b/spring-5-reactive-2/src/test/java/com/baeldung/webclient/WebControllerIntegrationTest.java new file mode 100644 index 0000000000..09c3a5fb84 --- /dev/null +++ b/spring-5-reactive-2/src/test/java/com/baeldung/webclient/WebControllerIntegrationTest.java @@ -0,0 +1,51 @@ +package com.baeldung.webclient; + +import org.junit.Before; +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.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WebClientApplication.class) +public class WebControllerIntegrationTest { + + @LocalServerPort + int randomServerPort; + + @Autowired + private WebTestClient testClient; + + @Autowired + private WebController webController; + + @Before + public void setup() { + webController.setServerPort(randomServerPort); + } + + @Test + public void whenEndpointWithBlockingClientIsCalled_thenThreeTweetsAreReceived() { + testClient.get() + .uri("/tweets-blocking") + .exchange() + .expectStatus() + .isOk() + .expectBodyList(Tweet.class) + .hasSize(3); + } + + @Test + public void whenEndpointWithNonBlockingClientIsCalled_thenThreeTweetsAreReceived() { + testClient.get() + .uri("/tweets-non-blocking") + .exchange() + .expectStatus() + .isOk() + .expectBodyList(Tweet.class) + .hasSize(3); + } +} \ No newline at end of file diff --git a/spring-boot-autoconfiguration/pom.xml b/spring-boot-autoconfiguration/pom.xml index 91692ebfff..5b3b0eb86c 100644 --- a/spring-boot-autoconfiguration/pom.xml +++ b/spring-boot-autoconfiguration/pom.xml @@ -38,6 +38,19 @@ mysql mysql-connector-java
    + + + org.springframework.boot + spring-boot-configuration-processor + 2.1.6.RELEASE + true + + + + org.hsqldb + hsqldb + runtime + @@ -50,12 +63,10 @@ - org.apache.maven.plugins maven-war-plugin - diff --git a/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/AnnotationProcessorApplication.java b/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/AnnotationProcessorApplication.java new file mode 100644 index 0000000000..91ea94e43e --- /dev/null +++ b/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/AnnotationProcessorApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.autoconfiguration.annotationprocessor; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.ComponentScan; + +import com.baeldung.autoconfiguration.MySQLAutoconfiguration; + +@EnableAutoConfiguration(exclude = { MySQLAutoconfiguration.class}) +@ComponentScan(basePackageClasses = {DatabaseProperties.class}) +public class AnnotationProcessorApplication { + public static void main(String[] args) { + new SpringApplicationBuilder(AnnotationProcessorApplication.class).run(); + } +} diff --git a/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/DatabaseProperties.java b/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/DatabaseProperties.java new file mode 100644 index 0000000000..4fb5b408a2 --- /dev/null +++ b/spring-boot-autoconfiguration/src/main/java/com/baeldung/autoconfiguration/annotationprocessor/DatabaseProperties.java @@ -0,0 +1,73 @@ +package com.baeldung.autoconfiguration.annotationprocessor; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "database") +public class DatabaseProperties { + + public static class Server { + + /** + * The IP of the database server + */ + private String ip; + + /** + * The Port of the database server. + * The Default value is 443. + * The allowed values are in the range 400-4000. + */ + @Min(400) + @Max(800) + private int port = 443; + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + } + + private String username; + private String password; + private Server server; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } +} \ No newline at end of file diff --git a/spring-boot-autoconfiguration/src/test/java/com/baeldung/autoconfiguration/annotationprocessor/DatabasePropertiesIntegrationTest.java b/spring-boot-autoconfiguration/src/test/java/com/baeldung/autoconfiguration/annotationprocessor/DatabasePropertiesIntegrationTest.java new file mode 100644 index 0000000000..350e65b465 --- /dev/null +++ b/spring-boot-autoconfiguration/src/test/java/com/baeldung/autoconfiguration/annotationprocessor/DatabasePropertiesIntegrationTest.java @@ -0,0 +1,31 @@ +package com.baeldung.autoconfiguration.annotationprocessor; + +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.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = AnnotationProcessorApplication.class) +@TestPropertySource("classpath:databaseproperties-test.properties") +public class DatabasePropertiesIntegrationTest { + + @Autowired + private DatabaseProperties databaseProperties; + + @Test + public void whenSimplePropertyQueriedThenReturnsPropertyValue() throws Exception { + Assert.assertEquals("Incorrectly bound Username property", "baeldung", databaseProperties.getUsername()); + Assert.assertEquals("Incorrectly bound Password property", "password", databaseProperties.getPassword()); + } + + @Test + public void whenNestedPropertyQueriedThenReturnsPropertyValue() throws Exception { + Assert.assertEquals("Incorrectly bound Server IP nested property", "127.0.0.1", databaseProperties.getServer().getIp()); + Assert.assertEquals("Incorrectly bound Server Port nested property", 3306, databaseProperties.getServer().getPort()); + } + +} diff --git a/spring-boot-autoconfiguration/src/test/resources/databaseproperties-test.properties b/spring-boot-autoconfiguration/src/test/resources/databaseproperties-test.properties new file mode 100644 index 0000000000..c0d1d1f158 --- /dev/null +++ b/spring-boot-autoconfiguration/src/test/resources/databaseproperties-test.properties @@ -0,0 +1,7 @@ +#Simple Properties +database.username=baeldung +database.password=password + +#Nested Properties +database.server.ip=127.0.0.1 +database.server.port=3306 \ No newline at end of file diff --git a/spring-boot-performance/pom.xml b/spring-boot-performance/pom.xml index f51df8bc0c..a4efa4a8d7 100644 --- a/spring-boot-performance/pom.xml +++ b/spring-boot-performance/pom.xml @@ -8,10 +8,10 @@ This is a simple Spring Boot application taking advantage of the latest Spring Boot improvements/features. Current version: 2.2 - parent-boot-performance + parent-boot-2 com.baeldung 0.0.1-SNAPSHOT - ../parent-boot-performance + ../parent-boot-2 @@ -42,4 +42,4 @@ com.baeldung.lazyinitialization.Application - \ No newline at end of file + diff --git a/spring-boot-properties/.gitignore b/spring-boot-properties/.gitignore new file mode 100644 index 0000000000..0532ef1888 --- /dev/null +++ b/spring-boot-properties/.gitignore @@ -0,0 +1,17 @@ +*.class + +#folders# +/target +/neoDb* +/data +/src/main/webapp/WEB-INF/classes +*/META-INF/* + +# Packaged files # +*.jar +*.war +*.ear + +*.ipr +*.iml +*.iws diff --git a/spring-boot-properties/README.md b/spring-boot-properties/README.md new file mode 100644 index 0000000000..c43cf4865c --- /dev/null +++ b/spring-boot-properties/README.md @@ -0,0 +1,2 @@ +### Relevant Articles: +- [Reloading Properties in Spring](https://www.baeldung.com/reloading-properties-files-in-spring/) \ No newline at end of file diff --git a/spring-boot-properties/extra.properties b/spring-boot-properties/extra.properties new file mode 100644 index 0000000000..8e28a6f889 --- /dev/null +++ b/spring-boot-properties/extra.properties @@ -0,0 +1 @@ +application.theme.color=blue \ No newline at end of file diff --git a/spring-boot-properties/extra2.properties b/spring-boot-properties/extra2.properties new file mode 100644 index 0000000000..2c46edc584 --- /dev/null +++ b/spring-boot-properties/extra2.properties @@ -0,0 +1 @@ +application.theme.background=red \ No newline at end of file diff --git a/spring-boot-properties/pom.xml b/spring-boot-properties/pom.xml new file mode 100644 index 0000000000..27ac48252b --- /dev/null +++ b/spring-boot-properties/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + spring-boot-properties + jar + spring-boot-properties + Spring Boot Properties Module + + + parent-boot-2 + com.baeldung + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + commons-configuration + commons-configuration + ${commons-configuration.version} + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + spring-boot-properties + + + src/main/resources + true + + + + + + + integration + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*LiveTest.java + **/*IntegrationTest.java + **/*IntTest.java + + + + + + + json + + + + + + + + + + 1.8 + Greenwich.SR1 + 1.10 + + + diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/SpringBootPropertiesApplication.java b/spring-boot-properties/src/main/java/com/baeldung/properties/SpringBootPropertiesApplication.java new file mode 100644 index 0000000000..67bbddf9f1 --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/SpringBootPropertiesApplication.java @@ -0,0 +1,44 @@ +package com.baeldung.properties; + +import com.baeldung.properties.configs.ReloadableProperties; +import java.io.File; +import java.util.Properties; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@SpringBootApplication +public class SpringBootPropertiesApplication { + + @Bean + @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false) + public PropertiesConfiguration propertiesConfiguration( + @Value("${spring.config.location}") String path, + @Value("${spring.properties.refreshDelay}") long refreshDelay) throws Exception { + String filePath = path.substring("file:".length()); + PropertiesConfiguration configuration = new PropertiesConfiguration(new File(filePath).getCanonicalPath()); + FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy(); + fileChangedReloadingStrategy.setRefreshDelay(refreshDelay); + configuration.setReloadingStrategy(fileChangedReloadingStrategy); + return configuration; + } + + @Bean + @ConditionalOnBean(PropertiesConfiguration.class) + @Primary + public Properties properties(PropertiesConfiguration propertiesConfiguration) throws Exception { + ReloadableProperties properties = new ReloadableProperties(propertiesConfiguration); + return properties; + } + + public static void main(String[] args) { + SpringApplication.run(SpringBootPropertiesApplication.class, args); + } + +} diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/configs/PropertiesException.java b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/PropertiesException.java new file mode 100644 index 0000000000..5ec3a042f7 --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/PropertiesException.java @@ -0,0 +1,10 @@ +package com.baeldung.properties.configs; + +public class PropertiesException extends RuntimeException { + public PropertiesException() { + } + + public PropertiesException(Throwable cause) { + super(cause); + } +} diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadableProperties.java b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadableProperties.java new file mode 100644 index 0000000000..33d503b9bd --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadableProperties.java @@ -0,0 +1,49 @@ +package com.baeldung.properties.configs; + +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Properties; +import javax.naming.OperationNotSupportedException; +import org.apache.commons.configuration.PropertiesConfiguration; + +public class ReloadableProperties extends Properties { + private PropertiesConfiguration propertiesConfiguration; + + public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException { + super.load(new FileReader(propertiesConfiguration.getFile())); + this.propertiesConfiguration = propertiesConfiguration; + } + + @Override + public synchronized Object setProperty(String key, String value) { + propertiesConfiguration.setProperty(key, value); + return super.setProperty(key, value); + } + + @Override + public String getProperty(String key) { + String val = propertiesConfiguration.getString(key); + super.setProperty(key, val); + return val; + } + + @Override + public String getProperty(String key, String defaultValue) { + String val = propertiesConfiguration.getString(key, defaultValue); + super.setProperty(key, val); + return val; + } + + @Override + public synchronized void load(Reader reader) throws IOException { + throw new PropertiesException(new OperationNotSupportedException()); + } + + @Override + public synchronized void load(InputStream inStream) throws IOException { + throw new PropertiesException(new OperationNotSupportedException()); + } + +} diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySource.java b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySource.java new file mode 100644 index 0000000000..8a0cef955f --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySource.java @@ -0,0 +1,33 @@ +package com.baeldung.properties.configs; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; +import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; + +public class ReloadablePropertySource extends PropertySource { + + PropertiesConfiguration propertiesConfiguration; + + public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) { + super(name); + this.propertiesConfiguration = propertiesConfiguration; + } + + public ReloadablePropertySource(String name, String path) { + super(StringUtils.isEmpty(name) ? path : name); + try { + this.propertiesConfiguration = new PropertiesConfiguration(path); + FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy(); + strategy.setRefreshDelay(1000); + this.propertiesConfiguration.setReloadingStrategy(strategy); + } catch (Exception e) { + throw new PropertiesException(e); + } + } + + @Override + public Object getProperty(String s) { + return propertiesConfiguration.getProperty(s); + } +} diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceConfig.java b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceConfig.java new file mode 100644 index 0000000000..37e1a04839 --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceConfig.java @@ -0,0 +1,29 @@ +package com.baeldung.properties.configs; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; + +@Configuration +public class ReloadablePropertySourceConfig { + + private ConfigurableEnvironment env; + + public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) { + this.env = env; + } + + @Bean + @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false) + public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) { + ReloadablePropertySource ret = new ReloadablePropertySource("dynamic", properties); + MutablePropertySources sources = env.getPropertySources(); + sources.addFirst(ret); + return ret; + } + +} diff --git a/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceFactory.java b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceFactory.java new file mode 100644 index 0000000000..574362b3bf --- /dev/null +++ b/spring-boot-properties/src/main/java/com/baeldung/properties/configs/ReloadablePropertySourceFactory.java @@ -0,0 +1,25 @@ +package com.baeldung.properties.configs; + +import java.io.IOException; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.FileUrlResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.DefaultPropertySourceFactory; +import org.springframework.core.io.support.EncodedResource; + +public class ReloadablePropertySourceFactory extends DefaultPropertySourceFactory { + @Override + public PropertySource createPropertySource(String s, EncodedResource encodedResource) throws IOException { + Resource internal = encodedResource.getResource(); + if (internal instanceof FileSystemResource) { + return new ReloadablePropertySource(s, ((FileSystemResource) internal).getPath()); + } + if (internal instanceof FileUrlResource) { + return new ReloadablePropertySource(s, ((FileUrlResource) internal) + .getURL() + .getPath()); + } + return super.createPropertySource(s, encodedResource); + } +} diff --git a/spring-boot-properties/src/main/resources/application.properties b/spring-boot-properties/src/main/resources/application.properties new file mode 100644 index 0000000000..f976004a04 --- /dev/null +++ b/spring-boot-properties/src/main/resources/application.properties @@ -0,0 +1,3 @@ +management.endpoints.web.exposure.include=refresh +spring.properties.refreshDelay=1000 +spring.config.location=file:extra.properties diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/PropertiesReloadIntegrationTest.java b/spring-boot-properties/src/test/java/com/baeldung/properties/PropertiesReloadIntegrationTest.java new file mode 100644 index 0000000000..a73311ded8 --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/PropertiesReloadIntegrationTest.java @@ -0,0 +1,161 @@ +package com.baeldung.properties; + +import com.baeldung.properties.beans.ConfigurationPropertiesRefreshConfigBean; +import com.baeldung.properties.beans.EnvironmentConfigBean; +import com.baeldung.properties.beans.PropertiesConfigBean; +import com.baeldung.properties.beans.ValueRefreshConfigBean; +import java.io.FileOutputStream; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = SpringBootPropertiesTestApplication.class) +public class PropertiesReloadIntegrationTest { + + protected MockMvc mvc; + + protected long refreshDelay = 3000; + + @Autowired + WebApplicationContext webApplicationContext; + + @Autowired + ValueRefreshConfigBean valueRefreshConfigBean; + + @Autowired + ConfigurationPropertiesRefreshConfigBean configurationPropertiesRefreshConfigBean; + + @Autowired + EnvironmentConfigBean environmentConfigBean; + + @Autowired + PropertiesConfigBean propertiesConfigBean; + + @Autowired + @Qualifier("singletonValueRefreshConfigBean") + ValueRefreshConfigBean singletonValueRefreshConfigBean; + + + @Before + public void setUp() throws Exception { + mvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .build(); + createConfig("extra.properties", "application.theme.color", "blue"); + createConfig("extra2.properties", "application.theme.background", "red"); + Thread.sleep(refreshDelay); + callRefresh(); + } + + @After + public void tearDown() throws Exception { + createConfig("extra.properties", "application.theme.color", "blue"); + createConfig("extra2.properties", "application.theme.background", "red"); + } + + @Test + public void givenEnvironmentReader_whenColorChanged_thenExpectChangeValue() throws Exception { + Assert.assertEquals("blue", environmentConfigBean.getColor()); + + createConfig("extra.properties", "application.theme.color", "red"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("red", environmentConfigBean.getColor()); + } + + @Test + public void givenEnvironmentReader_whenBackgroundChanged_thenExpectChangeValue() throws Exception { + Assert.assertEquals("red", environmentConfigBean.getBackgroundColor()); + + createConfig("extra2.properties", "application.theme.background", "blue"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("blue", environmentConfigBean.getBackgroundColor()); + } + + @Test + public void givenPropertiesReader_whenColorChanged_thenExpectChangeValue() throws Exception { + Assert.assertEquals("blue", propertiesConfigBean.getColor()); + + createConfig("extra.properties", "application.theme.color", "red"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("red", propertiesConfigBean.getColor()); + } + + @Test + public void givenRefreshScopedValueReader_whenColorChangedAndRefreshCalled_thenExpectChangeValue() throws Exception { + Assert.assertEquals("blue", valueRefreshConfigBean.getColor()); + + createConfig("extra.properties", "application.theme.color", "red"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("blue", valueRefreshConfigBean.getColor()); + + callRefresh(); + + Assert.assertEquals("red", valueRefreshConfigBean.getColor()); + } + + @Test + public void givenSingletonRefreshScopedValueReader_whenColorChangedAndRefreshCalled_thenExpectOldValue() throws Exception { + + Assert.assertEquals("blue", singletonValueRefreshConfigBean.getColor()); + + createConfig("extra.properties", "application.theme.color", "red"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("blue", singletonValueRefreshConfigBean.getColor()); + + callRefresh(); + + Assert.assertEquals("blue", singletonValueRefreshConfigBean.getColor()); + } + + @Test + public void givenRefreshScopedConfigurationPropertiesReader_whenColorChangedAndRefreshCalled_thenExpectChangeValue() throws Exception { + + Assert.assertEquals("blue", configurationPropertiesRefreshConfigBean.getColor()); + + createConfig("extra.properties", "application.theme.color", "red"); + Thread.sleep(refreshDelay); + + Assert.assertEquals("blue", configurationPropertiesRefreshConfigBean.getColor()); + + callRefresh(); + + Assert.assertEquals("red", configurationPropertiesRefreshConfigBean.getColor()); + } + + public void callRefresh() throws Exception { + MvcResult mvcResult = mvc + .perform(MockMvcRequestBuilders + .post("/actuator/refresh") + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andReturn(); + MockHttpServletResponse response = mvcResult.getResponse(); + Assert.assertEquals(response.getStatus(), 200); + } + + public void createConfig(String file, String key, String value) throws Exception { + FileOutputStream fo = new FileOutputStream(file); + fo.write(String + .format("%s=%s", key, value) + .getBytes()); + fo.close(); + } +} diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/SpringBootPropertiesTestApplication.java b/spring-boot-properties/src/test/java/com/baeldung/properties/SpringBootPropertiesTestApplication.java new file mode 100644 index 0000000000..c572a6c053 --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/SpringBootPropertiesTestApplication.java @@ -0,0 +1,28 @@ +package com.baeldung.properties; + +import com.baeldung.properties.beans.ValueRefreshConfigBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.web.WebAppConfiguration; + +@SpringBootApplication +@WebAppConfiguration +public class SpringBootPropertiesTestApplication { + + @Bean("singletonValueRefreshConfigBean") + @RefreshScope + @Scope("singleton") + public ValueRefreshConfigBean singletonValueRefreshConfigBean(@Value("${application.theme.color:null}") String val) { + return new ValueRefreshConfigBean(val); + } + + @Bean + @RefreshScope + public ValueRefreshConfigBean valueRefreshConfigBean(@Value("${application.theme.color:null}") String val) { + return new ValueRefreshConfigBean(val); + } + +} diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ConfigurationPropertiesRefreshConfigBean.java b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ConfigurationPropertiesRefreshConfigBean.java new file mode 100644 index 0000000000..31f168fdcc --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ConfigurationPropertiesRefreshConfigBean.java @@ -0,0 +1,20 @@ +package com.baeldung.properties.beans; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "application.theme") +@RefreshScope +public class ConfigurationPropertiesRefreshConfigBean { + private String color; + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/beans/EnvironmentConfigBean.java b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/EnvironmentConfigBean.java new file mode 100644 index 0000000000..fef12f8656 --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/EnvironmentConfigBean.java @@ -0,0 +1,26 @@ +package com.baeldung.properties.beans; + +import com.baeldung.properties.configs.ReloadablePropertySourceFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +@PropertySource(value = "file:extra2.properties", factory = ReloadablePropertySourceFactory.class) +public class EnvironmentConfigBean { + + private Environment environment; + + public EnvironmentConfigBean(@Autowired Environment environment) { + this.environment = environment; + } + + public String getColor() { + return environment.getProperty("application.theme.color"); + } + + public String getBackgroundColor() { + return environment.getProperty("application.theme.background"); + } +} diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/beans/PropertiesConfigBean.java b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/PropertiesConfigBean.java new file mode 100644 index 0000000000..da773b283a --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/PropertiesConfigBean.java @@ -0,0 +1,19 @@ +package com.baeldung.properties.beans; + +import java.util.Properties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class PropertiesConfigBean { + + private Properties properties; + + public PropertiesConfigBean(@Autowired Properties properties) { + this.properties = properties; + } + + public String getColor() { + return properties.getProperty("application.theme.color"); + } +} diff --git a/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ValueRefreshConfigBean.java b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ValueRefreshConfigBean.java new file mode 100644 index 0000000000..d806938092 --- /dev/null +++ b/spring-boot-properties/src/test/java/com/baeldung/properties/beans/ValueRefreshConfigBean.java @@ -0,0 +1,13 @@ +package com.baeldung.properties.beans; + +public class ValueRefreshConfigBean { + private String color; + + public ValueRefreshConfigBean(String color) { + this.color = color; + } + + public String getColor() { + return color; + } +} diff --git a/spring-boot-properties/src/test/resources/application.properties b/spring-boot-properties/src/test/resources/application.properties new file mode 100644 index 0000000000..6fc241a106 --- /dev/null +++ b/spring-boot-properties/src/test/resources/application.properties @@ -0,0 +1,3 @@ +management.endpoints.web.exposure.include=refresh +spring.properties.refreshDelay=1000 +spring.config.location=file:extra.properties \ No newline at end of file diff --git a/spring-boot-testing/pom.xml b/spring-boot-testing/pom.xml index 84107070bd..12fb14b366 100644 --- a/spring-boot-testing/pom.xml +++ b/spring-boot-testing/pom.xml @@ -15,6 +15,10 @@ + + org.springframework.boot + spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-web @@ -33,6 +37,13 @@ test + + + it.ozimov + embedded-redis + 0.7.2 + test + org.spockframework diff --git a/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisConfiguration.java b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisConfiguration.java new file mode 100644 index 0000000000..6b5b20892f --- /dev/null +++ b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisConfiguration.java @@ -0,0 +1,24 @@ +package com.baeldung.boot.embeddedRedis.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; + +@Configuration +@EnableRedisRepositories +public class RedisConfiguration { + + @Bean + public LettuceConnectionFactory redisConnectionFactory(final RedisProperties redisProperties) { + return new LettuceConnectionFactory(redisProperties.getRedisHost(), redisProperties.getRedisPort()); + } + + @Bean + public RedisTemplate redisTemplate(final LettuceConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + return template; + } +} diff --git a/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisProperties.java b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisProperties.java new file mode 100644 index 0000000000..76f2e1bbfe --- /dev/null +++ b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/configuration/RedisProperties.java @@ -0,0 +1,23 @@ +package com.baeldung.boot.embeddedRedis.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedisProperties { + private final int redisPort; + private final String redisHost; + + public RedisProperties(@Value("${spring.redis.port}") final int redisPort, @Value("${spring.redis.host}") final String redisHost) { + this.redisPort = redisPort; + this.redisHost = redisHost; + } + + public int getRedisPort() { + return redisPort; + } + + public String getRedisHost() { + return redisHost; + } +} diff --git a/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/User.java b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/User.java new file mode 100644 index 0000000000..41657aaa66 --- /dev/null +++ b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/User.java @@ -0,0 +1,25 @@ +package com.baeldung.boot.embeddedRedis.domain; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +import java.util.UUID; + +@RedisHash("user") +public class User { + @Id private UUID id; + private String name; + + public User(UUID id, String name) { + this.id = id; + this.name = name; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepository.java b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepository.java new file mode 100644 index 0000000000..0558bb8482 --- /dev/null +++ b/spring-boot-testing/src/main/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.boot.embeddedRedis.domain.repository; + +import com.baeldung.boot.embeddedRedis.domain.User; +import org.springframework.data.repository.CrudRepository; + +import java.util.UUID; + +public interface UserRepository extends CrudRepository { + +} diff --git a/spring-boot-testing/src/main/resources/application.properties b/spring-boot-testing/src/main/resources/application.properties index e378aacdd5..8dc7f6e3c3 100644 --- a/spring-boot-testing/src/main/resources/application.properties +++ b/spring-boot-testing/src/main/resources/application.properties @@ -1,3 +1,6 @@ +# embedded redis +spring.redis.host= localhost +spring.redis.port= 6379 # security spring.security.user.name=john diff --git a/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/TestRedisConfiguration.java b/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/TestRedisConfiguration.java new file mode 100644 index 0000000000..b4748bcd38 --- /dev/null +++ b/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/TestRedisConfiguration.java @@ -0,0 +1,28 @@ +package com.baeldung.boot.embeddedRedis; + +import com.baeldung.boot.embeddedRedis.configuration.RedisProperties; +import org.springframework.boot.test.context.TestConfiguration; +import redis.embedded.RedisServer; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@TestConfiguration +public class TestRedisConfiguration { + + private final RedisServer redisServer; + + public TestRedisConfiguration(final RedisProperties redisProperties) { + this.redisServer = new RedisServer(redisProperties.getRedisPort()); + } + + @PostConstruct + public void postConstruct() { + redisServer.start(); + } + + @PreDestroy + public void preDestroy() { + redisServer.stop(); + } +} diff --git a/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepositoryIntegrationTest.java b/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepositoryIntegrationTest.java new file mode 100644 index 0000000000..9577ccf0e8 --- /dev/null +++ b/spring-boot-testing/src/test/java/com/baeldung/boot/embeddedRedis/domain/repository/UserRepositoryIntegrationTest.java @@ -0,0 +1,31 @@ +package com.baeldung.boot.embeddedRedis.domain.repository; + +import com.baeldung.boot.embeddedRedis.TestRedisConfiguration; +import com.baeldung.boot.embeddedRedis.domain.User; +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.test.context.junit4.SpringRunner; + +import java.util.UUID; + +import static org.junit.Assert.assertNotNull; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestRedisConfiguration.class) +public class UserRepositoryIntegrationTest { + + @Autowired + private UserRepository userRepository; + + @Test + public void shouldSaveUser_toRedis() { + final UUID id = UUID.randomUUID(); + final User user = new User(id, "name"); + + final User saved = userRepository.save(user); + + assertNotNull(saved); + } +} \ No newline at end of file diff --git a/spring-boot-testing/src/test/resources/application.properties b/spring-boot-testing/src/test/resources/application.properties index 9a8b496a8b..0c5b0e13e6 100644 --- a/spring-boot-testing/src/test/resources/application.properties +++ b/spring-boot-testing/src/test/resources/application.properties @@ -1,3 +1,6 @@ +#embedded redis +spring.redis.host= localhost +spring.redis.port= 6370 # security spring.security.user.name=john spring.security.user.password=123 \ No newline at end of file diff --git a/spring-cloud/spring-cloud-vault/pom.xml b/spring-cloud/spring-cloud-vault/pom.xml index 9a1be3f5c0..faf27b6ac8 100644 --- a/spring-cloud/spring-cloud-vault/pom.xml +++ b/spring-cloud/spring-cloud-vault/pom.xml @@ -42,6 +42,18 @@ org.springframework.boot spring-boot-starter-jdbc + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + diff --git a/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountRepo.java b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountRepo.java new file mode 100644 index 0000000000..318cc84957 --- /dev/null +++ b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountRepo.java @@ -0,0 +1,10 @@ +package org.baeldung.spring.cloud.vaultsample; + +import org.baeldung.spring.cloud.vaultsample.domain.Account; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AccountRepo extends JpaRepository{ + +} diff --git a/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountResource.java b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountResource.java new file mode 100644 index 0000000000..8fcaa9e2aa --- /dev/null +++ b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/AccountResource.java @@ -0,0 +1,27 @@ +package org.baeldung.spring.cloud.vaultsample; + +import org.baeldung.spring.cloud.vaultsample.domain.Account; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +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.RestController; + +@RestController +public class AccountResource { + @Autowired + private AccountRepo repo; + + @GetMapping("/account/{id}") + public ResponseEntity getAccount(@PathVariable("id") Long id) { + + Account acc = repo.findById(id).orElse(null); + if ( acc != null ) { + return new ResponseEntity(acc, HttpStatus.OK); + } + else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } +} diff --git a/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/SecretResource.java b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/SecretResource.java new file mode 100644 index 0000000000..c4579da045 --- /dev/null +++ b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/SecretResource.java @@ -0,0 +1,37 @@ +/** + * + */ +package org.baeldung.spring.cloud.vaultsample; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +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.RestController; + +/** + * @author Philippe + * + */ +@RestController +public class SecretResource { + + @Autowired + Environment env; + + @GetMapping("/secret/{key}") + public ResponseEntity readSecret(@PathVariable("key") String key) { + + String value = env.getProperty(key); + + if ( value != null ) { + return new ResponseEntity(value, HttpStatus.OK); + } + else { + return new ResponseEntity("not found", HttpStatus.NOT_FOUND); + } + } +} diff --git a/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/domain/Account.java b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/domain/Account.java new file mode 100644 index 0000000000..df4778831b --- /dev/null +++ b/spring-cloud/spring-cloud-vault/src/main/java/org/baeldung/spring/cloud/vaultsample/domain/Account.java @@ -0,0 +1,133 @@ +/** + * + */ +package org.baeldung.spring.cloud.vaultsample.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * @author Philippe + * + */ +@Entity +@Table(name = "account") +public class Account { + + @Id + private Long id; + + @NotNull + private String name; + + private Long branchId; + private Long customerId; + + /** + * @return the id + */ + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the branchId + */ + public Long getBranchId() { + return branchId; + } + + /** + * @param branchId the branchId to set + */ + public void setBranchId(Long branchId) { + this.branchId = branchId; + } + + /** + * @return the customerId + */ + public Long getCustomerId() { + return customerId; + } + + /** + * @param customerId the customerId to set + */ + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((branchId == null) ? 0 : branchId.hashCode()); + result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Account other = (Account) obj; + if (branchId == null) { + if (other.branchId != null) + return false; + } else if (!branchId.equals(other.branchId)) + return false; + if (customerId == null) { + if (other.customerId != null) + return false; + } else if (!customerId.equals(other.customerId)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + +} diff --git a/spring-cloud/spring-cloud-vault/src/main/resources/application.yml b/spring-cloud/spring-cloud-vault/src/main/resources/application.yml index 3d347ec855..1c75ac21e6 100644 --- a/spring-cloud/spring-cloud-vault/src/main/resources/application.yml +++ b/spring-cloud/spring-cloud-vault/src/main/resources/application.yml @@ -1,6 +1,11 @@ -spring: - application: - name: fakebank - - datasource: - url: jdbc:mysql://localhost:3306/fakebank +spring: + application: + name: fakebank + + datasource: + url: jdbc:mysql://localhost:3306/fakebank?serverTimezone=GMT-3 + hikari: connection-test-query: select 1 + idle-timeout: 5000 + max-lifetime: 120000 + maximum-pool-size: 5 + minimum-idle: 5 diff --git a/spring-cloud/spring-cloud-vault/src/main/resources/bootstrap.yml b/spring-cloud/spring-cloud-vault/src/main/resources/bootstrap.yml index 1e837c4920..7d38b06c0f 100644 --- a/spring-cloud/spring-cloud-vault/src/main/resources/bootstrap.yml +++ b/spring-cloud/spring-cloud-vault/src/main/resources/bootstrap.yml @@ -4,8 +4,6 @@ spring: uri: https://localhost:8200 connection-timeout: 5000 read-timeout: 15000 - config: - order: -10 ssl: trust-store: classpath:/vault.jks @@ -15,17 +13,19 @@ spring: enabled: true application-name: fakebank - kv: - enabled: false - backend: kv - application-name: fakebank - +# kv: +# enabled: false +# backend: kv +# application-name: fakebank +# database: enabled: true role: fakebank-accounts-rw -# backend: database -# username-property: spring.datasource.username -# password-property: spring.datasource.password + backend: database + username-property: spring.datasource.username + password-property: spring.datasource.password +# +# diff --git a/spring-cloud/spring-cloud-vault/src/test/vault-config/vault-test.hcl b/spring-cloud/spring-cloud-vault/src/test/vault-config/vault-test.hcl index c880f2d744..d16665a744 100644 --- a/spring-cloud/spring-cloud-vault/src/test/vault-config/vault-test.hcl +++ b/spring-cloud/spring-cloud-vault/src/test/vault-config/vault-test.hcl @@ -17,4 +17,7 @@ listener "tcp" { tls_key_file = "./src/test/vault-config/localhost.key" } +// Audit to stdout + + diff --git a/spring-cloud/spring-cloud-vault/vault-cheatsheet.txt b/spring-cloud/spring-cloud-vault/vault-cheatsheet.txt index b965a95321..2d5f2363ac 100644 --- a/spring-cloud/spring-cloud-vault/vault-cheatsheet.txt +++ b/spring-cloud/spring-cloud-vault/vault-cheatsheet.txt @@ -5,16 +5,13 @@ 2. Open another shell and execute the command below: > vault operator init -Vault will output the unseal keys and root token: STORE THEM SAFELY !!! +Unseal Key 1: Iwvpd4IVofhcmQ2HEIPs5HMUbz4tz6JhqmLZ6+1MhAPx +Unseal Key 2: ANQDXUFGGtLtt6grX25YsdmeKELhM/ioKWzwFukJIe2f +Unseal Key 3: 8MHyzFnOvlwVQzdWYJ3BIN4xPDOn8a4VemZ/Qe5HgurU +Unseal Key 4: ywT9YR9OfxIpA4l1RniNNCvSZWAuNZsAEFRyD7aqFOrp +Unseal Key 5: q1c7M+lnlT72jGLoCH+jjri6KGSBhc5lCzlT0I1R9URU -Example output: -Unseal Key 1: OfCseaSZzjTZmrxhfx+5clKobwLGCNiJdAlfixSG9E3o -Unseal Key 2: iplVLPTHW0n0WL5XuI6QWwyNtWbKTek1SoKcG0gR7vdT -Unseal Key 3: K0TleK3OYUvWFF+uIDsQuf5a+/gkv1PtZ3O47ornzRoF -Unseal Key 4: +5zhysLAO4hIdZs0kiZpkrRovw11uQacfloiBwnZBJA/ -Unseal Key 5: GDwSq18lXV3Cw4MoHsKIH137kuI0mdl36UiD9WxOdulc - -Initial Root Token: d341fdaf-1cf9-936a-3c38-cf5eec94b5c0 +Initial Root Token: dee7107a-8819-0719-62a3-cea3ea854589 ... @@ -73,8 +70,8 @@ flush privileges; > vault write database/roles/fakebank-accounts-rw ^ db_name=mysql-fakebank ^ creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT,INSERT,UPDATE ON fakebank.* TO '{{name}}'@'%';" ^ - default_ttl="1m" ^ - max_ttl="2m" + default_ttl="5m" ^ + max_ttl="30m" === Get credentials > vault read database/creds/fakebank-accounts-rw diff --git a/spring-cloud/spring-cloud-vault/vault-unseal.bat b/spring-cloud/spring-cloud-vault/vault-unseal.bat index 8133f90892..1e3f229fcf 100644 --- a/spring-cloud/spring-cloud-vault/vault-unseal.bat +++ b/spring-cloud/spring-cloud-vault/vault-unseal.bat @@ -1,7 +1,7 @@ call %~dp0%/vault-env.bat -vault operator unseal OfCseaSZzjTZmrxhfx+5clKobwLGCNiJdAlfixSG9E3o -vault operator unseal iplVLPTHW0n0WL5XuI6QWwyNtWbKTek1SoKcG0gR7vdT -vault operator unseal iplVLPTHW0n0WL5XuI6QWwyNtWbKTek1SoKcG0gR7vdT -vault operator unseal K0TleK3OYUvWFF+uIDsQuf5a+/gkv1PtZ3O47ornzRoF +vault operator unseal Iwvpd4IVofhcmQ2HEIPs5HMUbz4tz6JhqmLZ6+1MhAPx +vault operator unseal ANQDXUFGGtLtt6grX25YsdmeKELhM/ioKWzwFukJIe2f +vault operator unseal 8MHyzFnOvlwVQzdWYJ3BIN4xPDOn8a4VemZ/Qe5HgurU + diff --git a/spring-quartz/pom.xml b/spring-quartz/pom.xml index 58e72c1d51..4c7ac6eee9 100644 --- a/spring-quartz/pom.xml +++ b/spring-quartz/pom.xml @@ -32,6 +32,16 @@ quartz ${quartz.version} + + com.mchange + c3p0 + ${c3p0.version} + + + + com.h2database + h2 + @@ -44,7 +54,8 @@ - 2.2.3 + 2.3.0 + 0.9.5.2 \ No newline at end of file diff --git a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/QrtzScheduler.java b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/QrtzScheduler.java index 6601df6c94..ccf9fca8c1 100644 --- a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/QrtzScheduler.java +++ b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/QrtzScheduler.java @@ -10,17 +10,20 @@ import javax.annotation.PostConstruct; import org.baeldung.springquartz.config.AutoWiringSpringBeanJobFactory; import org.quartz.*; -import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SpringBeanJobFactory; +import java.util.Properties; + @Configuration @ConditionalOnExpression("'${using.spring.schedulerFactory}'=='false'") public class QrtzScheduler { @@ -45,14 +48,9 @@ public class QrtzScheduler { } @Bean - public Scheduler scheduler(Trigger trigger, JobDetail job) throws SchedulerException, IOException { - - StdSchedulerFactory factory = new StdSchedulerFactory(); - factory.initialize(new ClassPathResource("quartz.properties").getInputStream()); - + public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory) throws SchedulerException { logger.debug("Getting a handle to the Scheduler"); Scheduler scheduler = factory.getScheduler(); - scheduler.setJobFactory(springBeanJobFactory()); scheduler.scheduleJob(job, trigger); logger.debug("Starting Scheduler threads"); @@ -60,6 +58,21 @@ public class QrtzScheduler { return scheduler; } + @Bean + public SchedulerFactoryBean schedulerFactoryBean() throws IOException { + SchedulerFactoryBean factory = new SchedulerFactoryBean(); + factory.setJobFactory(springBeanJobFactory()); + factory.setQuartzProperties(quartzProperties()); + return factory; + } + + public Properties quartzProperties() throws IOException { + PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); + propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); + propertiesFactoryBean.afterPropertiesSet(); + return propertiesFactoryBean.getObject(); + } + @Bean public JobDetail jobDetail() { diff --git a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/SpringQrtzScheduler.java b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/SpringQrtzScheduler.java index 9978f61522..f824765efe 100644 --- a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/SpringQrtzScheduler.java +++ b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/scheduler/SpringQrtzScheduler.java @@ -1,6 +1,7 @@ package org.baeldung.springquartz.basics.scheduler; import javax.annotation.PostConstruct; +import javax.sql.DataSource; import org.baeldung.springquartz.config.AutoWiringSpringBeanJobFactory; import org.quartz.JobDetail; @@ -9,7 +10,11 @@ import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.quartz.QuartzDataSource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,6 +25,7 @@ import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.scheduling.quartz.SpringBeanJobFactory; @Configuration +@EnableAutoConfiguration @ConditionalOnExpression("'${using.spring.schedulerFactory}'=='true'") public class SpringQrtzScheduler { @@ -43,7 +49,7 @@ public class SpringQrtzScheduler { } @Bean - public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job) { + public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties")); @@ -53,6 +59,9 @@ public class SpringQrtzScheduler { schedulerFactory.setJobDetails(job); schedulerFactory.setTriggers(trigger); + // Comment the following line to use the default Quartz job store. + schedulerFactory.setDataSource(quartzDataSource); + return schedulerFactory; } @@ -81,4 +90,12 @@ public class SpringQrtzScheduler { trigger.setName("Qrtz_Trigger"); return trigger; } + + @Bean + @QuartzDataSource + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource quartzDataSource() { + return DataSourceBuilder.create().build(); + } + } diff --git a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/service/SampleJobService.java b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/service/SampleJobService.java index ddf4efc2c5..6f66352616 100644 --- a/spring-quartz/src/main/java/org/baeldung/springquartz/basics/service/SampleJobService.java +++ b/spring-quartz/src/main/java/org/baeldung/springquartz/basics/service/SampleJobService.java @@ -4,20 +4,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.concurrent.atomic.AtomicInteger; + @Service public class SampleJobService { + public static final long EXECUTION_TIME = 5000L; + private Logger logger = LoggerFactory.getLogger(getClass()); + private AtomicInteger count = new AtomicInteger(); + public void executeSampleJob() { logger.info("The sample job has begun..."); try { - Thread.sleep(5000); + Thread.sleep(EXECUTION_TIME); } catch (InterruptedException e) { logger.error("Error while executing sample job", e); } finally { + count.incrementAndGet(); logger.info("Sample job has finished..."); } } + + public int getNumberOfInvocations() { + return count.get(); + } } diff --git a/spring-quartz/src/main/resources/application.properties b/spring-quartz/src/main/resources/application.properties index 7bdd647e25..557349af2e 100644 --- a/spring-quartz/src/main/resources/application.properties +++ b/spring-quartz/src/main/resources/application.properties @@ -1 +1,10 @@ -using.spring.schedulerFactory=true \ No newline at end of file +using.spring.schedulerFactory=true + +spring.quartz.job-store-type=jdbc +# Always create the Quartz database on startup +spring.quartz.jdbc.initialize-schema=always + +spring.datasource.jdbc-url=jdbc:h2:mem:spring-quartz;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= diff --git a/spring-quartz/src/main/resources/quartz.properties b/spring-quartz/src/main/resources/quartz.properties index cefaaef8e4..662bb83eb0 100644 --- a/spring-quartz/src/main/resources/quartz.properties +++ b/spring-quartz/src/main/resources/quartz.properties @@ -4,7 +4,19 @@ org.quartz.threadPool.threadCount=2 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true # job-store -org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore +# Enable this property for RAMJobStore +#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore -# others -org.quartz.jobStore.misfireThreshold = 60000 \ No newline at end of file +# Enable these properties for a JDBCJobStore using JobStoreTX +org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX +org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +org.quartz.jobStore.dataSource=quartzDataSource +# Enable this property for JobStoreCMT +#org.quartz.jobStore.nonManagedTXDataSource=quartzDataSource + +# H2 database +# use an in-memory database & initialise Quartz using their standard SQL script +org.quartz.dataSource.quartzDataSource.URL=jdbc:h2:mem:spring-quartz;INIT=RUNSCRIPT FROM 'classpath:/org/quartz/impl/jdbcjobstore/tables_h2.sql' +org.quartz.dataSource.quartzDataSource.driver=org.h2.Driver +org.quartz.dataSource.quartzDataSource.user=sa +org.quartz.dataSource.quartzDataSource.password= diff --git a/spring-quartz/src/test/java/org/baeldung/SpringContextIntegrationTest.java b/spring-quartz/src/test/java/org/baeldung/SpringContextIntegrationTest.java index 516cc587a7..fec47f045c 100644 --- a/spring-quartz/src/test/java/org/baeldung/SpringContextIntegrationTest.java +++ b/spring-quartz/src/test/java/org/baeldung/SpringContextIntegrationTest.java @@ -1,16 +1,30 @@ package org.baeldung; import org.baeldung.springquartz.SpringQuartzApp; +import org.baeldung.springquartz.basics.service.SampleJobService; 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.test.context.junit4.SpringRunner; +import static org.assertj.core.api.Assertions.assertThat; + @RunWith(SpringRunner.class) @SpringBootTest(classes = SpringQuartzApp.class) public class SpringContextIntegrationTest { + @Autowired + private SampleJobService sampleJobService; + @Test public void whenSpringContextIsBootstrapped_thenNoExceptions() { } + + @Test + public void whenSchedulerStarts_thenJobsRun() throws InterruptedException { + assertThat(sampleJobService.getNumberOfInvocations()).isEqualTo(0); + Thread.sleep(SampleJobService.EXECUTION_TIME); + assertThat(sampleJobService.getNumberOfInvocations()).isEqualTo(1); + } } diff --git a/spring-rest-simple/src/test/java/org/baeldung/SpringContextTest.java b/spring-rest-simple/src/test/java/org/baeldung/SpringContextTest.java index 0afb3899db..5e68a8e64f 100644 --- a/spring-rest-simple/src/test/java/org/baeldung/SpringContextTest.java +++ b/spring-rest-simple/src/test/java/org/baeldung/SpringContextTest.java @@ -1,11 +1,12 @@ package org.baeldung; -import org.baeldung.config.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import com.baeldung.Application; + @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class SpringContextTest { diff --git a/spring-security-mvc-session/pom.xml b/spring-security-mvc-session/pom.xml deleted file mode 100644 index b55ce70517..0000000000 --- a/spring-security-mvc-session/pom.xml +++ /dev/null @@ -1,125 +0,0 @@ - - 4.0.0 - com.baeldung - spring-security-mvc-session - 0.1-SNAPSHOT - spring-security-mvc-session - war - - - parent-boot-2 - com.baeldung - 0.0.1-SNAPSHOT - ../parent-boot-2 - - - - - - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-taglibs - - - - - org.springframework.boot - spring-boot-starter-web - - - org.apache.tomcat.embed - tomcat-embed-jasper - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - javax.servlet - javax.servlet-api - provided - - - - javax.servlet - jstl - runtime - - - - - - com.codahale.metrics - metrics-core - ${codahale.metrics.version} - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - spring-security-mvc-session - - - src/main/resources - true - - - - - - - org.apache.maven.plugins - maven-war-plugin - ${maven-war-plugin.version} - - - - org.codehaus.cargo - cargo-maven2-plugin - ${cargo-maven2-plugin.version} - - true - - jetty8x - embedded - - - - - - - 8082 - - - - - - - - - - - - 3.0.2 - - - 1.6.1 - - - \ No newline at end of file diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/spring/MvcConfig.java b/spring-security-mvc-session/src/main/java/org/baeldung/spring/MvcConfig.java deleted file mode 100644 index b9f50ded73..0000000000 --- a/spring-security-mvc-session/src/main/java/org/baeldung/spring/MvcConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.baeldung.spring; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.view.InternalResourceViewResolver; -import org.springframework.web.servlet.view.JstlView; - -@EnableWebMvc -@Configuration -public class MvcConfig implements WebMvcConfigurer { - - public MvcConfig() { - super(); - } - - // API - - @Override - public void addViewControllers(final ViewControllerRegistry registry) { - - registry.addViewController("/anonymous.html"); - - registry.addViewController("/login.html"); - registry.addViewController("/homepage.html"); - registry.addViewController("/sessionExpired.html"); - registry.addViewController("/invalidExpired.html"); - registry.addViewController("/console.html"); - } - - @Bean - public ViewResolver viewResolver() { - final InternalResourceViewResolver bean = new InternalResourceViewResolver(); - - bean.setViewClass(JstlView.class); - bean.setPrefix("/WEB-INF/view/"); - bean.setSuffix(".jsp"); - - return bean; - } -} \ No newline at end of file diff --git a/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextIntegrationTest.java b/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextIntegrationTest.java deleted file mode 100644 index 9e74e83a53..0000000000 --- a/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextIntegrationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.baeldung; - -import org.baeldung.spring.MvcConfig; -import org.baeldung.spring.SecSecurityConfig; -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.web.WebAppConfiguration; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { MvcConfig.class, SecSecurityConfig.class }) -@WebAppConfiguration -public class SpringContextIntegrationTest { - - @Test - public void whenSpringContextIsBootstrapped_thenNoExceptions() { - } -} diff --git a/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextTest.java b/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextTest.java deleted file mode 100644 index 5ee80d856a..0000000000 --- a/spring-security-mvc-session/src/test/java/org/baeldung/SpringContextTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.baeldung; - -import org.baeldung.spring.MvcConfig; -import org.baeldung.spring.SecSecurityConfig; -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.web.WebAppConfiguration; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { MvcConfig.class, SecSecurityConfig.class }) -@WebAppConfiguration -public class SpringContextTest { - - @Test - public void whenSpringContextIsBootstrapped_thenNoExceptions() { - } -} diff --git a/spring-security-mvc-session/.gitignore b/spring-security-mvc/.gitignore similarity index 100% rename from spring-security-mvc-session/.gitignore rename to spring-security-mvc/.gitignore diff --git a/spring-security-mvc-session/README.md b/spring-security-mvc/README.md similarity index 100% rename from spring-security-mvc-session/README.md rename to spring-security-mvc/README.md diff --git a/spring-security-mvc/pom.xml b/spring-security-mvc/pom.xml new file mode 100644 index 0000000000..98ad3daa46 --- /dev/null +++ b/spring-security-mvc/pom.xml @@ -0,0 +1,84 @@ + + 4.0.0 + com.baeldung + spring-security-mvc + 0.1-SNAPSHOT + spring-security-mvc + jar + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-taglibs + + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + org.springframework.boot + spring-boot-starter-tomcat + + + + javax.servlet + jstl + runtime + + + + + io.dropwizard.metrics + metrics-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.baeldung.SpringSessionApplication + JAR + + + + + + diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/SpringSessionApplication.java b/spring-security-mvc/src/main/java/com/baeldung/SpringSessionApplication.java similarity index 93% rename from spring-security-mvc-session/src/main/java/org/baeldung/SpringSessionApplication.java rename to spring-security-mvc/src/main/java/com/baeldung/SpringSessionApplication.java index 9e52f0430a..c2a4b35df0 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/SpringSessionApplication.java +++ b/spring-security-mvc/src/main/java/com/baeldung/SpringSessionApplication.java @@ -1,4 +1,4 @@ -package org.baeldung; +package com.baeldung; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/monitoring/MetricRegistrySingleton.java b/spring-security-mvc/src/main/java/com/baeldung/monitoring/MetricRegistrySingleton.java similarity index 95% rename from spring-security-mvc-session/src/main/java/org/baeldung/monitoring/MetricRegistrySingleton.java rename to spring-security-mvc/src/main/java/com/baeldung/monitoring/MetricRegistrySingleton.java index ed253305ed..e2224996c2 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/monitoring/MetricRegistrySingleton.java +++ b/spring-security-mvc/src/main/java/com/baeldung/monitoring/MetricRegistrySingleton.java @@ -1,4 +1,4 @@ -package org.baeldung.monitoring; +package com.baeldung.monitoring; import java.util.concurrent.TimeUnit; diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java b/spring-security-mvc/src/main/java/com/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java similarity index 99% rename from spring-security-mvc-session/src/main/java/org/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java rename to spring-security-mvc/src/main/java/com/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java index 19f49ea59d..9d4fc19098 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java +++ b/spring-security-mvc/src/main/java/com/baeldung/security/MySimpleUrlAuthenticationSuccessHandler.java @@ -1,4 +1,4 @@ -package org.baeldung.security; +package com.baeldung.security; import java.io.IOException; import java.util.Collection; diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/security/SessionFilter.java b/spring-security-mvc/src/main/java/com/baeldung/security/SessionFilter.java similarity index 97% rename from spring-security-mvc-session/src/main/java/org/baeldung/security/SessionFilter.java rename to spring-security-mvc/src/main/java/com/baeldung/security/SessionFilter.java index d37d46e478..f4f876af9c 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/security/SessionFilter.java +++ b/spring-security-mvc/src/main/java/com/baeldung/security/SessionFilter.java @@ -1,4 +1,4 @@ -package org.baeldung.security; +package com.baeldung.security; import java.io.IOException; import java.util.Arrays; diff --git a/spring-security-mvc/src/main/java/com/baeldung/spring/MvcConfig.java b/spring-security-mvc/src/main/java/com/baeldung/spring/MvcConfig.java new file mode 100644 index 0000000000..38a4f3f81b --- /dev/null +++ b/spring-security-mvc/src/main/java/com/baeldung/spring/MvcConfig.java @@ -0,0 +1,33 @@ +package com.baeldung.spring; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + registry.addViewController("/anonymous.html"); + + registry.addViewController("/login.html"); + registry.addViewController("/homepage.html"); + registry.addViewController("/sessionExpired.html"); + registry.addViewController("/invalidSession.html"); + registry.addViewController("/console.html"); + } + + + /* + * Spring Boot supports configuring a ViewResolver with properties + */ +// @Bean +// public ViewResolver viewResolver() { +// final InternalResourceViewResolver bean = new InternalResourceViewResolver(); +// +// bean.setViewClass(JstlView.class); +// bean.setPrefix("/WEB-INF/view/"); +// bean.setSuffix(".jsp"); +// } +} diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/spring/SecSecurityConfig.java b/spring-security-mvc/src/main/java/com/baeldung/spring/SecSecurityConfig.java similarity index 91% rename from spring-security-mvc-session/src/main/java/org/baeldung/spring/SecSecurityConfig.java rename to spring-security-mvc/src/main/java/com/baeldung/spring/SecSecurityConfig.java index b7996ebf18..a922ba6f7f 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/spring/SecSecurityConfig.java +++ b/spring-security-mvc/src/main/java/com/baeldung/spring/SecSecurityConfig.java @@ -1,11 +1,9 @@ -package org.baeldung.spring; +package com.baeldung.spring; -import org.baeldung.security.MySimpleUrlAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -13,9 +11,10 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.session.HttpSessionEventPublisher; +import com.baeldung.security.MySimpleUrlAuthenticationSuccessHandler; + @Configuration // @ImportResource({ "classpath:webSecurityConfig.xml" }) -@EnableWebSecurity public class SecSecurityConfig extends WebSecurityConfigurerAdapter { public SecSecurityConfig() { @@ -39,7 +38,7 @@ public class SecSecurityConfig extends WebSecurityConfigurerAdapter { .csrf().disable() .authorizeRequests() .antMatchers("/anonymous*").anonymous() - .antMatchers("/login*").permitAll() + .antMatchers("/login*","/invalidSession*", "/sessionExpired*").permitAll() .anyRequest().authenticated() .and() .formLogin() @@ -70,7 +69,7 @@ public class SecSecurityConfig extends WebSecurityConfigurerAdapter { public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } - + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); diff --git a/spring-security-mvc-session/src/main/java/org/baeldung/web/SessionListenerWithMetrics.java b/spring-security-mvc/src/main/java/com/baeldung/web/SessionListenerWithMetrics.java similarity index 92% rename from spring-security-mvc-session/src/main/java/org/baeldung/web/SessionListenerWithMetrics.java rename to spring-security-mvc/src/main/java/com/baeldung/web/SessionListenerWithMetrics.java index 46bf2708f7..fb1a81744e 100644 --- a/spring-security-mvc-session/src/main/java/org/baeldung/web/SessionListenerWithMetrics.java +++ b/spring-security-mvc/src/main/java/com/baeldung/web/SessionListenerWithMetrics.java @@ -1,12 +1,11 @@ -package org.baeldung.web; +package com.baeldung.web; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; -import org.baeldung.monitoring.MetricRegistrySingleton; - +import com.baeldung.monitoring.MetricRegistrySingleton; import com.codahale.metrics.Counter; public class SessionListenerWithMetrics implements HttpSessionListener { diff --git a/spring-security-mvc/src/main/java/com/baeldung/web/SessionRestController.java b/spring-security-mvc/src/main/java/com/baeldung/web/SessionRestController.java new file mode 100644 index 0000000000..1353ee25d0 --- /dev/null +++ b/spring-security-mvc/src/main/java/com/baeldung/web/SessionRestController.java @@ -0,0 +1,17 @@ +package com.baeldung.web; + +import javax.servlet.http.HttpSession; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SessionRestController { + + @GetMapping("/session-max-interval") + @ResponseBody + public String retrieveMaxSessionIncativeInterval(HttpSession session) { + return "Max Inactive Interval before Session expires: " + session.getMaxInactiveInterval(); + } +} diff --git a/spring-security-mvc/src/main/resources/application.properties b/spring-security-mvc/src/main/resources/application.properties new file mode 100644 index 0000000000..56b2b7b123 --- /dev/null +++ b/spring-security-mvc/src/main/resources/application.properties @@ -0,0 +1,8 @@ +server.servlet.session.timeout=65s + +spring.mvc.view.prefix=/WEB-INF/view/ +spring.mvc.view.suffix=.jsp + +## Secure Session Cookie configurations +#server.servlet.session.cookie.http-only=true +#server.servlet.session.cookie.secure=true \ No newline at end of file diff --git a/spring-security-mvc-session/src/main/resources/logback.xml b/spring-security-mvc/src/main/resources/logback.xml similarity index 100% rename from spring-security-mvc-session/src/main/resources/logback.xml rename to spring-security-mvc/src/main/resources/logback.xml diff --git a/spring-security-mvc-session/src/main/resources/webSecurityConfig.xml b/spring-security-mvc/src/main/resources/webSecurityConfig.xml similarity index 100% rename from spring-security-mvc-session/src/main/resources/webSecurityConfig.xml rename to spring-security-mvc/src/main/resources/webSecurityConfig.xml diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-security-mvc/src/main/webapp/WEB-INF/mvc-servlet.xml similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/mvc-servlet.xml rename to spring-security-mvc/src/main/webapp/WEB-INF/mvc-servlet.xml diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/anonymous.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/anonymous.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/anonymous.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/anonymous.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/console.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/console.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/console.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/console.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/homepage.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/homepage.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/homepage.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/homepage.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/invalidSession.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/invalidSession.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/invalidSession.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/invalidSession.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/login.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/login.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/login.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/login.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/view/sessionExpired.jsp b/spring-security-mvc/src/main/webapp/WEB-INF/view/sessionExpired.jsp similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/view/sessionExpired.jsp rename to spring-security-mvc/src/main/webapp/WEB-INF/view/sessionExpired.jsp diff --git a/spring-security-mvc-session/src/main/webapp/WEB-INF/web.xml b/spring-security-mvc/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from spring-security-mvc-session/src/main/webapp/WEB-INF/web.xml rename to spring-security-mvc/src/main/webapp/WEB-INF/web.xml diff --git a/spring-security-mvc/src/test/java/com/baeldung/SpringContextIntegrationTest.java b/spring-security-mvc/src/test/java/com/baeldung/SpringContextIntegrationTest.java new file mode 100644 index 0000000000..8e53a6371a --- /dev/null +++ b/spring-security-mvc/src/test/java/com/baeldung/SpringContextIntegrationTest.java @@ -0,0 +1,15 @@ +package com.baeldung; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringContextIntegrationTest { + + @Test + public void whenSpringContextIsBootstrapped_thenNoExceptions() { + } +} diff --git a/spring-security-mvc/src/test/java/com/baeldung/session/SessionConfigurationIntegrationTest.java b/spring-security-mvc/src/test/java/com/baeldung/session/SessionConfigurationIntegrationTest.java new file mode 100644 index 0000000000..5a9bf72077 --- /dev/null +++ b/spring-security-mvc/src/test/java/com/baeldung/session/SessionConfigurationIntegrationTest.java @@ -0,0 +1,92 @@ +package com.baeldung.session; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.Test; +import org.springframework.http.HttpStatus; + +import io.restassured.filter.session.SessionFilter; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +/** + * This Live Test requires the service to be up and running. + */ +public class SessionConfigurationIntegrationTest { + + private static final String USER = "user1"; + private static final String PASSWORD = "user1Pass"; + private static final String SESSION_SVC_URL = "http://localhost:8080/session-max-interval"; + + @Test + public void givenValidUser_whenRequestResourceAfterSessionExpiration_thenRedirectedToInvalidSessionUri() throws Exception { + SessionFilter sessionFilter = new SessionFilter(); + simpleSvcRequestLoggingIn(sessionFilter); + Response resp2 = simpleResponseRequestUsingSessionNotFollowingRedirects(sessionFilter); + assertThat(resp2.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(resp2.getBody() + .asString()).isEqualTo("Max Inactive Interval before Session expires: 60"); + + // session will be expired in 60 seconds... + Thread.sleep(62000); + Response resp3 = simpleResponseRequestUsingSessionNotFollowingRedirects(sessionFilter); + + assertThat(resp3.getStatusCode()).isEqualTo(HttpStatus.FOUND.value()); + assertThat(resp3.getHeader("Location")).isEqualTo("http://localhost:8080/invalidSession.html"); + } + + @Test + public void givenValidUser_whenLoginMoreThanMaxValidSession_thenRedirectedToExpiredSessionUri() throws Exception { + SessionFilter sessionFilter = new SessionFilter(); + simpleSvcRequestLoggingIn(sessionFilter); + simpleSvcRequestLoggingIn(); + + // this login will expire the first session + simpleSvcRequestLoggingIn(); + + // now try to access a resource using expired session + Response resp4 = given().filter(sessionFilter) + .and() + .redirects() + .follow(false) + .when() + .get(SESSION_SVC_URL); + + assertThat(resp4.getStatusCode()).isEqualTo(HttpStatus.FOUND.value()); + assertThat(resp4.getHeader("Location")).isEqualTo("http://localhost:8080/sessionExpired.html"); + } + + private static void simpleSvcRequestLoggingIn() { + simpleSvcRequestLoggingIn(null); + } + + private static void simpleSvcRequestLoggingIn(SessionFilter sessionFilter) { + Response response = simpleResponseSvcRequestLoggingIn(Optional.ofNullable(sessionFilter)); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(response.getBody() + .asString()).isEqualTo("Max Inactive Interval before Session expires: 60"); + } + + private static Response simpleResponseSvcRequestLoggingIn(Optional sessionFilter) { + RequestSpecification spec = given().auth() + .form(USER, PASSWORD); + sessionFilter.ifPresent(filter -> spec.and() + .filter(filter)); + return spec.when() + .get(SESSION_SVC_URL); + } + + private static Response simpleResponseRequestUsingSessionNotFollowingRedirects(SessionFilter sessionFilter) { + return given().filter(sessionFilter) + .and() + .redirects() + .follow(false) + .when() + .get(SESSION_SVC_URL); + } + +} diff --git a/spring-security-mvc-session/src/test/resources/.gitignore b/spring-security-mvc/src/test/resources/.gitignore similarity index 100% rename from spring-security-mvc-session/src/test/resources/.gitignore rename to spring-security-mvc/src/test/resources/.gitignore diff --git a/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/ThymeleafConfig.java b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/ThymeleafConfig.java new file mode 100644 index 0000000000..2fd11628ad --- /dev/null +++ b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/ThymeleafConfig.java @@ -0,0 +1,23 @@ +package com.baeldung.thymeleaf; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; + +@Configuration +public class ThymeleafConfig { + + @Bean + public ClassLoaderTemplateResolver secondaryTemplateResolver() { + ClassLoaderTemplateResolver secondaryTemplateResolver = new ClassLoaderTemplateResolver(); + secondaryTemplateResolver.setPrefix("templates-2/"); + secondaryTemplateResolver.setSuffix(".html"); + secondaryTemplateResolver.setTemplateMode(TemplateMode.HTML); + secondaryTemplateResolver.setCharacterEncoding("UTF-8"); + secondaryTemplateResolver.setOrder(1); + secondaryTemplateResolver.setCheckExistence(true); + + return secondaryTemplateResolver; + } +} diff --git a/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/templatedir/HelloController.java b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/templatedir/HelloController.java new file mode 100644 index 0000000000..b404418106 --- /dev/null +++ b/spring-thymeleaf-2/src/main/java/com/baeldung/thymeleaf/templatedir/HelloController.java @@ -0,0 +1,13 @@ +package com.baeldung.thymeleaf.templatedir; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HelloController { + + @GetMapping("/hello") + public String sayHello() { + return "hello"; + } +} diff --git a/spring-thymeleaf-2/src/main/resources/application.properties b/spring-thymeleaf-2/src/main/resources/application.properties new file mode 100644 index 0000000000..b09232bb1b --- /dev/null +++ b/spring-thymeleaf-2/src/main/resources/application.properties @@ -0,0 +1 @@ +#spring.thymeleaf.prefix=classpath:/templates-2/ \ No newline at end of file diff --git a/spring-thymeleaf-2/src/main/resources/templates-2/hello.html b/spring-thymeleaf-2/src/main/resources/templates-2/hello.html new file mode 100644 index 0000000000..035904c8ba --- /dev/null +++ b/spring-thymeleaf-2/src/main/resources/templates-2/hello.html @@ -0,0 +1,10 @@ + + + + +Enums in Thymeleaf + + +

    Hello from 'templates/templates-2'

    + + \ No newline at end of file diff --git a/spring-thymeleaf/src/main/java/com/baeldung/thymeleaf/controller/ParticipantController.java b/spring-thymeleaf/src/main/java/com/baeldung/thymeleaf/controller/ParticipantController.java new file mode 100644 index 0000000000..eebe37e000 --- /dev/null +++ b/spring-thymeleaf/src/main/java/com/baeldung/thymeleaf/controller/ParticipantController.java @@ -0,0 +1,28 @@ +package com.example.demo; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +import static java.util.Arrays.asList; + +@Controller +public class ParticipantController { + + @RequestMapping("/") + public String index( + @RequestParam(value = "participant", required = false) String participant, + @RequestParam(value = "country", required = false) String country, + @RequestParam(value = "action", required = false) String action, + @RequestParam(value = "id", required = false) Integer id, + Model model + ) { + model.addAttribute("id", id); + List userIds = asList(1,2,3,4); + model.addAttribute("userIds", userIds); + return "participants"; + } +} diff --git a/spring-thymeleaf/src/main/resources/templates/participants.html b/spring-thymeleaf/src/main/resources/templates/participants.html new file mode 100644 index 0000000000..8d4e552093 --- /dev/null +++ b/spring-thymeleaf/src/main/resources/templates/participants.html @@ -0,0 +1,32 @@ + + + + + +

    Enter participant

    +
    + + + + + +
    + + +

    User Details

    +

    Details for user [[${id}]] ...

    +
    + +

    Users

    + +

    + User [[${userId}]] +

    +
    + + diff --git a/testing-modules/junit-5-advanced/src/main/java/com/baeldung/failure_vs_error/SimpleCalculator.java b/testing-modules/junit-5-advanced/src/main/java/com/baeldung/failure_vs_error/SimpleCalculator.java new file mode 100644 index 0000000000..d018aa939f --- /dev/null +++ b/testing-modules/junit-5-advanced/src/main/java/com/baeldung/failure_vs_error/SimpleCalculator.java @@ -0,0 +1,15 @@ +package com.baeldung.failure_vs_error; + +/** + * @author paullatzelsperger + * @since 2019-07-17 + */ +public class SimpleCalculator { + + public static double divideNumbers(double dividend, double divisor) { + if (divisor == 0) { + throw new ArithmeticException("Division by zero!"); + } + return dividend / divisor; + } +} diff --git a/testing-modules/junit-5-advanced/src/test/java/com/baeldung/failure_vs_error/SimpleCalculatorUnitTest.java b/testing-modules/junit-5-advanced/src/test/java/com/baeldung/failure_vs_error/SimpleCalculatorUnitTest.java new file mode 100644 index 0000000000..9b1777258c --- /dev/null +++ b/testing-modules/junit-5-advanced/src/test/java/com/baeldung/failure_vs_error/SimpleCalculatorUnitTest.java @@ -0,0 +1,32 @@ +package com.baeldung.failure_vs_error; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author paullatzelsperger + * @since 2019-07-17 + */ +class SimpleCalculatorUnitTest { + + @Test + void divideNumbers() { + double result = SimpleCalculator.divideNumbers(6, 3); + assertEquals(2, result); + } + + @Test + @Disabled("test is expected to fail, disabled so that CI build still goes through") + void divideNumbers_failure() { + double result = SimpleCalculator.divideNumbers(6, 3); + assertEquals(15, result); + } + + @Test + @Disabled("test is expected to raise an error, disabled so that CI build still goes through") + void divideNumbers_error() { + SimpleCalculator.divideNumbers(10, 0); + } +} diff --git a/vaadin/src/main/java/com/baeldung/introduction/Row.java b/vaadin/src/main/java/com/baeldung/introduction/Row.java new file mode 100644 index 0000000000..412a286376 --- /dev/null +++ b/vaadin/src/main/java/com/baeldung/introduction/Row.java @@ -0,0 +1,45 @@ +package com.baeldung.introduction; + +public class Row { + + private String column1; + + private String column2; + + private String column3; + + public Row() { + + } + + public Row(String column1, String column2, String column3) { + super(); + this.column1 = column1; + this.column2 = column2; + this.column3 = column3; + } + + public String getColumn1() { + return column1; + } + + public void setColumn1(String column1) { + this.column1 = column1; + } + + public String getColumn2() { + return column2; + } + + public void setColumn2(String column2) { + this.column2 = column2; + } + + public String getColumn3() { + return column3; + } + + public void setColumn3(String column3) { + this.column3 = column3; + } +} \ No newline at end of file diff --git a/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java b/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java index 68be53b1b3..22ce19f5e0 100644 --- a/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java +++ b/vaadin/src/main/java/com/baeldung/introduction/VaadinUI.java @@ -201,10 +201,14 @@ public class VaadinUI extends UI { TwinColSelect twinColSelect = new TwinColSelect("TwinColSelect"); twinColSelect.setItems(numbers); - Grid grid = new Grid("Grid"); - grid.setColumns("Column1", "Column2", "Column3"); - grid.setItems("Item1", "Item2", "Item3"); - grid.setItems("Item4", "Item5", "Item6"); + Grid grid = new Grid(Row.class); + grid.setColumns("column1", "column2", "column3"); + Row row1 = new Row("Item1", "Item2", "Item3"); + Row row2 = new Row("Item4", "Item5", "Item6"); + List rows = new ArrayList(); + rows.add(row1); + rows.add(row2); + grid.setItems(rows); Panel panel = new Panel("Panel"); panel.setContent(grid); @@ -271,7 +275,7 @@ public class VaadinUI extends UI { setContent(verticalLayout); } - @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true) + @WebServlet(urlPatterns = "/VAADIN/*", name = "MyUIServlet", asyncSupported = true) @VaadinServletConfiguration(ui = VaadinUI.class, productionMode = false) public static class MyUIServlet extends VaadinServlet { } diff --git a/xml/pom.xml b/xml/pom.xml index 123875b1d9..540b1fc03b 100644 --- a/xml/pom.xml +++ b/xml/pom.xml @@ -1,4 +1,5 @@ - 4.0.0 xml @@ -14,7 +15,7 @@ - dom4j + org.dom4j dom4j ${dom4j.version} @@ -23,18 +24,31 @@ jaxen ${jaxen.version} - + + org.jooq + joox-java-6 + ${joox.version} + org.jdom jdom2 ${jdom2.version} - - javax.xml + javax.xml.bind jaxb-api ${jaxb-api.version} + + com.sun.xml.bind + jaxb-impl + ${jaxb-impl.version} + + + com.sun.xml.bind + jaxb-core + ${jaxb-core.version} + javax.xml jaxp-api @@ -45,7 +59,17 @@ stax-api ${stax-api.version} - + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + commons-io @@ -76,6 +100,31 @@ commons-lang ${commons-lang.version} + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit-jupiter.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + org.xmlunit + xmlunit-assertj + ${xmlunit-assertj.version} + test + @@ -86,6 +135,16 @@ true + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + maven-surefire-plugin + ${maven-surefire-plugin.version} + + @@ -213,7 +272,8 @@ - + maven-assembly-plugin ${project.basedir} @@ -232,8 +292,10 @@ - make-assembly - package + make-assembly + package attached @@ -246,17 +308,31 @@ - 1.6.1 - 1.1.6 + 2.1.1 + 1.2.0 2.0.6 + 1.6.2 + 2.5 4.1 1.2.4.5 - 2.1 + 2.3.1 1.4.2 + 2.3.0.1 + 2.3.2 1.0-2 + 3.12.2 + 2.6.3 + 5.5.0 + 1.21 + + 3.5 + 2.4 + 1.8 1.3.1 + 3.8.0 + 2.22.2 diff --git a/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java new file mode 100644 index 0000000000..a1922ad224 --- /dev/null +++ b/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java @@ -0,0 +1,45 @@ +package com.baeldung.xml.attribute; + +import org.dom4j.*; +import org.dom4j.io.DocumentSource; +import org.dom4j.io.SAXReader; + +import javax.xml.XMLConstants; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; +import java.io.Writer; +import java.util.List; + +public class Dom4jTransformer { + private final Document input; + + public Dom4jTransformer(String resourcePath) throws DocumentException { + // 1- Build the doc from the XML file + SAXReader xmlReader = new SAXReader(); + this.input = xmlReader.read(resourcePath); + } + + public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerException { + // 2- Locate the node(s) with xpath, we can use index and iterator too. + String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue); + XPath xpath = DocumentHelper.createXPath(expr); + List nodes = xpath.selectNodes(input); + // 3- Make the change on the selected nodes + for (int i = 0; i < nodes.size(); i++) { + Element element = (Element) nodes.get(i); + element.addAttribute(attribute, newValue); + } + // 4- Save the result to a new XML doc + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + Transformer xformer = factory.newTransformer(); + xformer.setOutputProperty(OutputKeys.INDENT, "yes"); + Writer output = new StringWriter(); + xformer.transform(new DocumentSource(input), new StreamResult(output)); + return output.toString(); + } +} diff --git a/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java new file mode 100644 index 0000000000..a2266a2b44 --- /dev/null +++ b/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java @@ -0,0 +1,63 @@ +package com.baeldung.xml.attribute; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +public class JaxpTransformer { + + private Document input; + + public JaxpTransformer(String resourcePath) throws SAXException, IOException, ParserConfigurationException { + // 1- Build the doc from the XML file + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + input = factory.newDocumentBuilder() + .parse(resourcePath); + } + + public String modifyAttribute(String attribute, String oldValue, String newValue) throws XPathExpressionException, TransformerFactoryConfigurationError, TransformerException { + // 2- Locate the node(s) with xpath + XPath xpath = XPathFactory.newInstance() + .newXPath(); + NodeList nodes = (NodeList) xpath.evaluate(String.format("//*[contains(@%s, '%s')]", attribute, oldValue), input, XPathConstants.NODESET); + // 3- Make the change on the selected nodes + for (int i = 0; i < nodes.getLength(); i++) { + Element value = (Element) nodes.item(i); + value.setAttribute(attribute, newValue); + } + //Stream api syntax + // IntStream + // .range(0, nodes.getLength()) + // .mapToObj(i -> (Element) nodes.item(i)) + // .forEach(value -> value.setAttribute(attribute, newValue)); + // 4- Save the result to a new XML doc + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + Transformer xformer = factory.newTransformer(); + xformer.setOutputProperty(OutputKeys.INDENT, "yes"); + Writer output = new StringWriter(); + xformer.transform(new DOMSource(input), new StreamResult(output)); + return output.toString(); + } +} diff --git a/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java new file mode 100644 index 0000000000..d36d60ec59 --- /dev/null +++ b/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java @@ -0,0 +1,38 @@ +package com.baeldung.xml.attribute; + +import org.joox.JOOX; +import org.joox.Match; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.TransformerFactoryConfigurationError; +import java.io.IOException; + +import static org.joox.JOOX.$; + +public class JooxTransformer { + + private final Document input; + + public JooxTransformer(String resourcePath) throws SAXException, IOException { + // 1- Build the doc from the XML file + DocumentBuilder builder = JOOX.builder(); + input = builder.parse(resourcePath); + } + + public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerFactoryConfigurationError { + // 2- Select the document + Match $ = $(input); + // 3 - Find node to modify + String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue); + $ + // .find("to") or with xpath + .xpath(expr) + .get() + .stream() + .forEach(e -> e.setAttribute(attribute, newValue)); + // 4- Return result as String + return $.toString(); + } +} diff --git a/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java b/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java new file mode 100644 index 0000000000..064e181713 --- /dev/null +++ b/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java @@ -0,0 +1,72 @@ +package com.baeldung.xml.attribute.jmh; + +import org.dom4j.DocumentException; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.xml.sax.SAXException; + +import com.baeldung.xml.attribute.Dom4jTransformer; +import com.baeldung.xml.attribute.JaxpTransformer; +import com.baeldung.xml.attribute.JooxTransformer; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathExpressionException; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class AttributeBenchMark { + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(AttributeBenchMark.class.getSimpleName()) + .forks(1) + .build(); + new Runner(opt).run(); + } + + @Benchmark + public String dom4jBenchmark() throws DocumentException, TransformerException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + Dom4jTransformer transformer = new Dom4jTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + return transformer.modifyAttribute(attribute, oldValue, newValue); + } + + @Benchmark + public String jooxBenchmark() throws IOException, SAXException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + JooxTransformer transformer = new JooxTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + return transformer.modifyAttribute(attribute, oldValue, newValue); + } + + @Benchmark + public String jaxpBenchmark() throws TransformerException, ParserConfigurationException, SAXException, IOException, XPathExpressionException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + JaxpTransformer transformer = new JaxpTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + return transformer.modifyAttribute(attribute, oldValue, newValue); + } +} diff --git a/xml/src/main/resources/xml/attribute.xml b/xml/src/main/resources/xml/attribute.xml new file mode 100644 index 0000000000..c8fa3f1071 --- /dev/null +++ b/xml/src/main/resources/xml/attribute.xml @@ -0,0 +1,5 @@ + + + john@email.com + mary@email.com + \ No newline at end of file diff --git a/xml/src/main/resources/xml/attribute_expected.xml b/xml/src/main/resources/xml/attribute_expected.xml new file mode 100644 index 0000000000..1d5d7b0cea --- /dev/null +++ b/xml/src/main/resources/xml/attribute_expected.xml @@ -0,0 +1,5 @@ + + + john@email.com + mary@email.com + \ No newline at end of file diff --git a/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java new file mode 100644 index 0000000000..485744f9a5 --- /dev/null +++ b/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java @@ -0,0 +1,55 @@ +package com.baeldung.xml.attribute; + +import org.dom4j.DocumentException; +import org.junit.jupiter.api.Test; + +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +/** + * Unit test for {@link Dom4jTransformer}. + */ +public class Dom4jProcessorUnitTest { + + @Test + public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws TransformerFactoryConfigurationError, TransformerException, DocumentException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + Dom4jTransformer transformer = new Dom4jTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + String result = transformer.modifyAttribute(attribute, oldValue, newValue); + + assertThat(result).hasXPath("//*[contains(@customer, 'false')]"); + } + + @Test + public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, TransformerException, URISyntaxException, DocumentException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + Dom4jTransformer transformer = new Dom4jTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + String expectedXml = new String(Files.readAllBytes((Paths.get(getClass() + .getResource("/xml/attribute_expected.xml") + .toURI())))); + + String result = transformer.modifyAttribute(attribute, oldValue, newValue); + + assertThat(result) + .and(expectedXml) + .areSimilar(); + } + +} diff --git a/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java new file mode 100644 index 0000000000..8394016dbd --- /dev/null +++ b/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java @@ -0,0 +1,34 @@ +package com.baeldung.xml.attribute; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.xpath.XPathExpressionException; + +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +/** + * Unit test for {@link JaxpTransformer}. + */ +public class JaxpProcessorUnitTest { + + @Test + public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, ParserConfigurationException, XPathExpressionException, TransformerFactoryConfigurationError, TransformerException { + String path = getClass().getResource("/xml/attribute.xml") + .toString(); + JaxpTransformer transformer = new JaxpTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + String result = transformer.modifyAttribute(attribute, oldValue, newValue); + + assertThat(result).hasXPath("//*[contains(@customer, 'false')]"); + } + +} diff --git a/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java new file mode 100644 index 0000000000..38c7c59789 --- /dev/null +++ b/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java @@ -0,0 +1,54 @@ +package com.baeldung.xml.attribute; + +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import javax.xml.transform.TransformerFactoryConfigurationError; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +/** + * Unit test for {@link JooxTransformer}. + */ +public class JooxProcessorUnitTest { + + @Test + public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, TransformerFactoryConfigurationError { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + JooxTransformer transformer = new JooxTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + + String result = transformer.modifyAttribute(attribute, oldValue, newValue); + + assertThat(result).hasXPath("//*[contains(@customer, 'false')]"); + } + + @Test + public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, URISyntaxException, SAXException { + String path = getClass() + .getResource("/xml/attribute.xml") + .toString(); + JooxTransformer transformer = new JooxTransformer(path); + String attribute = "customer"; + String oldValue = "true"; + String newValue = "false"; + String expectedXml = new String(Files.readAllBytes((Paths.get(getClass() + .getResource("/xml/attribute_expected.xml") + .toURI())))); + + String result = transformer.modifyAttribute(attribute, oldValue, newValue); + + assertThat(result) + .and(expectedXml) + .areSimilar(); + } + +} diff --git a/xml/src/test/resources/xml/attribute.xml b/xml/src/test/resources/xml/attribute.xml new file mode 100644 index 0000000000..c8fa3f1071 --- /dev/null +++ b/xml/src/test/resources/xml/attribute.xml @@ -0,0 +1,5 @@ + + + john@email.com + mary@email.com + \ No newline at end of file diff --git a/xml/src/test/resources/xml/attribute_expected.xml b/xml/src/test/resources/xml/attribute_expected.xml new file mode 100644 index 0000000000..1d5d7b0cea --- /dev/null +++ b/xml/src/test/resources/xml/attribute_expected.xml @@ -0,0 +1,5 @@ + + + john@email.com + mary@email.com + \ No newline at end of file