From 9063961bb43366b98422f56a7de77ab188f7c110 Mon Sep 17 00:00:00 2001 From: dhruba619 Date: Sun, 12 Mar 2017 20:49:43 +0530 Subject: [PATCH 001/149] BAEL-701 updated source after review --- .../test/java/baeldung/com/SimpleTest.java | 28 +++++++++++++++++++ testng/src/test/resources/test_suite.xml | 1 + 2 files changed, 29 insertions(+) create mode 100644 testng/src/test/java/baeldung/com/SimpleTest.java diff --git a/testng/src/test/java/baeldung/com/SimpleTest.java b/testng/src/test/java/baeldung/com/SimpleTest.java new file mode 100644 index 0000000000..24bf3a6f01 --- /dev/null +++ b/testng/src/test/java/baeldung/com/SimpleTest.java @@ -0,0 +1,28 @@ +package baeldung.com; + +import org.testng.Assert; +import org.testng.TestNG; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class SimpleTest extends TestNG { + private int number; + + @BeforeClass + public void setup() { + number = 12; + } + + @AfterClass + public void tearDown() { + number = 0; + } + + @Test + public void givenNumber_whenEven_thenTrue() { + Assert.assertTrue(number % 2 == 0); + } + +} + diff --git a/testng/src/test/resources/test_suite.xml b/testng/src/test/resources/test_suite.xml index 0ccbbd2714..4d0b17cbe8 100644 --- a/testng/src/test/resources/test_suite.xml +++ b/testng/src/test/resources/test_suite.xml @@ -7,6 +7,7 @@ + \ No newline at end of file From 788e261f928e06f5782b7ffd4aeaf0ac04758fc2 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 13 Mar 2017 20:31:24 +0100 Subject: [PATCH 002/149] Update .travis.yml (#1387) * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2a369a1b3..7da572edf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,11 @@ addons: cache: directories: - .autoconf - - $HOME/.m2 \ No newline at end of file + - $HOME/.m2 + + sudo: required + + env: + global: + JAVA_OPTS="-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" + MAVEN_OPTS="-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" From 15f5037879bb172e9c4a8e03efc6de3f03b2338e Mon Sep 17 00:00:00 2001 From: Wim Deblauwe Date: Mon, 13 Mar 2017 22:26:51 +0100 Subject: [PATCH 003/149] Feature/bael 75 (#1383) * BEAL-75 - Spring Boot Audit Support Source code for http://inprogress.baeldung.com/wp-admin/post.php?post=35337&action=edit * BEAL-75 - Spring Boot Audit Support Update to use SLF4J logger instead of System.out.println * BEAL-75 - Spring Boot Audit Support Give the user the 'ACTUATOR' role so the management endpoints can be viewed when logging on as 'user' * BEAL-75 - Spring Boot Audit Support Update to Spring Boot 1.5.2.RELEASE --- spring-boot-auditing/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-auditing/pom.xml b/spring-boot-auditing/pom.xml index 9307db39ee..0afbe0becc 100644 --- a/spring-boot-auditing/pom.xml +++ b/spring-boot-auditing/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.1.RELEASE + 1.5.2.RELEASE @@ -187,7 +187,7 @@ UTF-8 1.8 - 4.3.4.RELEASE + 4.3.7.RELEASE 2.2.1 3.1.1 3.3.7-1 From 46b1f93d52103953c9bec989c0cea4feca61e049 Mon Sep 17 00:00:00 2001 From: Johnson Okorie Date: Mon, 13 Mar 2017 23:20:14 +0100 Subject: [PATCH 004/149] BAEL-482 (#1379) * Evaluation artical : Different Types of Bean Injection in Spring * Evaluation artical : Different Types of Bean Injection in Spring * Evaluation artical : Different Types of Bean Injection in Spring * BAEL-482 * Evaluation artical : Different Types of Bean Injection in Spring * Evaluation artical : Different Types of Bean Injection in Spring * Evaluation artical : Different Types of Bean Injection in Spring * Evaluation artical : Different Types of Bean Injection in Spring * BAEL-482 * Evaluation artical : Different Types of Bean Injection in Spring * BAEL-482 * BAEL-482 --- redis/src/test/java/com/baeldung/RedissonIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redis/src/test/java/com/baeldung/RedissonIntegrationTest.java b/redis/src/test/java/com/baeldung/RedissonIntegrationTest.java index 4cacd9dbd0..766963e5cf 100644 --- a/redis/src/test/java/com/baeldung/RedissonIntegrationTest.java +++ b/redis/src/test/java/com/baeldung/RedissonIntegrationTest.java @@ -104,9 +104,9 @@ public class RedissonIntegrationTest { RTopic subscribeTopic = client.getTopic("baeldung"); subscribeTopic.addListener((channel, customMessage) -> future.complete(customMessage.getMessage())); - RTopic receiveTopic = client.getTopic("baeldung"); + RTopic publishTopic = client.getTopic("baeldung"); long clientsReceivedMessage - = receiveTopic.publish(new CustomMessage("This is a message")); + = publishTopic.publish(new CustomMessage("This is a message")); assertEquals("This is a message", future.get()); From f2f8839708d9637821a86db26d36535514f0988a Mon Sep 17 00:00:00 2001 From: baljeet20 Date: Tue, 14 Mar 2017 15:46:44 +0530 Subject: [PATCH 005/149] BAEL-707 Add the changes as per review comment (#1397) --- xml/src/main/resources/Order.xsd | 5 ----- 1 file changed, 5 deletions(-) diff --git a/xml/src/main/resources/Order.xsd b/xml/src/main/resources/Order.xsd index 16e689a342..81f3e41628 100644 --- a/xml/src/main/resources/Order.xsd +++ b/xml/src/main/resources/Order.xsd @@ -9,11 +9,6 @@ - - - - - From 52ef5b380382566f37977e98c671fe02af5d237d Mon Sep 17 00:00:00 2001 From: lor6 Date: Tue, 14 Mar 2017 15:25:49 +0200 Subject: [PATCH 006/149] remove constructors (#1398) --- .../MultipleEntryPointsSecurityConfig.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java index 9da2ef20e3..eba67706fa 100644 --- a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java +++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java @@ -26,10 +26,6 @@ public class MultipleEntryPointsSecurityConfig { @Order(1) public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter { - public App1ConfigurationAdapter() { - super(); - } - @Override protected void configure(HttpSecurity http) throws Exception { //@formatter:off @@ -45,10 +41,6 @@ public class MultipleEntryPointsSecurityConfig { @Order(2) public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter { - public App2ConfigurationAdapter() { - super(); - } - protected void configure(HttpSecurity http) throws Exception { //@formatter:off http.antMatcher("/user/**") @@ -67,10 +59,6 @@ public class MultipleEntryPointsSecurityConfig { @Order(3) public static class App3ConfigurationAdapter extends WebSecurityConfigurerAdapter { - public App3ConfigurationAdapter() { - super(); - } - protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/guest/**").authorizeRequests().anyRequest().permitAll(); } From 61660f5d37b045bbfafeef4c037f4fab04f1052e Mon Sep 17 00:00:00 2001 From: Doha2012 Date: Wed, 15 Mar 2017 10:59:55 +0200 Subject: [PATCH 007/149] add update to rest api (#1401) * upgrade to spring boot 1.5.2 * add full update to REST API --- .../svcbook/book/BookController.java | 25 +++++++++--- .../bootstrap/svcbook/book/BookService.java | 39 ++++++++++++------- .../svcrating/rating/RatingController.java | 26 ++++++++++--- .../svcrating/rating/RatingService.java | 28 ++++++++----- 4 files changed, 83 insertions(+), 35 deletions(-) diff --git a/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookController.java b/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookController.java index d00f114b8c..192f9c2342 100644 --- a/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookController.java +++ b/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookController.java @@ -1,11 +1,19 @@ package com.baeldung.spring.cloud.bootstrap.svcbook.book; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + @RestController @RequestMapping("/books") public class BookController { @@ -13,7 +21,7 @@ public class BookController { @Autowired private BookService bookService; - @GetMapping("") + @GetMapping public List findAllBooks() { return bookService.findAllBooks(); } @@ -23,7 +31,7 @@ public class BookController { return bookService.findBookById(bookId); } - @PostMapping("") + @PostMapping public Book createBook(@RequestBody Book book) { return bookService.createBook(book); } @@ -33,7 +41,12 @@ public class BookController { bookService.deleteBook(bookId); } - @PatchMapping("/{bookId") + @PutMapping("/{bookId}") + public Book updateBook(@RequestBody Book book, @PathVariable Long bookId) { + return bookService.updateBook(book, bookId); + } + + @PatchMapping("/{bookId}") public Book updateBook(@RequestBody Map updates, @PathVariable Long bookId) { return bookService.updateBook(updates, bookId); } diff --git a/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookService.java b/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookService.java index cfcbf15757..106fdad5d9 100644 --- a/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookService.java +++ b/spring-cloud/spring-cloud-bootstrap/svc-book/src/main/java/com/baeldung/spring/cloud/bootstrap/svcbook/book/BookService.java @@ -1,13 +1,15 @@ package com.baeldung.spring.cloud.bootstrap.svcbook.book; +import java.util.List; +import java.util.Map; +import java.util.Optional; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import com.google.common.base.Preconditions; @Service @Transactional(readOnly = true) @@ -27,7 +29,7 @@ public class BookService { @Transactional(propagation = Propagation.REQUIRED) public Book createBook(Book book) { - Book newBook = new Book(); + final Book newBook = new Book(); newBook.setTitle(book.getTitle()); newBook.setAuthor(book.getAuthor()); return bookRepository.save(newBook); @@ -40,16 +42,25 @@ public class BookService { @Transactional(propagation = Propagation.REQUIRED) public Book updateBook(Map updates, Long bookId) { - Book book = findBookById(bookId); - updates.keySet().forEach(key -> { - switch (key) { - case "author": - book.setAuthor(updates.get(key)); - break; - case "title": - book.setTitle(updates.get(key)); - } - }); + final Book book = findBookById(bookId); + updates.keySet() + .forEach(key -> { + switch (key) { + case "author": + book.setAuthor(updates.get(key)); + break; + case "title": + book.setTitle(updates.get(key)); + } + }); + return bookRepository.save(book); + } + + @Transactional(propagation = Propagation.REQUIRED) + public Book updateBook(Book book, Long bookId) { + Preconditions.checkNotNull(book); + Preconditions.checkState(book.getId() == bookId); + Preconditions.checkNotNull(bookRepository.findOne(bookId)); return bookRepository.save(book); } } diff --git a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java index 83452ad747..cbfeda49c0 100644 --- a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java +++ b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java @@ -1,11 +1,20 @@ package com.baeldung.spring.cloud.bootstrap.svcrating.rating; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + @RestController @RequestMapping("/ratings") public class RatingController { @@ -13,7 +22,7 @@ public class RatingController { @Autowired private RatingService ratingService; - @GetMapping("") + @GetMapping public List findRatingsByBookId(@RequestParam(required = false, defaultValue = "0") Long bookId) { if (bookId.equals(0L)) { return ratingService.findAllRatings(); @@ -21,7 +30,7 @@ public class RatingController { return ratingService.findRatingsByBookId(bookId); } - @PostMapping("") + @PostMapping public Rating createRating(@RequestBody Rating rating) { return ratingService.createRating(rating); } @@ -31,7 +40,12 @@ public class RatingController { ratingService.deleteRating(ratingId); } - @PatchMapping("/{ratingId") + @PutMapping("/{ratingId}") + public Rating updateRating(@RequestBody Rating rating, @PathVariable Long ratingId) { + return ratingService.updateRating(rating, ratingId); + } + + @PatchMapping("/{ratingId}") public Rating updateRating(@RequestBody Map updates, @PathVariable Long ratingId) { return ratingService.updateRating(updates, ratingId); } diff --git a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingService.java b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingService.java index a2360b7be5..b05d7e2f1f 100644 --- a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingService.java +++ b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingService.java @@ -1,13 +1,15 @@ package com.baeldung.spring.cloud.bootstrap.svcrating.rating; +import java.util.List; +import java.util.Map; +import java.util.Optional; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import com.google.common.base.Preconditions; @Service @Transactional(readOnly = true) @@ -31,7 +33,7 @@ public class RatingService { @Transactional(propagation = Propagation.REQUIRED) public Rating createRating(Rating rating) { - Rating newRating = new Rating(); + final Rating newRating = new Rating(); newRating.setBookId(rating.getBookId()); newRating.setStars(rating.getStars()); return ratingRepository.save(newRating); @@ -44,14 +46,22 @@ public class RatingService { @Transactional(propagation = Propagation.REQUIRED) public Rating updateRating(Map updates, Long ratingId) { - Rating rating = findRatingById(ratingId); - updates.keySet().forEach(key -> { - switch (key) { + final Rating rating = findRatingById(ratingId); + updates.keySet() + .forEach(key -> { + switch (key) { case "stars": rating.setStars(Integer.parseInt(updates.get(key))); break; - } - }); + } + }); + return ratingRepository.save(rating); + } + + public Rating updateRating(Rating rating, Long ratingId) { + Preconditions.checkNotNull(rating); + Preconditions.checkState(rating.getId() == ratingId); + Preconditions.checkNotNull(ratingRepository.findOne(ratingId)); return ratingRepository.save(rating); } } From 5cd552a16b0631a6f71c3011f5f5b9a50d7355bb Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Wed, 15 Mar 2017 10:51:33 +0100 Subject: [PATCH 008/149] BAEL-634 javassist (#1349) * BEEL-634 javassist dependency * BEEL-634 code for javassist article * BEEL-634 test refinement * BEEL-634 increment lib to newest version * add test that uses reflection to verify * add field * add bytecode to different class --- libraries/pom.xml | 8 +- .../java/com/baeldung/javasisst/Point.java | 17 +++ .../javasisst/ThreeDimensionalPoint.java | 19 +++ .../com/baeldung/javassist/JavasisstTest.java | 119 ++++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 libraries/src/main/java/com/baeldung/javasisst/Point.java create mode 100644 libraries/src/main/java/com/baeldung/javasisst/ThreeDimensionalPoint.java create mode 100644 libraries/src/test/java/com/baeldung/javassist/JavasisstTest.java diff --git a/libraries/pom.xml b/libraries/pom.xml index c89efa3f66..eaae539872 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -23,7 +23,7 @@ - + cglib @@ -51,6 +51,11 @@ javatuples ${javatuples.version} + + org.javassist + javassist + ${javaassist.version} + org.assertj @@ -65,6 +70,7 @@ 4.12 1.9.2 1.2 + 3.21.0-GA 3.6.2 diff --git a/libraries/src/main/java/com/baeldung/javasisst/Point.java b/libraries/src/main/java/com/baeldung/javasisst/Point.java new file mode 100644 index 0000000000..7e5c1cedd5 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javasisst/Point.java @@ -0,0 +1,17 @@ +package com.baeldung.javasisst; + + +public class Point { + public int x = 0; + public int y = 0; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public void move(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/libraries/src/main/java/com/baeldung/javasisst/ThreeDimensionalPoint.java b/libraries/src/main/java/com/baeldung/javasisst/ThreeDimensionalPoint.java new file mode 100644 index 0000000000..fb24d4b85d --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javasisst/ThreeDimensionalPoint.java @@ -0,0 +1,19 @@ +package com.baeldung.javasisst; + + +public class ThreeDimensionalPoint { + public int x = 0; + public int y = 0; + public int z = 0; + + public ThreeDimensionalPoint(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void move(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/libraries/src/test/java/com/baeldung/javassist/JavasisstTest.java b/libraries/src/test/java/com/baeldung/javassist/JavasisstTest.java new file mode 100644 index 0000000000..da5ae02c56 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javassist/JavasisstTest.java @@ -0,0 +1,119 @@ +package com.baeldung.javassist; + + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.NotFoundException; +import javassist.bytecode.*; +import org.junit.Test; + +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JavasisstTest { + @Test + public void givenJavasisstAPI_whenConstructClass_thenGenerateAClassFile() throws CannotCompileException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException { + //given + String classNameWithPackage = "com.baeldung.JavassistGeneratedClass"; + ClassFile cf = new ClassFile(false, classNameWithPackage, null); + cf.setInterfaces(new String[]{"java.lang.Cloneable"}); + + FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I"); + f.setAccessFlags(AccessFlag.PUBLIC); + cf.addField(f); + + //when + String className = "JavassistGeneratedClass.class"; + cf.write(new DataOutputStream(new FileOutputStream(className))); + + //then + ClassPool classPool = ClassPool.getDefault(); + Field[] fields = classPool.makeClass(cf).toClass().getFields(); + assertEquals(fields[0].getName(), "id"); + + String classContent = new String(Files.readAllBytes(Paths.get(className))); + assertTrue(classContent.contains("java/lang/Cloneable")); + } + + @Test + public void givenJavaClass_whenLoadAtByJavassist_thenTraversWholeClass() throws NotFoundException, CannotCompileException, BadBytecode { + //given + ClassPool cp = ClassPool.getDefault(); + ClassFile cf = cp.get("com.baeldung.javasisst.Point").getClassFile(); + MethodInfo minfo = cf.getMethod("move"); + CodeAttribute ca = minfo.getCodeAttribute(); + CodeIterator ci = ca.iterator(); + + //when + List operations = new LinkedList<>(); + while (ci.hasNext()) { + int index = ci.next(); + int op = ci.byteAt(index); + operations.add(Mnemonic.OPCODE[op]); + } + + //then + assertEquals(operations, + Arrays.asList("aload_0", "iload_1", "putfield", "aload_0", "iload_2", "putfield", "return")); + + } + + @Test + public void givenTableOfInstructions_whenAddNewInstruction_thenShouldConstructProperSequence() throws NotFoundException, BadBytecode, CannotCompileException, IllegalAccessException, InstantiationException { + //given + ClassFile cf = ClassPool.getDefault().get("com.baeldung.javasisst.ThreeDimensionalPoint").getClassFile(); + + //when + FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I"); + f.setAccessFlags(AccessFlag.PUBLIC); + cf.addField(f); + + + ClassPool classPool = ClassPool.getDefault(); + Field[] fields = classPool.makeClass(cf).toClass().getFields(); + List fieldsList = Stream.of(fields).map(Field::getName).collect(Collectors.toList()); + assertTrue(fieldsList.contains("id")); + + } + + @Test + public void givenLoadedClass_whenAddConstructorToClass_shouldCreateClassWithConstructor() throws NotFoundException, CannotCompileException, BadBytecode { + //given + ClassFile cf = ClassPool.getDefault().get("com.baeldung.javasisst.Point").getClassFile(); + Bytecode code = new Bytecode(cf.getConstPool()); + code.addAload(0); + code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V"); + code.addReturn(null); + + //when + MethodInfo minfo = new MethodInfo(cf.getConstPool(), MethodInfo.nameInit, "()V"); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + + //then + CodeIterator ci = code.toCodeAttribute().iterator(); + List operations = new LinkedList<>(); + while (ci.hasNext()) { + int index = ci.next(); + int op = ci.byteAt(index); + operations.add(Mnemonic.OPCODE[op]); + } + + assertEquals(operations, + Arrays.asList("aload_0", "invokespecial", "return")); + + + } +} From f6e570c6cad68b67c31c8995d0af237f5e37f164 Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Wed, 15 Mar 2017 15:33:32 +0530 Subject: [PATCH 009/149] adding following modules with updated testcase : DB, Filter, Json (#1410) * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json --- ratpack/build.gradle | 3 ++ ratpack/pom.xml | 51 +++++++++++++------ .../main/java/com/baeldung/Application.java | 50 +++++++++++++----- .../filter/RequestValidatorFilter.java | 16 ++++++ .../java/com/baeldung/model/Employee.java | 44 ++++++++++++++++ ratpack/src/main/resources/DDL.sql | 6 +++ .../java/com/baeldung/ApplicationTest.java | 17 +++++++ 7 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 ratpack/src/main/java/com/baeldung/filter/RequestValidatorFilter.java create mode 100644 ratpack/src/main/java/com/baeldung/model/Employee.java create mode 100644 ratpack/src/main/resources/DDL.sql diff --git a/ratpack/build.gradle b/ratpack/build.gradle index 29d9633531..aeddd5f9f9 100644 --- a/ratpack/build.gradle +++ b/ratpack/build.gradle @@ -4,6 +4,7 @@ buildscript { } dependencies { classpath "io.ratpack:ratpack-gradle:1.4.5" + classpath "com.h2database:h2:1.4.193" } } @@ -19,6 +20,8 @@ repositories { } dependencies { + compile ratpack.dependency('hikari') + compile 'com.h2database:h2:1.4.193' testCompile 'junit:junit:4.11' runtime "org.slf4j:slf4j-simple:1.7.21" } diff --git a/ratpack/pom.xml b/ratpack/pom.xml index 0290a25d2b..bb606b9b11 100644 --- a/ratpack/pom.xml +++ b/ratpack/pom.xml @@ -15,22 +15,41 @@ - - io.ratpack - ratpack-core - 1.4.5 - - - io.ratpack - ratpack-test - 1.4.5 - - - junit - junit - 4.12 - test - + + io.ratpack + ratpack-core + 1.4.5 + + + io.ratpack + ratpack-hikari + 1.4.5 + + + io.ratpack + ratpack-test + 1.4.5 + + + com.h2database + h2 + 1.4.193 + + + junit + junit + 4.12 + test + + + ${project.artifactId} + + + src/main/resources + + + + diff --git a/ratpack/src/main/java/com/baeldung/Application.java b/ratpack/src/main/java/com/baeldung/Application.java index 3a5bf54d00..94709e88e9 100644 --- a/ratpack/src/main/java/com/baeldung/Application.java +++ b/ratpack/src/main/java/com/baeldung/Application.java @@ -1,22 +1,48 @@ package com.baeldung; +import java.util.ArrayList; +import java.util.List; + +import com.baeldung.filter.RequestValidatorFilter; +import com.baeldung.model.Employee; + +import ratpack.guice.Guice; +import ratpack.hikari.HikariModule; +import ratpack.http.MutableHeaders; +import ratpack.jackson.Jackson; import ratpack.http.MutableHeaders; import ratpack.server.RatpackServer; public class Application { - public static void main(String... args) throws Exception { - RatpackServer.start(server -> server.handlers(chain -> chain.all(ctx -> { - MutableHeaders headers = ctx.getResponse().getHeaders(); - headers.set("Access-Control-Allow-Origin", "*"); - headers.set("Accept-Language", "en-us"); - headers.set("Accept-Charset", "UTF-8"); - ctx.next(); - }) - .get(ctx -> ctx.render("Welcome to baeldung ratpack!!!")) - .get(":name", ctx -> ctx.render("Hello " + ctx.getPathTokens().get("name") + "!!!")) - .post(":amount", ctx -> ctx.render(" Amount $" + ctx.getPathTokens().get("amount") + " added successfully !!!")) - )); + public static void main(String[] args) throws Exception { + + List employees = new ArrayList(); + employees.add(new Employee(1L, "Mr", "John Doe")); + employees.add(new Employee(2L, "Mr", "White Snow")); + + + RatpackServer.start( + server -> server.registry(Guice.registry(bindings -> bindings.module(HikariModule.class, config -> { + config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); + config.addDataSourceProperty("URL", + "jdbc:h2:mem:baeldung;INIT=RUNSCRIPT FROM 'classpath:/DDL.sql'"); + }))).handlers(chain -> chain + .all( + // ctx -> { + // MutableHeaders headers = + // ctx.getResponse().getHeaders(); + // headers.set("Access-Control-Allow-Origin","*"); + // headers.set("Accept-Language", "en-us"); + // headers.set("Accept-Charset", "UTF-8"); + // ctx.next(); + // } + new RequestValidatorFilter()) + .get(ctx -> ctx.render("Welcome to baeldung ratpack!!!")) + .get("data/employees", ctx -> ctx.render(Jackson.json(employees))) + .get(":name", ctx -> ctx.render("Hello " + ctx.getPathTokens().get("name") + "!!!")) + .post(":amount", ctx -> ctx + .render(" Amount $" + ctx.getPathTokens().get("amount") + " added successfully !!!")))); } } diff --git a/ratpack/src/main/java/com/baeldung/filter/RequestValidatorFilter.java b/ratpack/src/main/java/com/baeldung/filter/RequestValidatorFilter.java new file mode 100644 index 0000000000..0a8b77aa71 --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/filter/RequestValidatorFilter.java @@ -0,0 +1,16 @@ +package com.baeldung.filter; + +import ratpack.handling.Context; +import ratpack.handling.Handler; +import ratpack.http.MutableHeaders; + +public class RequestValidatorFilter implements Handler { + + @Override + public void handle(Context ctx) throws Exception { + MutableHeaders headers = ctx.getResponse().getHeaders(); + headers.set("Access-Control-Allow-Origin", "*"); + ctx.next(); + } + +} diff --git a/ratpack/src/main/java/com/baeldung/model/Employee.java b/ratpack/src/main/java/com/baeldung/model/Employee.java new file mode 100644 index 0000000000..c983d8b769 --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/model/Employee.java @@ -0,0 +1,44 @@ +package com.baeldung.model; + +import java.io.Serializable; + +public class Employee implements Serializable { + + private static final long serialVersionUID = 3077867088762010705L; + + private Long id; + private String title; + private String name; + + public Employee(Long id, String title, String name) { + super(); + this.id = id; + this.title = title; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/ratpack/src/main/resources/DDL.sql b/ratpack/src/main/resources/DDL.sql new file mode 100644 index 0000000000..af8709ee75 --- /dev/null +++ b/ratpack/src/main/resources/DDL.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS employee; +CREATE TABLE employee ( + id bigint auto_increment primary key, + title varchar(255), + name varchar(255) +) \ No newline at end of file diff --git a/ratpack/src/test/java/com/baeldung/ApplicationTest.java b/ratpack/src/test/java/com/baeldung/ApplicationTest.java index f04a51f2bb..0333441928 100644 --- a/ratpack/src/test/java/com/baeldung/ApplicationTest.java +++ b/ratpack/src/test/java/com/baeldung/ApplicationTest.java @@ -4,10 +4,17 @@ import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import com.baeldung.model.Employee; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import ratpack.test.MainClassApplicationUnderTest; import static org.junit.Assert.assertEquals; +import java.util.ArrayList; +import java.util.List; + @RunWith(JUnit4.class) public class ApplicationTest { @@ -23,6 +30,16 @@ public class ApplicationTest { assertEquals("Hello dummybot!!!", appUnderTest.getHttpClient().getText("/dummybot")); } + @Test + public void givenUrl_getListOfEmployee() throws JsonProcessingException { + List employees = new ArrayList(); + ObjectMapper mapper = new ObjectMapper(); + employees.add(new Employee(1L, "Mr", "John Doe")); + employees.add(new Employee(2L, "Mr", "White Snow")); + + assertEquals(mapper.writeValueAsString(employees), appUnderTest.getHttpClient().getText("/data/employees")); + } + @After public void shutdown() { appUnderTest.close(); From 21aa12753d6fd61061894098bc9e0246a5f94757 Mon Sep 17 00:00:00 2001 From: lor6 Date: Wed, 15 Mar 2017 14:11:37 +0200 Subject: [PATCH 010/149] add entry points (#1413) --- .../MultipleEntryPointsSecurityConfig.java | 31 +++++++++++++-- .../multipleentrypoints/PagesController.java | 12 +++++- .../spring-security-multiple-entry.xml | 38 ++++++++++++++++--- .../multipleHttpElems/loginWithWarning.html | 28 ++++++++++++++ .../multipleHttpElems/multipleHttpLinks.html | 4 +- .../multipleHttpElems/myPrivateUserPage.html | 13 +++++++ .../baeldung/web/MultipleEntryPointsTest.java | 4 +- 7 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/loginWithWarning.html create mode 100644 spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java index eba67706fa..9f2eba0a2e 100644 --- a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java +++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/MultipleEntryPointsSecurityConfig.java @@ -9,6 +9,10 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity @@ -31,10 +35,17 @@ public class MultipleEntryPointsSecurityConfig { //@formatter:off http.antMatcher("/admin/**") .authorizeRequests().anyRequest().hasRole("ADMIN") - .and().httpBasic() + .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint()) .and().exceptionHandling().accessDeniedPage("/403"); //@formatter:on } + + @Bean + public AuthenticationEntryPoint authenticationEntryPoint(){ + BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); + entryPoint.setRealmName("admin realm"); + return entryPoint; + } } @Configuration @@ -42,17 +53,31 @@ public class MultipleEntryPointsSecurityConfig { public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { + //@formatter:off http.antMatcher("/user/**") .authorizeRequests().anyRequest().hasRole("USER") - .and().formLogin().loginPage("/userLogin").loginProcessingUrl("/user/login") + .and().formLogin().loginProcessingUrl("/user/login") .failureUrl("/userLogin?error=loginError").defaultSuccessUrl("/user/myUserPage") .and().logout().logoutUrl("/user/logout").logoutSuccessUrl("/multipleHttpLinks") .deleteCookies("JSESSIONID") - .and().exceptionHandling().accessDeniedPage("/403") + .and().exceptionHandling() + .defaultAuthenticationEntryPointFor(loginUrlauthenticationEntryPointWithWarning(), new AntPathRequestMatcher("/user/private/**")) + .defaultAuthenticationEntryPointFor(loginUrlauthenticationEntryPoint(), new AntPathRequestMatcher("/user/general/**")) + .accessDeniedPage("/403") .and().csrf().disable(); //@formatter:on } + + @Bean + public AuthenticationEntryPoint loginUrlauthenticationEntryPoint(){ + return new LoginUrlAuthenticationEntryPoint("/userLogin"); + } + + @Bean + public AuthenticationEntryPoint loginUrlauthenticationEntryPointWithWarning(){ + return new LoginUrlAuthenticationEntryPoint("/userLoginWithWarning"); + } } @Configuration diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/PagesController.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/PagesController.java index 3b59678b87..b3462d4061 100644 --- a/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/PagesController.java +++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multipleentrypoints/PagesController.java @@ -16,10 +16,15 @@ public class PagesController { return "multipleHttpElems/myAdminPage"; } - @RequestMapping("/user/myUserPage") + @RequestMapping("/user/general/myUserPage") public String getUserPage() { return "multipleHttpElems/myUserPage"; } + + @RequestMapping("/user/private/myPrivateUserPage") + public String getPrivateUserPage() { + return "multipleHttpElems/myPrivateUserPage"; + } @RequestMapping("/guest/myGuestPage") public String getGuestPage() { @@ -30,6 +35,11 @@ public class PagesController { public String getUserLoginPage() { return "multipleHttpElems/login"; } + + @RequestMapping("/userLoginWithWarning") + public String getUserLoginPageWithWarning() { + return "multipleHttpElems/loginWithWarning"; + } @RequestMapping("/403") public String getAccessDeniedPage() { diff --git a/spring-security-mvc-boot/src/main/resources/spring-security-multiple-entry.xml b/spring-security-mvc-boot/src/main/resources/spring-security-multiple-entry.xml index 1a68bd5c30..c026700810 100644 --- a/spring-security-mvc-boot/src/main/resources/spring-security-multiple-entry.xml +++ b/spring-security-mvc-boot/src/main/resources/spring-security-multiple-entry.xml @@ -2,7 +2,7 @@ @@ -14,9 +14,10 @@ - - - + + @@ -24,14 +25,41 @@ + + + + + + + + + + + + + + + + - + + + + + + diff --git a/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/loginWithWarning.html b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/loginWithWarning.html new file mode 100644 index 0000000000..a5b2eaf3dc --- /dev/null +++ b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/loginWithWarning.html @@ -0,0 +1,28 @@ + + + + +

Login

+

Warning! You are about to access sensible data!

+ +
+ + + + + + + + + + + + + + +
Username:
Password:
+ +
+ + + \ No newline at end of file diff --git a/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html index 4a2af1d649..676badb16f 100644 --- a/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html +++ b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/multipleHttpLinks.html @@ -8,7 +8,9 @@ Admin page
-User page +User page +
+Private user page
Guest page diff --git a/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html new file mode 100644 index 0000000000..52045ec320 --- /dev/null +++ b/spring-security-mvc-boot/src/main/resources/templates/multipleHttpElems/myPrivateUserPage.html @@ -0,0 +1,13 @@ + + + + +Insert title here + + +Welcome user to your private page! Logout + +

+Back to links + + \ No newline at end of file diff --git a/spring-security-mvc-boot/src/test/java/org/baeldung/web/MultipleEntryPointsTest.java b/spring-security-mvc-boot/src/test/java/org/baeldung/web/MultipleEntryPointsTest.java index 96d38d4943..050d2363af 100644 --- a/spring-security-mvc-boot/src/test/java/org/baeldung/web/MultipleEntryPointsTest.java +++ b/spring-security-mvc-boot/src/test/java/org/baeldung/web/MultipleEntryPointsTest.java @@ -46,9 +46,9 @@ public class MultipleEntryPointsTest { @Test public void whenTestUserCredentials_thenOk() throws Exception { - mockMvc.perform(get("/user/myUserPage")).andExpect(status().isFound()); + mockMvc.perform(get("/user/general/myUserPage")).andExpect(status().isFound()); - mockMvc.perform(get("/user/myUserPage").with(user("user").password("userPass").roles("USER"))).andExpect(status().isOk()); + mockMvc.perform(get("/user/general/myUserPage").with(user("user").password("userPass").roles("USER"))).andExpect(status().isOk()); mockMvc.perform(get("/admin/myAdminPage").with(user("user").password("userPass").roles("USER"))).andExpect(status().isForbidden()); } From d1233d04378c7b36a490cc1a7c2f7db52d7e4989 Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Wed, 15 Mar 2017 18:11:43 +0530 Subject: [PATCH 011/149] spring boot custom banner (#1412) * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial --- .../src/main/resources/application.properties | 8 ++++++++ spring-boot/src/main/resources/banner.txt | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 spring-boot/src/main/resources/banner.txt diff --git a/spring-boot/src/main/resources/application.properties b/spring-boot/src/main/resources/application.properties index 8c6549f53d..1ffc95849d 100644 --- a/spring-boot/src/main/resources/application.properties +++ b/spring-boot/src/main/resources/application.properties @@ -33,3 +33,11 @@ logging.level.org.springframework=INFO #Servlet Configuration servlet.name=dispatcherExample servlet.mapping=/dispatcherExampleURL + +#banner.charset=UTF-8 +#banner.location=classpath:banner.txt +#banner.image.location=classpath:banner.gif +#banner.image.width= //TODO +#banner.image.height= //TODO +#banner.image.margin= //TODO +#banner.image.invert= //TODO \ No newline at end of file diff --git a/spring-boot/src/main/resources/banner.txt b/spring-boot/src/main/resources/banner.txt new file mode 100644 index 0000000000..c45ff763bf --- /dev/null +++ b/spring-boot/src/main/resources/banner.txt @@ -0,0 +1,19 @@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}:${AnsiColor.WHITE}.${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}*${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}.${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}:${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}#${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ From de29f6af4b6b8ec756d6d25448dff0eafc311506 Mon Sep 17 00:00:00 2001 From: baljeet20 Date: Thu, 16 Mar 2017 14:32:00 +0530 Subject: [PATCH 012/149] BALE-707 Refactoring changes (#1418) * BAEL-707 Add the changes as per review comment * BAEL-707 Refactored the code as per review comments --- .../java/com/baeldung/xml/jibx/Person.java | 19 ++++----------- .../java/com/baeldung/xml/jibx/Phone.java | 18 --------------- xml/src/main/resources/Order.xsd | 23 ------------------- xml/src/main/resources/customer-binding.xml | 5 +--- .../com/baeldung/xml/jibx/CustomerTest.java | 8 ++----- xml/src/test/resources/Customer1.xml | 7 +----- 6 files changed, 9 insertions(+), 71 deletions(-) diff --git a/xml/src/main/java/com/baeldung/xml/jibx/Person.java b/xml/src/main/java/com/baeldung/xml/jibx/Person.java index 54ce75e257..fbd7cde0a2 100644 --- a/xml/src/main/java/com/baeldung/xml/jibx/Person.java +++ b/xml/src/main/java/com/baeldung/xml/jibx/Person.java @@ -9,23 +9,14 @@ package com.baeldung.xml.jibx; import org.apache.commons.lang.builder.ToStringBuilder; public class Person extends Identity { - private String firstName; - private String lastName; + private String name; - public String getFirstName() { - return firstName; + public String getName() { + return name; } - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; + public void setName(String name) { + this.name = name; } public String toString() { diff --git a/xml/src/main/java/com/baeldung/xml/jibx/Phone.java b/xml/src/main/java/com/baeldung/xml/jibx/Phone.java index b24950289a..783f2ee6fb 100644 --- a/xml/src/main/java/com/baeldung/xml/jibx/Phone.java +++ b/xml/src/main/java/com/baeldung/xml/jibx/Phone.java @@ -2,26 +2,8 @@ package com.baeldung.xml.jibx; public class Phone { - private String countryCode; - private String networkPrefix; private String number; - public String getCountryCode() { - return countryCode; - } - - public void setCountryCode(String countryCode) { - this.countryCode = countryCode; - } - - public String getNetworkPrefix() { - return networkPrefix; - } - - public void setNetworkPrefix(String networkPrefix) { - this.networkPrefix = networkPrefix; - } - public String getNumber() { return number; } diff --git a/xml/src/main/resources/Order.xsd b/xml/src/main/resources/Order.xsd index 81f3e41628..cbb5bc7596 100644 --- a/xml/src/main/resources/Order.xsd +++ b/xml/src/main/resources/Order.xsd @@ -13,29 +13,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/xml/src/main/resources/customer-binding.xml b/xml/src/main/resources/customer-binding.xml index 54ce4a925f..c1a80366ef 100755 --- a/xml/src/main/resources/customer-binding.xml +++ b/xml/src/main/resources/customer-binding.xml @@ -14,14 +14,11 @@ - - + - - diff --git a/xml/src/test/java/com/baeldung/xml/jibx/CustomerTest.java b/xml/src/test/java/com/baeldung/xml/jibx/CustomerTest.java index c678ce8406..8b14277799 100644 --- a/xml/src/test/java/com/baeldung/xml/jibx/CustomerTest.java +++ b/xml/src/test/java/com/baeldung/xml/jibx/CustomerTest.java @@ -6,7 +6,6 @@ import org.jibx.runtime.IUnmarshallingContext; import org.jibx.runtime.JiBXException; import org.junit.Test; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; @@ -22,8 +21,7 @@ public class CustomerTest { InputStream inputStream = classLoader.getResourceAsStream("Customer1.xml"); Customer customer = (Customer) uctx.unmarshalDocument(inputStream, null); - assertEquals("Stefan", customer.getPerson().getFirstName()); - assertEquals("Jaeger", customer.getPerson().getLastName()); + assertEquals("Stefan Jaegar", customer.getPerson().getName()); assertEquals("Davos Dorf", customer.getCity()); } @@ -47,9 +45,7 @@ public class CustomerTest { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("Customer1.xml"); Customer customer = (Customer) uctx.unmarshalDocument(inputStream, null); - - assertEquals("1", customer.getHomePhone().getCountryCode()); - assertEquals("234", customer.getHomePhone().getNetworkPrefix()); + assertEquals("234678", customer.getHomePhone().getNumber()); } diff --git a/xml/src/test/resources/Customer1.xml b/xml/src/test/resources/Customer1.xml index 3941563ffe..7f4fbc79af 100644 --- a/xml/src/test/resources/Customer1.xml +++ b/xml/src/test/resources/Customer1.xml @@ -2,18 +2,13 @@ 12345 - Stefan - Jaeger + Stefan Jaeger - 1 - 234 234678 - 1 - 234 234678 Davos Dorf From 26b5ab860a8459bb8f41b05dd3fa2100ce0fa3ea Mon Sep 17 00:00:00 2001 From: ahamedm Date: Thu, 16 Mar 2017 14:49:13 +0400 Subject: [PATCH 013/149] BAEL-696 - Implement OR in the REST API Query Language (#1404) * Dependency Injection Types, XML-Config, Java-Config, Test Classes * Formatting done with Formatter Configuration in Eclipse * REST Query Lang - Adv Search Ops - Improvement - C1 * REST Query Lang - Adv Search Ops - Improvement - C2 * add update to rest api (#1401) * upgrade to spring boot 1.5.2 * add full update to REST API * BAEL-634 javassist (#1349) * BEEL-634 javassist dependency * BEEL-634 code for javassist article * BEEL-634 test refinement * BEEL-634 increment lib to newest version * add test that uses reflection to verify * add field * add bytecode to different class * adding following modules with updated testcase : DB, Filter, Json (#1410) * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * add entry points (#1413) * spring boot custom banner (#1412) * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial * BALE-707 Refactoring changes (#1418) * BAEL-707 Add the changes as per review comment * BAEL-707 Refactored the code as per review comments * BAEL-696 Code formatting --- .../beaninjection/AnotherSampleDAOBean.java | 17 ++ .../beaninjection/ExampleDAOBean.java | 39 +++ .../beaninjection/ExampleServiceBean.java | 47 +++ .../beaninjection/IAnotherSampleDAO.java | 5 + .../baeldung/beaninjection/IExampleDAO.java | 12 + .../beaninjection/IExampleService.java | 9 + .../beaninjection/SampleDomainObject.java | 31 ++ ...licationContextTestBeanInjectionTypes.java | 36 +++ .../resources/beaninjectiontypes-context.xml | 22 ++ .../BeanInjectionJavaConfigTest.java | 50 ++++ .../BeanInjectionXMLConfigTest.java | 49 +++ .../persistence/IEnhancedSpecification.java | 10 + .../dao/GenericSpecificationsBuilder.java | 64 ++++ .../persistence/dao/UserSpecification.java | 62 ++-- .../dao/UserSpecificationsBuilder.java | 134 +++++---- .../web/controller/UserController.java | 54 ++-- .../baeldung/web/util/SearchOperation.java | 52 ++-- .../baeldung/web/util/SpecSearchCriteria.java | 105 ++++--- .../JPASpecificationIntegrationTest.java | 279 ++++++++++-------- .../query/JPASpecificationLiveTest.java | 234 ++++++++------- 20 files changed, 898 insertions(+), 413 deletions(-) create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/AnotherSampleDAOBean.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/ExampleDAOBean.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/ExampleServiceBean.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/IAnotherSampleDAO.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/IExampleDAO.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/IExampleService.java create mode 100644 spring-core/src/main/java/com/baeldung/beaninjection/SampleDomainObject.java create mode 100644 spring-core/src/main/java/com/baeldung/configuration/ApplicationContextTestBeanInjectionTypes.java create mode 100644 spring-core/src/main/resources/beaninjectiontypes-context.xml create mode 100644 spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionJavaConfigTest.java create mode 100644 spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionXMLConfigTest.java create mode 100644 spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java create mode 100644 spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/AnotherSampleDAOBean.java b/spring-core/src/main/java/com/baeldung/beaninjection/AnotherSampleDAOBean.java new file mode 100644 index 0000000000..6313ba5a65 --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/AnotherSampleDAOBean.java @@ -0,0 +1,17 @@ +package com.baeldung.beaninjection; + +public class AnotherSampleDAOBean implements IAnotherSampleDAO { + + private String propertyY; + + public AnotherSampleDAOBean(String propertyY) { + this.propertyY = propertyY; + } + + // standard setters and getters + + public String getPropertyY() { + return propertyY; + } + +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/ExampleDAOBean.java b/spring-core/src/main/java/com/baeldung/beaninjection/ExampleDAOBean.java new file mode 100644 index 0000000000..cbc7b35a7c --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/ExampleDAOBean.java @@ -0,0 +1,39 @@ +package com.baeldung.beaninjection; + +import java.util.List; + +public class ExampleDAOBean implements IExampleDAO { + + private String propertyX; + + public ExampleDAOBean(String propertyX) { + this.propertyX = propertyX; + } + + public String getPropertyX() { + return propertyX; + } + + public void setPropertyX(String propertyX) { + this.propertyX = propertyX; + } + + @Override + public List getDomainList() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SampleDomainObject createNewDomain(SampleDomainObject inputDomain) { + // TODO Auto-generated method stub + return null; + } + + @Override + public SampleDomainObject getSomeDomain() { + // TODO Auto-generated method stub + return new SampleDomainObject(); + } + +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/ExampleServiceBean.java b/spring-core/src/main/java/com/baeldung/beaninjection/ExampleServiceBean.java new file mode 100644 index 0000000000..e5a5dfff5d --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/ExampleServiceBean.java @@ -0,0 +1,47 @@ +package com.baeldung.beaninjection; + +import java.util.List; + +public class ExampleServiceBean implements IExampleService { + + private IExampleDAO exampleDAO; + private IAnotherSampleDAO anotherSampleDAO; + + public ExampleServiceBean(IExampleDAO exampleDAO) { + this.exampleDAO = exampleDAO; + } + + public void setAnotherSampleDAO(IAnotherSampleDAO anotherSampleDAO) { + this.anotherSampleDAO = anotherSampleDAO; + } + + // standard setters and getters + + public IAnotherSampleDAO getAnotherSampleDAO() { + return anotherSampleDAO; + } + + public void setExampleDAO(ExampleDAOBean exampleDAO) { + this.exampleDAO = exampleDAO; + } + + public IExampleDAO getExampleDAO() { + return exampleDAO; + } + + private String propertyX; + + public String getPropertyX() { + return propertyX; + } + + public void setPropertyX(String propertyX) { + this.propertyX = propertyX; + } + + public List serviceMethodX() { + /*get domain list from DAO .. business logic on domain objects..return*/ + return exampleDAO.getDomainList(); + } + +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/IAnotherSampleDAO.java b/spring-core/src/main/java/com/baeldung/beaninjection/IAnotherSampleDAO.java new file mode 100644 index 0000000000..ed4ad42705 --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/IAnotherSampleDAO.java @@ -0,0 +1,5 @@ +package com.baeldung.beaninjection; + +public interface IAnotherSampleDAO { + +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/IExampleDAO.java b/spring-core/src/main/java/com/baeldung/beaninjection/IExampleDAO.java new file mode 100644 index 0000000000..f7dbd2f9fe --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/IExampleDAO.java @@ -0,0 +1,12 @@ +package com.baeldung.beaninjection; + +import java.util.List; + +public interface IExampleDAO { + + List getDomainList(); + + SampleDomainObject createNewDomain(SampleDomainObject domainObject); + + SampleDomainObject getSomeDomain(); +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/IExampleService.java b/spring-core/src/main/java/com/baeldung/beaninjection/IExampleService.java new file mode 100644 index 0000000000..9ea572d16b --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/IExampleService.java @@ -0,0 +1,9 @@ +package com.baeldung.beaninjection; + +import java.util.List; + +public interface IExampleService { + + List serviceMethodX(); + +} diff --git a/spring-core/src/main/java/com/baeldung/beaninjection/SampleDomainObject.java b/spring-core/src/main/java/com/baeldung/beaninjection/SampleDomainObject.java new file mode 100644 index 0000000000..3a5a913aa6 --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/beaninjection/SampleDomainObject.java @@ -0,0 +1,31 @@ +package com.baeldung.beaninjection; + +import java.io.Serializable; + +public class SampleDomainObject implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 449859763481296747L; + + private String domainPropX; + private String domainPropY; + + public String getDomainPropX() { + return domainPropX; + } + + public void setDomainPropX(String domainPropX) { + this.domainPropX = domainPropX; + } + + public String getDomainPropY() { + return domainPropY; + } + + public void setDomainPropY(String domainPropY) { + this.domainPropY = domainPropY; + } + +} diff --git a/spring-core/src/main/java/com/baeldung/configuration/ApplicationContextTestBeanInjectionTypes.java b/spring-core/src/main/java/com/baeldung/configuration/ApplicationContextTestBeanInjectionTypes.java new file mode 100644 index 0000000000..3939abf148 --- /dev/null +++ b/spring-core/src/main/java/com/baeldung/configuration/ApplicationContextTestBeanInjectionTypes.java @@ -0,0 +1,36 @@ +package com.baeldung.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import com.baeldung.beaninjection.AnotherSampleDAOBean; +import com.baeldung.beaninjection.ExampleDAOBean; +import com.baeldung.beaninjection.ExampleServiceBean; +import com.baeldung.beaninjection.IAnotherSampleDAO; +import com.baeldung.beaninjection.IExampleDAO; +import com.baeldung.beaninjection.IExampleService; + +@Configuration +@ComponentScan(basePackages = { "com.baeldung.beaninjection" }) +public class ApplicationContextTestBeanInjectionTypes { + + @Bean + public IExampleDAO exampleDAO() { + return new ExampleDAOBean("Mandatory DAO Property X"); + } + + @Bean + public IExampleService exampleServiceBean() { + ExampleServiceBean serviceBean = new ExampleServiceBean(exampleDAO()); + serviceBean.setAnotherSampleDAO(anotherSampleDAO()); + serviceBean.setPropertyX("Some Service Property X"); + return serviceBean; + } + + @Bean + public IAnotherSampleDAO anotherSampleDAO() { + return new AnotherSampleDAOBean("Mandatory DAO Property Y"); + } + +} diff --git a/spring-core/src/main/resources/beaninjectiontypes-context.xml b/spring-core/src/main/resources/beaninjectiontypes-context.xml new file mode 100644 index 0000000000..dfdea41cdc --- /dev/null +++ b/spring-core/src/main/resources/beaninjectiontypes-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionJavaConfigTest.java b/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionJavaConfigTest.java new file mode 100644 index 0000000000..4befd884eb --- /dev/null +++ b/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionJavaConfigTest.java @@ -0,0 +1,50 @@ +package com.baeldung.test.beaninjection; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.baeldung.beaninjection.AnotherSampleDAOBean; +import com.baeldung.beaninjection.ExampleDAOBean; +import com.baeldung.beaninjection.ExampleServiceBean; +import com.baeldung.configuration.ApplicationContextTestBeanInjectionTypes; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = ApplicationContextTestBeanInjectionTypes.class) +public class BeanInjectionJavaConfigTest implements ApplicationContextAware { + + private ApplicationContext beanInjectedContext; + + @Test + public void testDAOInjectionByJava() { + ExampleServiceBean serviceBean = beanInjectedContext.getBean(ExampleServiceBean.class); + assertNotNull("Failed: Constructor Injection,Bean Reference Injection,Java Config, ExampleServiceBean", serviceBean.getExampleDAO()); + assertNotNull("Failed: Constructor Injection,Bean Reference Injection,Java Config, ExampleServiceBean", serviceBean.getAnotherSampleDAO()); + assertTrue("Failed: Constructor Injection,String Property , Java Config", serviceBean.getPropertyX() + .equals("Some Service Property X")); + } + + @Test + public void testPropertyInjectioninDAOByJava() { + ExampleDAOBean daoBean = beanInjectedContext.getBean(ExampleDAOBean.class); + assertTrue("Failed: Constructor Injection,String Property , Java Config", daoBean.getPropertyX() + .equals("Mandatory DAO Property X")); + + AnotherSampleDAOBean anotherDAOBean = beanInjectedContext.getBean(AnotherSampleDAOBean.class); + assertTrue("Failed: Constructor Injection,String Property , XML Config", anotherDAOBean.getPropertyY() + .equals("Mandatory DAO Property Y")); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // TODO Auto-generated method stub + this.beanInjectedContext = applicationContext; + } +} diff --git a/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionXMLConfigTest.java b/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionXMLConfigTest.java new file mode 100644 index 0000000000..d19a099aad --- /dev/null +++ b/spring-core/src/test/java/com/baeldung/test/beaninjection/BeanInjectionXMLConfigTest.java @@ -0,0 +1,49 @@ +package com.baeldung.test.beaninjection; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.baeldung.beaninjection.AnotherSampleDAOBean; +import com.baeldung.beaninjection.ExampleDAOBean; +import com.baeldung.beaninjection.ExampleServiceBean; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:beaninjectiontypes-context.xml") +public class BeanInjectionXMLConfigTest implements ApplicationContextAware { + + private ApplicationContext beanInjectedContext; + + @Test + public void testDAOInjectionByXML() { + ExampleServiceBean serviceBean = beanInjectedContext.getBean(ExampleServiceBean.class); + assertNotNull("Failed: Constructor Injection,Bean Reference Injection,XML Config, ExampleServiceBean", serviceBean.getExampleDAO()); + assertNotNull("Failed: Constructor Injection,Bean Reference Injection,XML Config, ExampleServiceBean", serviceBean.getAnotherSampleDAO()); + assertTrue("Failed: Constructor Injection,String Property , XML Config", serviceBean.getPropertyX() + .equals("Some Service Property X")); + } + + @Test + public void testPropertyInjectioninDAOByXML() { + ExampleDAOBean daoBean = beanInjectedContext.getBean(ExampleDAOBean.class); + assertTrue("Failed: Constructor Injection,String Property , XML Config", daoBean.getPropertyX() + .equals("Mandatory DAO Property X")); + + AnotherSampleDAOBean anotherDAOBean = beanInjectedContext.getBean(AnotherSampleDAOBean.class); + assertTrue("Failed: Constructor Injection,String Property , XML Config", anotherDAOBean.getPropertyY() + .equals("Mandatory DAO Property Y")); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // TODO Auto-generated method stub + this.beanInjectedContext = applicationContext; + } +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java new file mode 100644 index 0000000000..58d08a161e --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence; + +import org.springframework.data.jpa.domain.Specification; + +public interface IEnhancedSpecification extends Specification { + + default boolean isOfLowPrecedence() { + return false; + } +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java new file mode 100644 index 0000000000..4936c2e1c1 --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java @@ -0,0 +1,64 @@ +package org.baeldung.persistence.dao; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.baeldung.web.util.SearchOperation; +import org.baeldung.web.util.SpecSearchCriteria; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; + +public class GenericSpecificationsBuilder { + + private final List params; + + public GenericSpecificationsBuilder() { + this.params = new ArrayList<>(); + } + + public final GenericSpecificationsBuilder with(final String key, final String operation, final Object value, + final String prefix, final String suffix) { + return with(null, key, operation, value, prefix, suffix); + } + + public final GenericSpecificationsBuilder with(final String precedenceIndicator, final String key, + final String operation, final Object value, final String prefix, final String suffix) { + SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); + if (op != null) { + if (op == SearchOperation.EQUALITY) // the operation may be complex operation + { + final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + + if (startWithAsterisk && endWithAsterisk) { + op = SearchOperation.CONTAINS; + } else if (startWithAsterisk) { + op = SearchOperation.ENDS_WITH; + } else if (endWithAsterisk) { + op = SearchOperation.STARTS_WITH; + } + } + params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value)); + } + return this; + } + + public Specification build(Function> converter) { + + if (params.size() == 0) + return null; + + params.sort((spec0, spec1) -> Boolean.compare(spec0.isLowPrecedence(), spec1.isLowPrecedence())); + + final List> specs = params.stream().map(converter).collect(Collectors.toCollection(ArrayList::new)); + + Specification result = specs.get(0); + + for (int idx = 1; idx < specs.size(); idx++) { + result=params.get(idx).isLowPrecedence()? Specifications.where(result).or(specs.get(idx)): Specifications.where(result).and(specs.get(idx)); + } + return result; + } +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecification.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecification.java index e41c7ca663..2788c46fde 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecification.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecification.java @@ -11,39 +11,39 @@ import org.springframework.data.jpa.domain.Specification; public class UserSpecification implements Specification { - private SpecSearchCriteria criteria; + private SpecSearchCriteria criteria; - public UserSpecification(final SpecSearchCriteria criteria) { - super(); - this.criteria = criteria; - } + public UserSpecification(final SpecSearchCriteria criteria) { + super(); + this.criteria = criteria; + } - public SpecSearchCriteria getCriteria() { - return criteria; - } + public SpecSearchCriteria getCriteria() { + return criteria; + } - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder builder) { - switch (criteria.getOperation()) { - case EQUALITY: - return builder.equal(root.get(criteria.getKey()), criteria.getValue()); - case NEGATION: - return builder.notEqual(root.get(criteria.getKey()), criteria.getValue()); - case GREATER_THAN: - return builder.greaterThan(root. get(criteria.getKey()), criteria.getValue().toString()); - case LESS_THAN: - return builder.lessThan(root. get(criteria.getKey()), criteria.getValue().toString()); - case LIKE: - return builder.like(root. get(criteria.getKey()), criteria.getValue().toString()); - case STARTS_WITH: - return builder.like(root. get(criteria.getKey()), criteria.getValue() + "%"); - case ENDS_WITH: - return builder.like(root. get(criteria.getKey()), "%" + criteria.getValue()); - case CONTAINS: - return builder.like(root. get(criteria.getKey()), "%" + criteria.getValue() + "%"); - default: - return null; - } - } + @Override + public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder builder) { + switch (criteria.getOperation()) { + case EQUALITY: + return builder.equal(root.get(criteria.getKey()), criteria.getValue()); + case NEGATION: + return builder.notEqual(root.get(criteria.getKey()), criteria.getValue()); + case GREATER_THAN: + return builder.greaterThan(root. get(criteria.getKey()), criteria.getValue().toString()); + case LESS_THAN: + return builder.lessThan(root. get(criteria.getKey()), criteria.getValue().toString()); + case LIKE: + return builder.like(root. get(criteria.getKey()), criteria.getValue().toString()); + case STARTS_WITH: + return builder.like(root. get(criteria.getKey()), criteria.getValue() + "%"); + case ENDS_WITH: + return builder.like(root. get(criteria.getKey()), "%" + criteria.getValue()); + case CONTAINS: + return builder.like(root. get(criteria.getKey()), "%" + criteria.getValue() + "%"); + default: + return null; + } + } } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java index 3db4267ae0..8a10163f51 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java @@ -1,60 +1,74 @@ -package org.baeldung.persistence.dao; - -import java.util.ArrayList; -import java.util.List; - -import org.baeldung.persistence.model.User; -import org.baeldung.web.util.SearchOperation; -import org.baeldung.web.util.SpecSearchCriteria; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.domain.Specifications; - -public final class UserSpecificationsBuilder { - - private final List params; - - public UserSpecificationsBuilder() { - params = new ArrayList(); - } - - // API - - public final UserSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) { - SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); - if (op != null) { - if (op == SearchOperation.EQUALITY) // the operation may be complex operation - { - final boolean startWithAsterisk = prefix.contains("*"); - final boolean endWithAsterisk = suffix.contains("*"); - - if (startWithAsterisk && endWithAsterisk) { - op = SearchOperation.CONTAINS; - } else if (startWithAsterisk) { - op = SearchOperation.ENDS_WITH; - } else if (endWithAsterisk) { - op = SearchOperation.STARTS_WITH; - } - } - params.add(new SpecSearchCriteria(key, op, value)); - } - return this; - } - - public Specification build() { - if (params.size() == 0) { - return null; - } - - final List> specs = new ArrayList>(); - for (final SpecSearchCriteria param : params) { - specs.add(new UserSpecification(param)); - } - - Specification result = specs.get(0); - for (int i = 1; i < specs.size(); i++) { - result = Specifications.where(result).and(specs.get(i)); - } - return result; - } - -} +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.User; +import org.baeldung.web.util.SearchOperation; +import org.baeldung.web.util.SpecSearchCriteria; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; + +import java.util.ArrayList; +import java.util.List; + +public final class UserSpecificationsBuilder { + + private final List params; + + public UserSpecificationsBuilder() { + params = new ArrayList(); + } + + // API + + public final UserSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) { + return with(null, key, operation, value, prefix, suffix); + } + + public final UserSpecificationsBuilder with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) { + SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); + if (op != null) { + if (op == SearchOperation.EQUALITY) { // the operation may be complex operation + final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + + if (startWithAsterisk && endWithAsterisk) { + op = SearchOperation.CONTAINS; + } else if (startWithAsterisk) { + op = SearchOperation.ENDS_WITH; + } else if (endWithAsterisk) { + op = SearchOperation.STARTS_WITH; + } + } + params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value)); + } + return this; + } + + public Specification build() { + + if (params.size() == 0) + return null; + + params.sort((spec0, spec1) -> { + return Boolean.compare(spec0.isLowPrecedence(), spec1.isLowPrecedence()); + }); + + Specification result = new UserSpecification(params.get(0)); + + for (int i = 1; i < params.size(); i++) { + result = params.get(i).isLowPrecedence() ? Specifications.where(result).or(new UserSpecification(params.get(i))) : Specifications.where(result).and(new UserSpecification(params.get(i))); + + } + + return result; + } + + public final UserSpecificationsBuilder with(UserSpecification spec) { + params.add(spec.getCriteria()); + return this; + } + + public final UserSpecificationsBuilder with(SpecSearchCriteria criteria) { + params.add(criteria); + return this; + } +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java index d20423ddc0..fff089a62b 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java @@ -1,15 +1,12 @@ package org.baeldung.web.controller; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.baeldung.persistence.dao.IUserDAO; -import org.baeldung.persistence.dao.MyUserPredicatesBuilder; -import org.baeldung.persistence.dao.MyUserRepository; -import org.baeldung.persistence.dao.UserRepository; -import org.baeldung.persistence.dao.UserSpecificationsBuilder; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import cz.jirutka.rsql.parser.RSQLParser; +import cz.jirutka.rsql.parser.ast.Node; +import org.baeldung.persistence.dao.*; import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor; import org.baeldung.persistence.model.MyUser; import org.baeldung.persistence.model.User; @@ -20,20 +17,12 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.*; -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.BooleanExpression; - -import cz.jirutka.rsql.parser.RSQLParser; -import cz.jirutka.rsql.parser.ast.Node; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; //@EnableSpringDataWebSupport @Controller @@ -84,6 +73,25 @@ public class UserController { return dao.findAll(spec); } + @RequestMapping(method = RequestMethod.GET, value = "/users/espec") + @ResponseBody + public List findAllByOptionalSpecification(@RequestParam(value = "search") final String search) { + final Specification spec = resolveSpecification(search); + return dao.findAll(spec); + } + + protected Specification resolveSpecification(String searchParameters) { + + final UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); + final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET); + final Pattern pattern = Pattern.compile("(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); + final Matcher matcher = pattern.matcher(searchParameters + ","); + while (matcher.find()) { + builder.with(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(5), matcher.group(4), matcher.group(6)); + } + return builder.build(); + } + @RequestMapping(method = RequestMethod.GET, value = "/myusers") @ResponseBody public Iterable findAllByQuerydsl(@RequestParam(value = "search") final String search) { diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java index 703f9b93f6..41a556c18a 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java @@ -1,24 +1,28 @@ -package org.baeldung.web.util; - -public enum SearchOperation { - EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS; - - public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~" }; - - public static SearchOperation getSimpleOperation(final char input) { - switch (input) { - case ':': - return EQUALITY; - case '!': - return NEGATION; - case '>': - return GREATER_THAN; - case '<': - return LESS_THAN; - case '~': - return LIKE; - default: - return null; - } - } -} +package org.baeldung.web.util; + +public enum SearchOperation { + EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS; + + public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~" }; + + public static final String LOW_PRECEDENCE_INDICATOR="'"; + + public static final String ZERO_OR_MORE_REGEX="*"; + + public static SearchOperation getSimpleOperation(final char input) { + switch (input) { + case ':': + return EQUALITY; + case '!': + return NEGATION; + case '>': + return GREATER_THAN; + case '<': + return LESS_THAN; + case '~': + return LIKE; + default: + return null; + } + } +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java index 4a04d395fa..7dbb66edea 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java @@ -1,44 +1,61 @@ -package org.baeldung.web.util; - -public class SpecSearchCriteria { - - private String key; - private SearchOperation operation; - private Object value; - - public SpecSearchCriteria() { - - } - - public SpecSearchCriteria(final String key, final SearchOperation operation, final Object value) { - super(); - this.key = key; - this.operation = operation; - this.value = value; - } - - public String getKey() { - return key; - } - - public void setKey(final String key) { - this.key = key; - } - - public SearchOperation getOperation() { - return operation; - } - - public void setOperation(final SearchOperation operation) { - this.operation = operation; - } - - public Object getValue() { - return value; - } - - public void setValue(final Object value) { - this.value = value; - } - -} +package org.baeldung.web.util; + +public class SpecSearchCriteria { + + private String key; + private SearchOperation operation; + private Object value; + private boolean lowPrecedence; + + public SpecSearchCriteria() { + + } + + public SpecSearchCriteria(final String key, final SearchOperation operation, final Object value) { + super(); + this.key = key; + this.operation = operation; + this.value = value; + } + + public SpecSearchCriteria(final String lowPrecedenceIndicator, final String key, final SearchOperation operation, final Object value) { + super(); + this.lowPrecedence = lowPrecedenceIndicator != null && lowPrecedenceIndicator.equals(SearchOperation.LOW_PRECEDENCE_INDICATOR); + this.key = key; + this.operation = operation; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + public SearchOperation getOperation() { + return operation; + } + + public void setOperation(final SearchOperation operation) { + this.operation = operation; + } + + public Object getValue() { + return value; + } + + public void setValue(final Object value) { + this.value = value; + } + + public boolean isLowPrecedence() { + return lowPrecedence; + } + + public void setLowPrecedence(boolean lowPrecedence) { + this.lowPrecedence = lowPrecedence; + } + +} diff --git a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java index 8bd4857e85..e5c408bfdb 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java @@ -1,119 +1,160 @@ -package org.baeldung.persistence.query; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsIn.isIn; -import static org.hamcrest.core.IsNot.not; - -import java.util.List; - -import org.baeldung.persistence.dao.UserRepository; -import org.baeldung.persistence.dao.UserSpecification; -import org.baeldung.persistence.model.User; -import org.baeldung.spring.PersistenceConfig; -import org.baeldung.web.util.SearchOperation; -import org.baeldung.web.util.SpecSearchCriteria; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.domain.Specifications; -import org.springframework.test.annotation.Rollback; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.transaction.annotation.Transactional; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { PersistenceConfig.class }) -@Transactional -@Rollback -public class JPASpecificationIntegrationTest { - - @Autowired - private UserRepository repository; - - private User userJohn; - - private User userTom; - - @Before - public void init() { - userJohn = new User(); - userJohn.setFirstName("john"); - userJohn.setLastName("doe"); - userJohn.setEmail("john@doe.com"); - userJohn.setAge(22); - repository.save(userJohn); - - userTom = new User(); - userTom.setFirstName("tom"); - userTom.setLastName("doe"); - userTom.setEmail("tom@doe.com"); - userTom.setAge(26); - repository.save(userTom); - } - - @Test - public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john")); - final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe")); - final List results = repository.findAll(Specifications.where(spec).and(spec1)); - - assertThat(userJohn, isIn(results)); - assertThat(userTom, not(isIn(results))); - } - - @Test - public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.NEGATION, "john")); - final List results = repository.findAll(Specifications.where(spec)); - - assertThat(userTom, isIn(results)); - assertThat(userJohn, not(isIn(results))); - } - - @Test - public void givenMinAge_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25")); - final List results = repository.findAll(Specifications.where(spec)); - - assertThat(userTom, isIn(results)); - assertThat(userJohn, not(isIn(results))); - } - - @Test - public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo")); - final List results = repository.findAll(spec); - - assertThat(userJohn, isIn(results)); - assertThat(userTom, not(isIn(results))); - } - - @Test - public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.ENDS_WITH, "n")); - final List results = repository.findAll(spec); - - assertThat(userJohn, isIn(results)); - assertThat(userTom, not(isIn(results))); - } - - @Test - public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.CONTAINS, "oh")); - final List results = repository.findAll(spec); - - assertThat(userJohn, isIn(results)); - assertThat(userTom, not(isIn(results))); - } - - @Test - public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { - final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "20")); - final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.LESS_THAN, "25")); - final List results = repository.findAll(Specifications.where(spec).and(spec1)); - - assertThat(userJohn, isIn(results)); - assertThat(userTom, not(isIn(results))); - } -} +package org.baeldung.persistence.query; + +import org.baeldung.persistence.dao.GenericSpecificationsBuilder; +import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.dao.UserSpecification; +import org.baeldung.persistence.dao.UserSpecificationsBuilder; +import org.baeldung.persistence.model.User; +import org.baeldung.spring.PersistenceConfig; +import org.baeldung.web.util.SearchOperation; +import org.baeldung.web.util.SpecSearchCriteria; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.function.Function; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.collection.IsIn.isIn; +import static org.hamcrest.core.IsNot.not; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { PersistenceConfig.class }) +@Transactional +@Rollback +public class JPASpecificationIntegrationTest { + + @Autowired + private UserRepository repository; + + private User userJohn; + + private User userTom; + + private User userPercy; + + @Before + public void init() { + userJohn = new User(); + userJohn.setFirstName("john"); + userJohn.setLastName("doe"); + userJohn.setEmail("john@doe.com"); + userJohn.setAge(22); + repository.save(userJohn); + + userTom = new User(); + userTom.setFirstName("tom"); + userTom.setLastName("doe"); + userTom.setEmail("tom@doe.com"); + userTom.setAge(26); + repository.save(userTom); + + userPercy = new User(); + userPercy.setFirstName("percy"); + userPercy.setLastName("blackney"); + userPercy.setEmail("percy@blackney.com"); + userPercy.setAge(30); + repository.save(userPercy); + } + + @Test + public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john")); + final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe")); + final List results = repository.findAll(Specifications.where(spec).and(spec1)); + + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } + + @Test + public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() { + UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); + + final SpecSearchCriteria spec = new SpecSearchCriteria("'", "firstName", SearchOperation.EQUALITY, "john"); + final SpecSearchCriteria spec1 = new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"); + + final List results = repository.findAll(builder.with(spec1).with(spec).build()); + + assertThat(results, hasSize(2)); + assertThat(userJohn, isIn(results)); + assertThat(userTom, isIn(results)); + } + + @Test + public void givenFirstOrLastNameGenericBuilder_whenGettingListOfUsers_thenCorrect() { + GenericSpecificationsBuilder builder = new GenericSpecificationsBuilder(); + Function> converter = UserSpecification::new; + builder.with("'", "firstName", ":", "john", null, null); + builder.with(null, "lastName", ":", "doe", null, null); + + final List results = repository.findAll(builder.build(converter)); + assertThat(results, hasSize(2)); + assertThat(userJohn, isIn(results)); + assertThat(userTom, isIn(results)); + } + + @Test + public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.NEGATION, "john")); + final List results = repository.findAll(Specifications.where(spec)); + + assertThat(userTom, isIn(results)); + assertThat(userJohn, not(isIn(results))); + } + + @Test + public void givenMinAge_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25")); + final List results = repository.findAll(Specifications.where(spec)); + + assertThat(userTom, isIn(results)); + assertThat(userJohn, not(isIn(results))); + } + + @Test + public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo")); + final List results = repository.findAll(spec); + + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } + + @Test + public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.ENDS_WITH, "n")); + final List results = repository.findAll(spec); + + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } + + @Test + public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.CONTAINS, "oh")); + final List results = repository.findAll(spec); + + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } + + @Test + public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { + final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "20")); + final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.LESS_THAN, "25")); + final List results = repository.findAll(Specifications.where(spec).and(spec1)); + + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } +} diff --git a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java index 3b85cfb487..55fde80add 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java @@ -1,112 +1,122 @@ -package org.baeldung.persistence.query; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.baeldung.persistence.model.User; -import org.junit.Before; -import org.junit.Test; -import org.springframework.test.context.ActiveProfiles; - -import com.jayway.restassured.RestAssured; -import com.jayway.restassured.response.Response; -import com.jayway.restassured.specification.RequestSpecification; - -//@RunWith(SpringJUnit4ClassRunner.class) -//@ContextConfiguration(classes = { ConfigTest.class, PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class) -@ActiveProfiles("test") -public class JPASpecificationLiveTest { - - // @Autowired - // private UserRepository repository; - - private User userJohn; - - private User userTom; - - private final String URL_PREFIX = "http://localhost:8082/spring-security-rest-full/auth/users/spec?search="; - - @Before - public void init() { - userJohn = new User(); - userJohn.setFirstName("john"); - userJohn.setLastName("doe"); - userJohn.setEmail("john@doe.com"); - userJohn.setAge(22); - // repository.save(userJohn); - - userTom = new User(); - userTom.setFirstName("tom"); - userTom.setLastName("doe"); - userTom.setEmail("tom@doe.com"); - userTom.setAge(26); - // repository.save(userTom); - } - - @Test - public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "firstName:john,lastName:doe"); - final String result = response.body().asString(); - - assertTrue(result.contains(userJohn.getEmail())); - assertFalse(result.contains(userTom.getEmail())); - } - - @Test - public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "firstName!john"); - final String result = response.body().asString(); - - assertTrue(result.contains(userTom.getEmail())); - assertFalse(result.contains(userJohn.getEmail())); - } - - @Test - public void givenMinAge_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "age>25"); - final String result = response.body().asString(); - - assertTrue(result.contains(userTom.getEmail())); - assertFalse(result.contains(userJohn.getEmail())); - } - - @Test - public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "firstName:jo*"); - final String result = response.body().asString(); - - assertTrue(result.contains(userJohn.getEmail())); - assertFalse(result.contains(userTom.getEmail())); - } - - @Test - public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "firstName:*n"); - final String result = response.body().asString(); - - assertTrue(result.contains(userJohn.getEmail())); - assertFalse(result.contains(userTom.getEmail())); - } - - @Test - public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "firstName:*oh*"); - final String result = response.body().asString(); - - assertTrue(result.contains(userJohn.getEmail())); - assertFalse(result.contains(userTom.getEmail())); - } - - @Test - public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(URL_PREFIX + "age>20,age<25"); - final String result = response.body().asString(); - - assertTrue(result.contains(userJohn.getEmail())); - assertFalse(result.contains(userTom.getEmail())); - } - - private final RequestSpecification givenAuth() { - return RestAssured.given().auth().preemptive().basic("user1", "user1Pass"); - } -} +package org.baeldung.persistence.query; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.response.Response; +import com.jayway.restassured.specification.RequestSpecification; +import org.baeldung.persistence.model.User; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +//@RunWith(SpringJUnit4ClassRunner.class) +//@ContextConfiguration(classes = { ConfigTest.class, +// PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class) +@ActiveProfiles("test") +public class JPASpecificationLiveTest { + + // @Autowired + // private UserRepository repository; + + private User userJohn; + + private User userTom; + + private final String URL_PREFIX = "http://localhost:8082/spring-security-rest-full/auth/users/spec?search="; + + @Before + public void init() { + userJohn = new User(); + userJohn.setFirstName("john"); + userJohn.setLastName("doe"); + userJohn.setEmail("john@doe.com"); + userJohn.setAge(22); + // repository.save(userJohn); + + userTom = new User(); + userTom.setFirstName("tom"); + userTom.setLastName("doe"); + userTom.setEmail("tom@doe.com"); + userTom.setAge(26); + // repository.save(userTom); + } + + private final String EURL_PREFIX = "http://localhost:8082/spring-security-rest-full/auth/users/espec?search="; + + @Test + public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(EURL_PREFIX + "'firstName:john,lastName:doe"); + final String result = response.body().asString(); + assertTrue(result.contains(userJohn.getEmail())); + assertTrue(result.contains(userTom.getEmail())); + } + + @Test + public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "firstName:john,lastName:doe"); + final String result = response.body().asString(); + + assertTrue(result.contains(userJohn.getEmail())); + assertFalse(result.contains(userTom.getEmail())); + } + + @Test + public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "firstName!john"); + final String result = response.body().asString(); + + assertTrue(result.contains(userTom.getEmail())); + assertFalse(result.contains(userJohn.getEmail())); + } + + @Test + public void givenMinAge_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "age>25"); + final String result = response.body().asString(); + + assertTrue(result.contains(userTom.getEmail())); + assertFalse(result.contains(userJohn.getEmail())); + } + + @Test + public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "firstName:jo*"); + final String result = response.body().asString(); + + assertTrue(result.contains(userJohn.getEmail())); + assertFalse(result.contains(userTom.getEmail())); + } + + @Test + public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "firstName:*n"); + final String result = response.body().asString(); + + assertTrue(result.contains(userJohn.getEmail())); + assertFalse(result.contains(userTom.getEmail())); + } + + @Test + public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "firstName:*oh*"); + final String result = response.body().asString(); + + assertTrue(result.contains(userJohn.getEmail())); + assertFalse(result.contains(userTom.getEmail())); + } + + @Test + public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { + final Response response = givenAuth().get(URL_PREFIX + "age>20,age<25"); + final String result = response.body().asString(); + + assertTrue(result.contains(userJohn.getEmail())); + assertFalse(result.contains(userTom.getEmail())); + } + + private final RequestSpecification givenAuth() { + return RestAssured.given().auth().preemptive().basic("user1", "user1Pass"); + } +} From c2bcb6338481708d3d9502ccbdde11453fbbe991 Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Thu, 16 Mar 2017 19:00:27 +0530 Subject: [PATCH 014/149] changing banner format in plain text (#1417) * rest with spark java * 4 * Update Application.java * indentation changes * spring @requestmapping shortcuts * removing spring requestmapping and pushing spring-mvc-java * Joining/Splitting Strings with Java and Stream API * adding more join/split functionality * changing package name * testcase change * adding webutils * adding testcase for WebUtils and ServletRequestUtils * adding testcase * spring-security-stormpath * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial * changing banner format in plain text * Delete banner.txt~ * Delete b.txt~ --- spring-boot/src/main/resources/banner.txt | 33 ++++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/spring-boot/src/main/resources/banner.txt b/spring-boot/src/main/resources/banner.txt index c45ff763bf..abfa666eb6 100644 --- a/spring-boot/src/main/resources/banner.txt +++ b/spring-boot/src/main/resources/banner.txt @@ -1,19 +1,14 @@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}:${AnsiColor.WHITE}.${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}*${AnsiColor.WHITE}:${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}.${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}:${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}#${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}:${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}o${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.WHITE}*${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BRIGHT_BLACK}o${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.WHITE}o${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.WHITE}*${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_WHITE}.${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}&${AnsiColor.WHITE}:${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}o${AnsiColor.BRIGHT_BLACK}8${AnsiColor.WHITE}*${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}8${AnsiColor.BRIGHT_BLACK}&${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BRIGHT_BLACK}#${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ -${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@${AnsiColor.BLACK}@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@#@@@@@########@@@@@@@@@@@@@@@@@@@@@@@@...@@@@@@@@@:..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@#. @@@@@* *@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@#o @@@@@* @@@@@* @@@:*.*@@@@@@@: *8@@@ @@@@&:.#@. @o**@@@@**:@o*o@@:.:@@@@@:.o#@&*:@@@@ +@@@@@@@@@@@@* @@@@@* 8888 8@ @@@8 #@o 8@# .@ @@* :. @* @@@@ @. : &@ ** .@@@@ +@@@@@@@@@@. @ o@@@@@* *@@@o::& .* 8@@@@. @@ 8@@@@. @* @@@@ @. @@@& * @@@@# .@@@@ +@@@@@@@@@& @ @@@@@@* @@@@@@ 8 @@@@ .. o&&&&&&& @@ #@@@@. @* @@@@ @. @@@# * @@@@@ .@@@@ +@@@@@@@@@ @@o @@@@@@@* oooo* 8 @@@& @* @@@ # 88. 88. *& o#: @. @@@# *@ &#& .@@@@ +@@@@@@@@# @@@8 @@@@@@@* .*@@@#. *@@ @@@& :#@@@o .@@: *&@8 @o o@@: @. @@@# *@@#. :8# .@@@@ +@@@@@@@@@ @@@@ &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# o@@@@ @@@@@ +@@@@@& &@@@@ 8@@@@@@@@@8&8@@@@@#8#@@@o8@#&@@o&@@@&@@8@@&@@@@88@@8#@8&@@##@@@@@@#8@@#8@@88@@@@@ *@@@@@@@ +@@@# #@@@@#. @@@@@@@@@@@@@8@@8#o@&#@@@@o.@o*@@*.@@@.@&:8o8*@@@8&@@#@@@8@@@@8@#@@@8&@@@@@@#@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \ No newline at end of file From 6aefd62288e64513319d467af0deded953ba972f Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Thu, 16 Mar 2017 19:29:32 +0100 Subject: [PATCH 015/149] Update .travis.yml (#1419) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7da572edf9..6063fbf3e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ install: travis_wait 40 mvn -q clean install -Dgib.enabled=true jdk: - oraclejdk8 -sudo: false addons: apt: packages: From dbc2c49fe2af9ac0bfb7def7fa3730e8f4e6727f Mon Sep 17 00:00:00 2001 From: Vivek Kumar Date: Fri, 17 Mar 2017 00:04:53 +0530 Subject: [PATCH 016/149] BAEL-578: Add spring-kafka module (#1407) --- spring-kafka/README.md | 9 +++ spring-kafka/pom.xml | 46 ++++++++++++ .../spring/kafka/KafkaApplication.java | 72 +++++++++++++++++++ .../spring/kafka/KafkaConsumerConfig.java | 45 ++++++++++++ .../spring/kafka/KafkaProducerConfig.java | 36 ++++++++++ .../src/main/resources/application.properties | 2 + 6 files changed, 210 insertions(+) create mode 100644 spring-kafka/README.md create mode 100644 spring-kafka/pom.xml create mode 100644 spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java create mode 100644 spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java create mode 100644 spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java create mode 100644 spring-kafka/src/main/resources/application.properties diff --git a/spring-kafka/README.md b/spring-kafka/README.md new file mode 100644 index 0000000000..2731eca042 --- /dev/null +++ b/spring-kafka/README.md @@ -0,0 +1,9 @@ +# Spring Kakfa + +This is a simple Spring Boot app to demonstrate sending and receiving of messages in Kafka using spring-kafka. + +As Kafka topics are not created automatically by default, this application requires that a topic named 'baeldung' is created manually. + +`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic baeldung` + +Two listeners with group Ids **foo** and **bar** are configured. When run successfully, the *Hello World!* message will be received by both the listeners and logged on console. diff --git a/spring-kafka/pom.xml b/spring-kafka/pom.xml new file mode 100644 index 0000000000..73eaf3acff --- /dev/null +++ b/spring-kafka/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + com.baeldung + spring-kafka + 0.0.1-SNAPSHOT + + spring-kafka + Intro to Kafka with Spring + + + 1.8 + 1.1.3.RELEASE + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.2.RELEASE + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.kafka + spring-kafka + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java new file mode 100644 index 0000000000..252054a9f1 --- /dev/null +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java @@ -0,0 +1,72 @@ +package com.baeldung.spring.kafka; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; + +@SpringBootApplication +public class KafkaApplication { + + public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(KafkaApplication.class, args); + MessageProducer producer = context.getBean(MessageProducer.class); + producer.sendMessage("Hello, World!"); + + MessageListener listener = context.getBean(MessageListener.class); + listener.latch.await(20, TimeUnit.SECONDS); + Thread.sleep(60000); + context.close(); + + } + + @Bean + public MessageProducer messageProducer() { + return new MessageProducer(); + } + + @Bean + public MessageListener messageListener() { + return new MessageListener(); + } + + public static class MessageProducer { + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Value(value = "${message.topic.name}") + private String topicName; + + public void sendMessage(String message) { + kafkaTemplate.send(topicName, message); + } + + } + + public static class MessageListener { + + private CountDownLatch latch = new CountDownLatch(2); + + @KafkaListener(topics = "${message.topic.name}", group = "foo", containerFactory = "fooKafkaListenerContainerFactory") + public void listenGroupFoo(String message) { + System.out.println("Received Messasge in group 'foo': " + message); + latch.countDown(); + } + + @KafkaListener(topics = "${message.topic.name}", group = "bar", containerFactory = "barKafkaListenerContainerFactory") + public void listenGroupBar(String message) { + System.out.println("Received Messasge in group 'bar': " + message); + latch.countDown(); + } + + } + +} diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java new file mode 100644 index 0000000000..f9edda2435 --- /dev/null +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.kafka; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; + +@EnableKafka +@Configuration +public class KafkaConsumerConfig { + + @Value(value = "${kafka.bootstrapAddress}") + private String bootstrapAddress; + + public ConsumerFactory consumerFactory(String groupId) { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); + props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + return new DefaultKafkaConsumerFactory<>(props); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory fooKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("foo")); + return factory; + } + + @Bean + public ConcurrentKafkaListenerContainerFactory barKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("bar")); + return factory; + } +} diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java new file mode 100644 index 0000000000..4f9f9719ee --- /dev/null +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java @@ -0,0 +1,36 @@ +package com.baeldung.spring.kafka; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +@Configuration +public class KafkaProducerConfig { + + @Value(value = "${kafka.bootstrapAddress}") + private String bootstrapAddress; + + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap(); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return new DefaultKafkaProducerFactory(configProps); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + KafkaTemplate template = + new KafkaTemplate(producerFactory()); + return template; + } +} diff --git a/spring-kafka/src/main/resources/application.properties b/spring-kafka/src/main/resources/application.properties new file mode 100644 index 0000000000..a1d73b204c --- /dev/null +++ b/spring-kafka/src/main/resources/application.properties @@ -0,0 +1,2 @@ +kafka.bootstrapAddress=localhost:9092 +message.topic.name=baeldung From a794db3183eee1b46f773f7290ca111868184fd8 Mon Sep 17 00:00:00 2001 From: lor6 Date: Fri, 17 Mar 2017 04:30:52 +0200 Subject: [PATCH 017/149] internationalization app (#1394) --- spring-boot/pom.xml | 2 +- .../InternationalizationApp.java | 15 ++++++++ .../config/MvcConfig.java | 38 +++++++++++++++++++ .../config/PageController.java | 14 +++++++ .../src/main/resources/messages.properties | 4 ++ .../src/main/resources/messages_fr.properties | 4 ++ .../resources/templates/international.html | 29 ++++++++++++++ 7 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 spring-boot/src/main/java/com/baeldung/internationalization/InternationalizationApp.java create mode 100644 spring-boot/src/main/java/com/baeldung/internationalization/config/MvcConfig.java create mode 100644 spring-boot/src/main/java/com/baeldung/internationalization/config/PageController.java create mode 100644 spring-boot/src/main/resources/messages.properties create mode 100644 spring-boot/src/main/resources/messages_fr.properties create mode 100644 spring-boot/src/main/resources/templates/international.html diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 7a305322a6..f087617709 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.1.RELEASE + 1.5.2.RELEASE diff --git a/spring-boot/src/main/java/com/baeldung/internationalization/InternationalizationApp.java b/spring-boot/src/main/java/com/baeldung/internationalization/InternationalizationApp.java new file mode 100644 index 0000000000..c92d1c32e6 --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/internationalization/InternationalizationApp.java @@ -0,0 +1,15 @@ +package com.baeldung.internationalization; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class InternationalizationApp { + @RolesAllowed("*") + public static void main(String[] args) { + System.setProperty("security.basic.enabled", "false"); + SpringApplication.run(InternationalizationApp.class, args); + } +} diff --git a/spring-boot/src/main/java/com/baeldung/internationalization/config/MvcConfig.java b/spring-boot/src/main/java/com/baeldung/internationalization/config/MvcConfig.java new file mode 100644 index 0000000000..59f7fd3ba5 --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/internationalization/config/MvcConfig.java @@ -0,0 +1,38 @@ +package com.baeldung.internationalization.config; + +import java.util.Locale; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.baeldung.internationalization.config") +public class MvcConfig extends WebMvcConfigurerAdapter { + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver slr = new SessionLocaleResolver(); + slr.setDefaultLocale(Locale.US); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/spring-boot/src/main/java/com/baeldung/internationalization/config/PageController.java b/spring-boot/src/main/java/com/baeldung/internationalization/config/PageController.java new file mode 100644 index 0000000000..96a534b853 --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/internationalization/config/PageController.java @@ -0,0 +1,14 @@ +package com.baeldung.internationalization.config; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class PageController { + + @GetMapping("/international") + public String getInternationalPage() { + return "international"; + } + +} diff --git a/spring-boot/src/main/resources/messages.properties b/spring-boot/src/main/resources/messages.properties new file mode 100644 index 0000000000..e4dbc44c3f --- /dev/null +++ b/spring-boot/src/main/resources/messages.properties @@ -0,0 +1,4 @@ +greeting=Hello! Welcome to our website! +lang.change=Change the language +lang.eng=English +lang.fr=French \ No newline at end of file diff --git a/spring-boot/src/main/resources/messages_fr.properties b/spring-boot/src/main/resources/messages_fr.properties new file mode 100644 index 0000000000..ac5853717d --- /dev/null +++ b/spring-boot/src/main/resources/messages_fr.properties @@ -0,0 +1,4 @@ +greeting=Bonjour! Bienvenue sur notre site! +lang.change=Changez la langue +lang.eng=Anglais +lang.fr=Francais \ No newline at end of file diff --git a/spring-boot/src/main/resources/templates/international.html b/spring-boot/src/main/resources/templates/international.html new file mode 100644 index 0000000000..a2a5fbb591 --- /dev/null +++ b/spring-boot/src/main/resources/templates/international.html @@ -0,0 +1,29 @@ + + + + +Home + + + + +

+ +

+: + + + \ No newline at end of file From fbd5d1d2a81780c2d3eabd6ba75c3f4e7f759f14 Mon Sep 17 00:00:00 2001 From: Mohamed Sanaulla Date: Fri, 17 Mar 2017 10:36:27 +0300 Subject: [PATCH 018/149] code for Introduction to Project Jigsaw BAEL-603 (#1421) --- core-java-9/compile-modules.sh | 1 + core-java-9/compile-student-client.bat | 3 +++ core-java-9/compile-student-model.bat | 2 ++ .../compile-student-service-dbimpl.bat | 3 +++ core-java-9/compile-student-service.bat | 3 +++ core-java-9/run-student-client.bat | 1 + core-java-9/run-student-client.sh | 1 + .../student/client/StudentClient.java | 16 +++++++++++ .../module-info.java | 3 +++ .../com/baeldung/student/model/Student.java | 12 +++++++++ .../module-info.java | 3 +++ .../service/dbimpl/StudentDbService.java | 27 +++++++++++++++++++ .../module-info.java | 4 +++ .../student/service/StudentService.java | 14 ++++++++++ .../module-info.java | 4 +++ 15 files changed, 97 insertions(+) create mode 100644 core-java-9/compile-modules.sh create mode 100644 core-java-9/compile-student-client.bat create mode 100644 core-java-9/compile-student-model.bat create mode 100644 core-java-9/compile-student-service-dbimpl.bat create mode 100644 core-java-9/compile-student-service.bat create mode 100644 core-java-9/run-student-client.bat create mode 100644 core-java-9/run-student-client.sh create mode 100644 core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java create mode 100644 core-java-9/src/modules/com.baeldung.student.client/module-info.java create mode 100644 core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java create mode 100644 core-java-9/src/modules/com.baeldung.student.model/module-info.java create mode 100644 core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java create mode 100644 core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java create mode 100644 core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java create mode 100644 core-java-9/src/modules/com.baeldung.student.service/module-info.java diff --git a/core-java-9/compile-modules.sh b/core-java-9/compile-modules.sh new file mode 100644 index 0000000000..4c9521de75 --- /dev/null +++ b/core-java-9/compile-modules.sh @@ -0,0 +1 @@ +javac -d mods --module-source-path src/modules $(find src/modules -name "*.java") \ No newline at end of file diff --git a/core-java-9/compile-student-client.bat b/core-java-9/compile-student-client.bat new file mode 100644 index 0000000000..72b2774480 --- /dev/null +++ b/core-java-9/compile-student-client.bat @@ -0,0 +1,3 @@ +javac --module-path mods -d mods/com.baeldung.student.client^ + src/modules/com.baeldung.student.client/module-info.java^ + src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java \ No newline at end of file diff --git a/core-java-9/compile-student-model.bat b/core-java-9/compile-student-model.bat new file mode 100644 index 0000000000..902756c274 --- /dev/null +++ b/core-java-9/compile-student-model.bat @@ -0,0 +1,2 @@ +javac -d mods/com.baeldung.student.model src/modules/com.baeldung.student.model/module-info.java^ + src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java \ No newline at end of file diff --git a/core-java-9/compile-student-service-dbimpl.bat b/core-java-9/compile-student-service-dbimpl.bat new file mode 100644 index 0000000000..bd1cfb7cfe --- /dev/null +++ b/core-java-9/compile-student-service-dbimpl.bat @@ -0,0 +1,3 @@ +javac --module-path mods -d mods/com.baeldung.student.service.dbimpl^ + src/modules/com.baeldung.student.service.dbimpl/module-info.java^ + src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java \ No newline at end of file diff --git a/core-java-9/compile-student-service.bat b/core-java-9/compile-student-service.bat new file mode 100644 index 0000000000..2892b237d1 --- /dev/null +++ b/core-java-9/compile-student-service.bat @@ -0,0 +1,3 @@ +javac --module-path mods -d mods/com.baeldung.student.service^ + src/modules/com.baeldung.student.service/module-info.java^ + src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java \ No newline at end of file diff --git a/core-java-9/run-student-client.bat b/core-java-9/run-student-client.bat new file mode 100644 index 0000000000..2b78a26ec4 --- /dev/null +++ b/core-java-9/run-student-client.bat @@ -0,0 +1 @@ +java --module-path mods -m com.baeldung.student.client/com.baeldung.student.client.StudentClient \ No newline at end of file diff --git a/core-java-9/run-student-client.sh b/core-java-9/run-student-client.sh new file mode 100644 index 0000000000..2b78a26ec4 --- /dev/null +++ b/core-java-9/run-student-client.sh @@ -0,0 +1 @@ +java --module-path mods -m com.baeldung.student.client/com.baeldung.student.client.StudentClient \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java b/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java new file mode 100644 index 0000000000..b5b5d1eead --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java @@ -0,0 +1,16 @@ +package com.baeldung.student.client; + +import com.baeldung.student.service.StudentService; +import com.baeldung.student.service.dbimpl.StudentDbService; +import com.baeldung.student.model.Student; + +public class StudentClient{ + + public static void main(String[] args) { + StudentService service = new StudentDbService(); + service.create(new Student()); + service.read("17SS0001"); + service.update(new Student()); + service.delete("17SS0001"); + } +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.client/module-info.java b/core-java-9/src/modules/com.baeldung.student.client/module-info.java new file mode 100644 index 0000000000..3979297068 --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.client/module-info.java @@ -0,0 +1,3 @@ +module com.baeldung.student.client{ + requires com.baeldung.student.service.dbimpl; +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java b/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java new file mode 100644 index 0000000000..21c0b4e0ae --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java @@ -0,0 +1,12 @@ +package com.baeldung.student.model; + +import java.util.Date; + +public class Student{ + public String registrationId; + public String firstName; + public String lastName; + public Date dateOfBirth; + public String city; + public String country; +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.model/module-info.java b/core-java-9/src/modules/com.baeldung.student.model/module-info.java new file mode 100644 index 0000000000..6f60e3b4dc --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.model/module-info.java @@ -0,0 +1,3 @@ +module com.baeldung.student.model{ + exports com.baeldung.student.model; +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java new file mode 100644 index 0000000000..fb24a440f0 --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java @@ -0,0 +1,27 @@ +package com.baeldung.student.service.dbimpl; + +import com.baeldung.student.service.StudentService; +import com.baeldung.student.model.Student; + +public class StudentDbService implements StudentService{ + + public String create(Student student){ + System.out.println("Creating student in DB..."); + return student.registrationId; + } + + public Student read(String registrationId){ + System.out.println("Reading student from DB..."); + return new Student(); + } + + public Student update(Student student){ + System.out.println("Updating sutdent in DB..."); + return student; + } + + public String delete(String registrationId){ + System.out.println("Deleteing sutdent in DB..."); + return registrationId; + } +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java new file mode 100644 index 0000000000..a55b4e0f80 --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java @@ -0,0 +1,4 @@ +module com.baeldung.student.service.dbimpl{ + requires transitive com.baeldung.student.service; + exports com.baeldung.student.service.dbimpl; +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java b/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java new file mode 100644 index 0000000000..3c0070ff49 --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java @@ -0,0 +1,14 @@ +package com.baeldung.student.service; + +import com.baeldung.student.model.Student; + +public interface StudentService{ + + public String create(Student student); + + public Student read(String registrationId); + + public Student update(Student student); + + public String delete(String registrationId); +} \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service/module-info.java b/core-java-9/src/modules/com.baeldung.student.service/module-info.java new file mode 100644 index 0000000000..c083e62776 --- /dev/null +++ b/core-java-9/src/modules/com.baeldung.student.service/module-info.java @@ -0,0 +1,4 @@ +module com.baeldung.student.service{ + requires transitive com.baeldung.student.model; + exports com.baeldung.student.service; +} \ No newline at end of file From ac50687880e6bb3ee8c0b3d1bf210744147d4c9a Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Fri, 17 Mar 2017 08:19:37 -0500 Subject: [PATCH 019/149] README files for BAEL-393 and BAEL-541 (#1409) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files --- guice/README.md | 4 ++++ spring-security-mvc-boot/README.MD | 1 + 2 files changed, 5 insertions(+) create mode 100644 guice/README.md diff --git a/guice/README.md b/guice/README.md new file mode 100644 index 0000000000..d1bd1ff883 --- /dev/null +++ b/guice/README.md @@ -0,0 +1,4 @@ +## Google Guice Tutorials Project + +### Relevant Articles +- [Guide to Google Guice](http://www.baeldung.com/guice) diff --git a/spring-security-mvc-boot/README.MD b/spring-security-mvc-boot/README.MD index 3e789dedad..70b0f23cbb 100644 --- a/spring-security-mvc-boot/README.MD +++ b/spring-security-mvc-boot/README.MD @@ -6,3 +6,4 @@ The "REST With Spring" Classes: http://github.learnspringsecurity.com - [Custom AccessDecisionVoters in Spring Security](http://www.baeldung.com/spring-security-custom-voter) - [Spring Security: Authentication with a Database-backed UserDetailsService](http://www.baeldung.com/spring-security-authentication-with-a-database) - [Two Login Pages with Spring Security](http://www.baeldung.com/spring-security-two-login-pages) +- [Multiple Entry Points in Spring Security](http://www.baeldung.com/spring-security-multiple-entry-points) From e7e6326a0034fd40a5bc43ac5d35a00d5371cab7 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 17 Mar 2017 13:57:34 -0400 Subject: [PATCH 020/149] Spring MVC Custom Validator --- spring-mvc-java/pom.xml | 23 ++++++++++- .../ContactNumberConstraint.java | 22 ++++++++++ .../ContactNumberValidator.java | 19 +++++++++ .../com/baeldung/model/ValidatedPhone.java | 18 ++++++++ .../controller/ValidatedPhoneController.java | 35 ++++++++++++++++ .../src/main/webapp/WEB-INF/mvc-servlet.xml | 16 ++++++-- .../main/webapp/WEB-INF/view/phoneHome.jsp | 38 +++++++++++++++++ .../controller/CustomMVCValidatorTest.java | 41 +++++++++++++++++++ 8 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java create mode 100644 spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java create mode 100644 spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java create mode 100644 spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java create mode 100644 spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp create mode 100644 spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java diff --git a/spring-mvc-java/pom.xml b/spring-mvc-java/pom.xml index ef18cef3e0..0f6dbfbd98 100644 --- a/spring-mvc-java/pom.xml +++ b/spring-mvc-java/pom.xml @@ -166,7 +166,28 @@ poi-ooxml ${poi.version} - + + + + javax.validation + validation-api + 1.1.0.Final + + + org.hibernate + hibernate-validator + 5.1.2.Final + + + javax.el + javax.el-api + 2.2.4 + + + org.glassfish.web + javax.el + 2.2.4 + diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java new file mode 100644 index 0000000000..2fba2720c3 --- /dev/null +++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java @@ -0,0 +1,22 @@ +package com.baeldung.customvalidator; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Documented +@Constraint(validatedBy = ContactNumberValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContactNumberConstraint { + + String message() default "Invalid phone number"; + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java new file mode 100644 index 0000000000..a7eb7a9df4 --- /dev/null +++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java @@ -0,0 +1,19 @@ +package com.baeldung.customvalidator; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class ContactNumberValidator implements ConstraintValidator { + + @Override + public void initialize(ContactNumberConstraint contactNumber) {} + + @Override + public boolean isValid(String contactField, ConstraintValidatorContext cxt) { + if(contactField == null) { + return false; + } + return contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14); + } + +} diff --git a/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java new file mode 100644 index 0000000000..f860394707 --- /dev/null +++ b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java @@ -0,0 +1,18 @@ +package com.baeldung.model; + +import com.baeldung.customvalidator.ContactNumberConstraint; + +public class ValidatedPhone { + + @ContactNumberConstraint + private String phone; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + +} diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java new file mode 100644 index 0000000000..70b151e066 --- /dev/null +++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java @@ -0,0 +1,35 @@ +package com.baeldung.web.controller; + +import javax.validation.Valid; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import com.baeldung.model.ValidatedPhone; + +@Controller +@EnableWebMvc +public class ValidatedPhoneController { + + @RequestMapping(value="/validatePhone", method=RequestMethod.GET) + public String loadFormPage(Model m) { + m.addAttribute("validatedPhone", new ValidatedPhone()); + return "phoneHome"; + } + + @RequestMapping(value="/addValidatePhone", method=RequestMethod.POST) + public String submitForm(@Valid ValidatedPhone validatedPhone, BindingResult result, Model m) { + if(result.hasErrors()) { + return "phoneHome"; + } + + m.addAttribute("message", "Successfully saved phone: " + validatedPhone.toString()); + return "phoneHome"; + } + + +} diff --git a/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml index 4ba9642448..b0a4d4892a 100644 --- a/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml +++ b/spring-mvc-java/src/main/webapp/WEB-INF/mvc-servlet.xml @@ -1,6 +1,14 @@ - - + + + \ No newline at end of file diff --git a/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp b/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp new file mode 100644 index 0000000000..b873e9bc5f --- /dev/null +++ b/spring-mvc-java/src/main/webapp/WEB-INF/view/phoneHome.jsp @@ -0,0 +1,38 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> + + + + + Sample Form + + + + +
+ +

Phone Number

+
${message}
+ + + + + + +
+ + +
+
+ + diff --git a/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java new file mode 100644 index 0000000000..069cc8e141 --- /dev/null +++ b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CustomMVCValidatorTest.java @@ -0,0 +1,41 @@ +package com.baeldung.web.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +public class CustomMVCValidatorTest { + + private MockMvc mockMvc; + + @Before + public void setup(){ + this.mockMvc = MockMvcBuilders.standaloneSetup(new ValidatedPhoneController()).build(); + } + + @Test + public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage() throws Exception{ + this.mockMvc.perform(get("/validatePhone")).andExpect(view().name("phoneHome")); + } + + @Test + public void givenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.post("/addValidatePhone"). + accept(MediaType.TEXT_HTML). + param("phoneInput", "123")). + andExpect(model().attributeHasFieldErrorCode("validatedPhone", "phone","ContactNumberConstraint")). + andExpect(view().name("phoneHome")). + andExpect(status().isOk()). + andDo(print()); + } + +} From 99bebe806c3bd50eaddd5e1ab006c299ce4c9cfb Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 18 Mar 2017 07:05:02 +0100 Subject: [PATCH 021/149] Refactor flattening lists example (#1372) --- .../flattennestedlist/FlattenNestedList.java | 17 ----- .../FlattenNestedListTest.java | 76 +++++++++++-------- 2 files changed, 44 insertions(+), 49 deletions(-) delete mode 100644 core-java/src/main/java/com/baeldung/list/flattennestedlist/FlattenNestedList.java diff --git a/core-java/src/main/java/com/baeldung/list/flattennestedlist/FlattenNestedList.java b/core-java/src/main/java/com/baeldung/list/flattennestedlist/FlattenNestedList.java deleted file mode 100644 index 11ee66560b..0000000000 --- a/core-java/src/main/java/com/baeldung/list/flattennestedlist/FlattenNestedList.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.baeldung.list.flattennestedlist; - -import java.util.ArrayList; -import java.util.List; - -public class FlattenNestedList { - - public List flattenListOfLists(List> lol) { - - // flatten the list - List ls = new ArrayList<>(); - lol.forEach((k) -> ls.addAll(k)); - - return ls; - } - -} diff --git a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java index 09bfdae9a5..cf9334954b 100644 --- a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java +++ b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java @@ -1,52 +1,64 @@ package com.baeldung.list.flattennestedlist; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class FlattenNestedListTest { - private static final Logger LOGGER = LoggerFactory.getLogger(FlattenNestedListTest.class); - private FlattenNestedList flol; - - @Before - public void setup() { - flol = new FlattenNestedList(); - } - @Test - public void givenListOfListOfString_flattenNestedList() { - - // create the list to flatten + public void givenListOfListOfString_flattenNestedList1() { + // given List ls1 = Arrays.asList("one:one", "one:two", "one:three"); List ls2 = Arrays.asList("two:one", "two:two", "two:three"); List ls3 = Arrays.asList("three:one", "three:two", "three:three"); - List> lol = new ArrayList<>(); - lol.addAll(Arrays.asList(ls1, ls2, ls3)); - - // show nested list - LOGGER.debug("\nNested list: "); - lol.forEach((nl) -> LOGGER.debug(nl + "")); + List> list = Arrays.asList(ls1, ls2, ls3); - // flatten it - List ls = flol.flattenListOfLists(lol); + // when + List ls = flattenListOfListsImperatively(list); + // then assertNotNull(ls); assertTrue(ls.size() == 9); - - // show flattened list - LOGGER.debug("\nFlattened list:"); - ls.forEach((l) -> LOGGER.debug(l)); + //TODO content assertion + } + @Test + public void givenListOfListOfString_flattenNestedList2() { + // given + List ls1 = Arrays.asList("one:one", "one:two", "one:three"); + List ls2 = Arrays.asList("two:one", "two:two", "two:three"); + List ls3 = Arrays.asList("three:one", "three:two", "three:three"); + + List> list = Arrays.asList(ls1, ls2, ls3); + + // when + List ls = flattenListOfListsStream(list); + + // then + assertNotNull(ls); + assertTrue(ls.size() == 9); + //TODO content assertion + } + + public List flattenListOfListsImperatively(List> list) { + List ls = new ArrayList<>(); + list.forEach(ls::addAll); + return ls; + } + + public List flattenListOfListsStream(List> list) { + return list.stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); } } From dc90aace68ccb29428970fde4283784c405ed1a6 Mon Sep 17 00:00:00 2001 From: Mohamed Sanaulla Date: Sat, 18 Mar 2017 10:18:04 +0300 Subject: [PATCH 022/149] incorporate few review comments for bael-603 (#1429) --- .../student/client/StudentClient.java | 16 ++++---- .../module-info.java | 2 +- .../com/baeldung/student/model/Student.java | 17 +++++---- .../module-info.java | 2 +- .../service/dbimpl/StudentDbService.java | 37 ++++++++++--------- .../module-info.java | 5 ++- .../student/service/StudentService.java | 10 ++--- .../module-info.java | 4 +- 8 files changed, 50 insertions(+), 43 deletions(-) diff --git a/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java b/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java index b5b5d1eead..e6fce9163f 100644 --- a/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java +++ b/core-java-9/src/modules/com.baeldung.student.client/com/baeldung/student/client/StudentClient.java @@ -4,13 +4,13 @@ import com.baeldung.student.service.StudentService; import com.baeldung.student.service.dbimpl.StudentDbService; import com.baeldung.student.model.Student; -public class StudentClient{ +public class StudentClient { - public static void main(String[] args) { - StudentService service = new StudentDbService(); - service.create(new Student()); - service.read("17SS0001"); - service.update(new Student()); - service.delete("17SS0001"); - } + public static void main(String[] args) { + StudentService service = new StudentDbService(); + service.create(new Student()); + service.read("17SS0001"); + service.update(new Student()); + service.delete("17SS0001"); + } } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.client/module-info.java b/core-java-9/src/modules/com.baeldung.student.client/module-info.java index 3979297068..7ef7b430fc 100644 --- a/core-java-9/src/modules/com.baeldung.student.client/module-info.java +++ b/core-java-9/src/modules/com.baeldung.student.client/module-info.java @@ -1,3 +1,3 @@ module com.baeldung.student.client{ - requires com.baeldung.student.service.dbimpl; + requires com.baeldung.student.service.dbimpl; } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java b/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java index 21c0b4e0ae..d7f8f69107 100644 --- a/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java +++ b/core-java-9/src/modules/com.baeldung.student.model/com/baeldung/student/model/Student.java @@ -2,11 +2,14 @@ package com.baeldung.student.model; import java.util.Date; -public class Student{ - public String registrationId; - public String firstName; - public String lastName; - public Date dateOfBirth; - public String city; - public String country; +public class Student { + private String registrationId; + + public String getRegistrationId() { + return registrationId; + } + + public void setRegistrationId(String registrationId) { + this.registrationId = registrationId; + } } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.model/module-info.java b/core-java-9/src/modules/com.baeldung.student.model/module-info.java index 6f60e3b4dc..3bdab058d4 100644 --- a/core-java-9/src/modules/com.baeldung.student.model/module-info.java +++ b/core-java-9/src/modules/com.baeldung.student.model/module-info.java @@ -1,3 +1,3 @@ module com.baeldung.student.model{ - exports com.baeldung.student.model; + exports com.baeldung.student.model; } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java index fb24a440f0..2519da085b 100644 --- a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java +++ b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/com/baeldung/student/service/dbimpl/StudentDbService.java @@ -2,26 +2,29 @@ package com.baeldung.student.service.dbimpl; import com.baeldung.student.service.StudentService; import com.baeldung.student.model.Student; +import java.util.logging.*; -public class StudentDbService implements StudentService{ +public class StudentDbService implements StudentService { - public String create(Student student){ - System.out.println("Creating student in DB..."); - return student.registrationId; - } + private static Logger logger = Logger.getLogger("StudentDbService"); - public Student read(String registrationId){ - System.out.println("Reading student from DB..."); - return new Student(); - } + public String create(Student student) { + logger.log(Level.INFO, "Creating student in DB..."); + return student.getRegistrationId(); + } - public Student update(Student student){ - System.out.println("Updating sutdent in DB..."); - return student; - } + public Student read(String registrationId) { + logger.log(Level.INFO, "Reading student from DB..."); + return new Student(); + } - public String delete(String registrationId){ - System.out.println("Deleteing sutdent in DB..."); - return registrationId; - } + public Student update(Student student) { + logger.log(Level.INFO, "Updating sutdent in DB..."); + return student; + } + + public String delete(String registrationId) { + logger.log(Level.INFO, "Deleteing sutdent in DB..."); + return registrationId; + } } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java index a55b4e0f80..96a453ea6b 100644 --- a/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java +++ b/core-java-9/src/modules/com.baeldung.student.service.dbimpl/module-info.java @@ -1,4 +1,5 @@ module com.baeldung.student.service.dbimpl{ - requires transitive com.baeldung.student.service; - exports com.baeldung.student.service.dbimpl; + requires transitive com.baeldung.student.service; + exports com.baeldung.student.service.dbimpl; + requires java.logging; } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java b/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java index 3c0070ff49..6076bf12e3 100644 --- a/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java +++ b/core-java-9/src/modules/com.baeldung.student.service/com/baeldung/student/service/StudentService.java @@ -2,13 +2,13 @@ package com.baeldung.student.service; import com.baeldung.student.model.Student; -public interface StudentService{ +public interface StudentService { - public String create(Student student); + public String create(Student student); - public Student read(String registrationId); + public Student read(String registrationId); - public Student update(Student student); + public Student update(Student student); - public String delete(String registrationId); + public String delete(String registrationId); } \ No newline at end of file diff --git a/core-java-9/src/modules/com.baeldung.student.service/module-info.java b/core-java-9/src/modules/com.baeldung.student.service/module-info.java index c083e62776..5de9e58348 100644 --- a/core-java-9/src/modules/com.baeldung.student.service/module-info.java +++ b/core-java-9/src/modules/com.baeldung.student.service/module-info.java @@ -1,4 +1,4 @@ module com.baeldung.student.service{ - requires transitive com.baeldung.student.model; - exports com.baeldung.student.service; + requires transitive com.baeldung.student.model; + exports com.baeldung.student.service; } \ No newline at end of file From 42f1ef0bf38782db75a00cfb5a7af678b490407a Mon Sep 17 00:00:00 2001 From: slavisa-baeldung Date: Sat, 18 Mar 2017 09:20:33 +0000 Subject: [PATCH 023/149] BAEL-112 - custom validator - format fixes --- .../ContactNumberConstraint.java | 6 +++-- .../ContactNumberValidator.java | 7 +++--- .../com/baeldung/model/ValidatedPhone.java | 6 ++++- .../controller/ValidatedPhoneController.java | 24 +++++++++---------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java index 2fba2720c3..dbd38c1122 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java +++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberConstraint.java @@ -11,12 +11,14 @@ import javax.validation.Payload; @Documented @Constraint(validatedBy = ContactNumberValidator.class) -@Target( { ElementType.METHOD, ElementType.FIELD }) +@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ContactNumberConstraint { String message() default "Invalid phone number"; + Class[] groups() default {}; + Class[] payload() default {}; - + } diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java index a7eb7a9df4..713b7429cf 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java +++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java @@ -6,14 +6,15 @@ import javax.validation.ConstraintValidatorContext; public class ContactNumberValidator implements ConstraintValidator { @Override - public void initialize(ContactNumberConstraint contactNumber) {} + public void initialize(ContactNumberConstraint contactNumber) { + } @Override public boolean isValid(String contactField, ConstraintValidatorContext cxt) { - if(contactField == null) { + if (contactField == null) { return false; } - return contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14); + return contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14); } } diff --git a/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java index f860394707..be702a8517 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java +++ b/spring-mvc-java/src/main/java/com/baeldung/model/ValidatedPhone.java @@ -3,7 +3,7 @@ package com.baeldung.model; import com.baeldung.customvalidator.ContactNumberConstraint; public class ValidatedPhone { - + @ContactNumberConstraint private String phone; @@ -15,4 +15,8 @@ public class ValidatedPhone { this.phone = phone; } + @Override + public String toString() { + return phone; + } } diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java index 70b151e066..54b0e19e60 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java +++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java @@ -15,21 +15,21 @@ import com.baeldung.model.ValidatedPhone; @EnableWebMvc public class ValidatedPhoneController { - @RequestMapping(value="/validatePhone", method=RequestMethod.GET) + @RequestMapping(value = "/validatePhone", method = RequestMethod.GET) public String loadFormPage(Model m) { - m.addAttribute("validatedPhone", new ValidatedPhone()); - return "phoneHome"; + m.addAttribute("validatedPhone", new ValidatedPhone()); + return "phoneHome"; } - - @RequestMapping(value="/addValidatePhone", method=RequestMethod.POST) + + @RequestMapping(value = "/addValidatePhone", method = RequestMethod.POST) public String submitForm(@Valid ValidatedPhone validatedPhone, BindingResult result, Model m) { - if(result.hasErrors()) { - return "phoneHome"; - } - - m.addAttribute("message", "Successfully saved phone: " + validatedPhone.toString()); + if (result.hasErrors()) { return "phoneHome"; + } + + m.addAttribute("message", "Successfully saved phone: " + validatedPhone.toString()); + return "phoneHome"; } - - + + } From 6300112fc66ef339a1e1815e3d49bc9d851703e8 Mon Sep 17 00:00:00 2001 From: mariiakulik Date: Sat, 18 Mar 2017 20:25:06 +0100 Subject: [PATCH 024/149] README files update (#1435) * Create README.md * Update README.md * Update README.md * Create README.md * Update README.md * Update README.md * Update README.md * Create README.md * Update README.md * Create README.md * Update README.md * Create README.md * Update README.md * Update README.md * Create README.md * Create README.md * Update README.md * Update README.md * Update README.md * Create README.md * Create README.md * Update README.md * Update README.md * Update README.md * Update README.md * Create README.md * Update README.md * Update README.md * Update README.md * Update README.MD * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Create README.md * Create README.md * Update README.md * Create README.md * Update README.md * Update README.md * Create README.md * Create README.md * Update README.md * Update README.md * Update README.MD * Update README.MD * Create README.md --- Twitter4J/README.md | 3 +++ aws/README.md | 3 +++ axon/README.md | 3 +++ core-java-9/README.md | 5 ++++- core-java/README.md | 4 ++++ guava/README.md | 1 + hbase/README.md | 3 +++ java-websocket/README.md | 3 +++ jooq/README.md | 3 +++ kotlin/README.md | 1 + libraries/README.md | 11 ++++++++++- .../src/test/java/com/baeldung/cglib/proxy/README.md | 3 +++ log4j2/README.md | 3 +++ mesos-marathon/README.md | 3 +++ mockito2/README.md | 4 +++- protobuffer/README.md | 3 +++ rabbitmq/README.md | 3 +++ ratpack/README.md | 3 +++ reactor-core/README.md | 3 +++ redis/README.md | 1 + spring-boot/README.MD | 4 +++- spring-cloud/spring-cloud-bootstrap/README.MD | 2 ++ spring-hibernate5/README.md | 3 +++ spring-ldap/README.md | 5 ++++- spring-security-openid/README.md | 6 +++++- spring-security-stormpath/README.md | 3 +++ struts2/README.md | 3 +++ 27 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 Twitter4J/README.md create mode 100644 aws/README.md create mode 100644 axon/README.md create mode 100644 hbase/README.md create mode 100644 java-websocket/README.md create mode 100644 jooq/README.md create mode 100644 libraries/src/test/java/com/baeldung/cglib/proxy/README.md create mode 100644 log4j2/README.md create mode 100644 mesos-marathon/README.md create mode 100644 protobuffer/README.md create mode 100644 rabbitmq/README.md create mode 100644 ratpack/README.md create mode 100644 reactor-core/README.md create mode 100644 spring-hibernate5/README.md create mode 100644 spring-security-stormpath/README.md create mode 100644 struts2/README.md diff --git a/Twitter4J/README.md b/Twitter4J/README.md new file mode 100644 index 0000000000..3057c1c4b2 --- /dev/null +++ b/Twitter4J/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Introduction to Twitter4J](http://www.baeldung.com/twitter4j) diff --git a/aws/README.md b/aws/README.md new file mode 100644 index 0000000000..10db004765 --- /dev/null +++ b/aws/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [AWS Lambda Using DynamoDB With Java](http://www.baeldung.com/aws-lambda-dynamodb-java) diff --git a/axon/README.md b/axon/README.md new file mode 100644 index 0000000000..f1ae5d00d8 --- /dev/null +++ b/axon/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [A Guide to the Axon Framework](http://www.baeldung.com/axon-cqrs-event-sourcing) diff --git a/core-java-9/README.md b/core-java-9/README.md index 6e58383a5e..a6cda8e883 100644 --- a/core-java-9/README.md +++ b/core-java-9/README.md @@ -8,4 +8,7 @@ - [Java 9 Stream API Improvements](http://www.baeldung.com/java-9-stream-api) - [Java 9 Convenience Factory Methods for Collections](http://www.baeldung.com/java-9-collections-factory-methods) - [New Stream Collectors in Java 9](http://www.baeldung.com/java9-stream-collectors) -- [Java 9 CompletableFuture API Improvements](http://www.baeldung.com/java9-completablefuture-api-improvements/) \ No newline at end of file +- [Java 9 CompletableFuture API Improvements](http://www.baeldung.com/java9-completablefuture-api-improvements/) +- [Spring Security – Redirect to the Previous URL After Login](http://www.baeldung.com/spring-security-redirect-login) +- [Java 9 Process API Improvements](http://www.baeldung.com/java-9-process-api) +- [Introduction to Java 9 StackWalking API](http://www.baeldung.com/java-9-stackwalking-api) diff --git a/core-java/README.md b/core-java/README.md index a34908d8ae..3cdc8c2290 100644 --- a/core-java/README.md +++ b/core-java/README.md @@ -79,3 +79,7 @@ - [The Java HashMap Under the Hood](http://www.baeldung.com/java-hashmap) - [A Guide to LinkedHashMap in Java](http://www.baeldung.com/java-linked-hashmap) - [A Guide to TreeMap in Java](http://www.baeldung.com/java-treemap) +- [Finding Max/Min of a List or Collection](http://www.baeldung.com/java-collection-min-max) +- [Guide to java.util.concurrent.Locks](http://www.baeldung.com/java-concurrent-locks) +- [Java Primitive Conversions](http://www.baeldung.com/java-primitive-conversions) +- [Java Money and the Currency API](http://www.baeldung.com/java-money-and-currency) diff --git a/guava/README.md b/guava/README.md index ee224bae5f..f46c4dd3de 100644 --- a/guava/README.md +++ b/guava/README.md @@ -24,3 +24,4 @@ - [Guide to Guava RangeSet](http://www.baeldung.com/guava-rangeset) - [Guide to Guava RangeMap](http://www.baeldung.com/guava-rangemap) - [Guide to Guava Table](http://www.baeldung.com/guava-table) +- [Guide to Guava’s Reflection Utilities](http://www.baeldung.com/guava-reflection) diff --git a/hbase/README.md b/hbase/README.md new file mode 100644 index 0000000000..df2683b27a --- /dev/null +++ b/hbase/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [HBase with Java](http://www.baeldung.com/hbase) diff --git a/java-websocket/README.md b/java-websocket/README.md new file mode 100644 index 0000000000..f9f0784043 --- /dev/null +++ b/java-websocket/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [A Guide to the Java API for WebSocket](http://www.baeldung.com/java-websockets) diff --git a/jooq/README.md b/jooq/README.md new file mode 100644 index 0000000000..2f09cab46b --- /dev/null +++ b/jooq/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Introduction to jOOL](http://www.baeldung.com/jool) diff --git a/kotlin/README.md b/kotlin/README.md index 6447a26f5c..ceebde4573 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -1,3 +1,4 @@ ## Relevant articles: - [Introduction to the Kotlin Language](http://www.baeldung.com/kotlin) +- [A guide to the “when{}†block in Kotlin](http://www.baeldung.com/kotlin-when) diff --git a/libraries/README.md b/libraries/README.md index 7b4f7c36ac..a8ecf56cc2 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -1,5 +1,14 @@ +### Relevant articles + +- [Intro to Jasypt](http://www.baeldung.com/jasypt) +- [Array Processing with Apache Commons Lang 3](http://www.baeldung.com/array-processing-commons-lang) +- [String Processing with Apache Commons Lang 3](http://www.baeldung.com/string-processing-commons-lang) +- [Introduction to Javatuples](http://www.baeldung.com/java-tuples) +- [Introduction to Javassist](http://www.baeldung.com/javassist) + + The libraries module contains examples related to small libraries that are relatively easy to use and does not require any separate module of its own. The code examples related to different libraries should go in a new package. -Remember, for advanced libraries like JUnit, Jackson, etc. we already have separate modules. Please make sure to have a look at the existing modules in such cases. \ No newline at end of file +Remember, for advanced libraries like JUnit, Jackson, etc. we already have separate modules. Please make sure to have a look at the existing modules in such cases. diff --git a/libraries/src/test/java/com/baeldung/cglib/proxy/README.md b/libraries/src/test/java/com/baeldung/cglib/proxy/README.md new file mode 100644 index 0000000000..abeabc6162 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/cglib/proxy/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Introduction to cglib](http://www.baeldung.com/cglib) diff --git a/log4j2/README.md b/log4j2/README.md new file mode 100644 index 0000000000..3afd842c82 --- /dev/null +++ b/log4j2/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Intro to Log4j2 – Appenders, Layouts and Filters](http://www.baeldung.com/log4j2-appenders-layouts-filters) diff --git a/mesos-marathon/README.md b/mesos-marathon/README.md new file mode 100644 index 0000000000..3223eb2478 --- /dev/null +++ b/mesos-marathon/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Simple Jenkins Pipeline with Marathon and Mesos](http://www.baeldung.com/jenkins-pipeline-with-marathon-mesos) diff --git a/mockito2/README.md b/mockito2/README.md index 587f1341bb..49741c66d1 100644 --- a/mockito2/README.md +++ b/mockito2/README.md @@ -1,4 +1,6 @@ -========= +### Relevant articles + +- [Mockito’s Java 8 Features](http://www.baeldung.com/mockito-2-java-8) ## Mockito 2 and Java 8 Tips diff --git a/protobuffer/README.md b/protobuffer/README.md new file mode 100644 index 0000000000..4dcdb3cf52 --- /dev/null +++ b/protobuffer/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Introduction to Google Protocol Buffer](http://www.baeldung.com/google-protocol-buffer) diff --git a/rabbitmq/README.md b/rabbitmq/README.md new file mode 100644 index 0000000000..11300b047f --- /dev/null +++ b/rabbitmq/README.md @@ -0,0 +1,3 @@ +### Relevant articles +- [Introduction to RabbitMQ](http://www.baeldung.com/rabbitmq) + diff --git a/ratpack/README.md b/ratpack/README.md new file mode 100644 index 0000000000..af473ef0e8 --- /dev/null +++ b/ratpack/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Introduction to Ratpack](http://www.baeldung.com/ratpack) diff --git a/reactor-core/README.md b/reactor-core/README.md new file mode 100644 index 0000000000..9d952ec84c --- /dev/null +++ b/reactor-core/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Intro To Reactor Core](http://www.baeldung.com/reactor-core) diff --git a/redis/README.md b/redis/README.md index 3ce9031b62..d179b80c33 100644 --- a/redis/README.md +++ b/redis/README.md @@ -1,2 +1,3 @@ ### Relevant Articles: - [Intro to Jedis – the Java Redis Client Library](http://www.baeldung.com/jedis-java-redis-client-library) +- [A Guide to Redis with Redisson](http://www.baeldung.com/redis-redisson) diff --git a/spring-boot/README.MD b/spring-boot/README.MD index 9fe18aaacc..4c078a5d38 100644 --- a/spring-boot/README.MD +++ b/spring-boot/README.MD @@ -1,7 +1,8 @@ ###The Course The "REST With Spring" Classes: http://bit.ly/restwithspring -###Relevant Articles: +### Relevant Articles: + - [Quick Guide to @RestClientTest in Spring Boot](http://www.baeldung.com/restclienttest-in-spring-boot) - [Intro to Spring Boot Starters](http://www.baeldung.com/spring-boot-starters) - [A Guide to Spring in Eclipse STS](http://www.baeldung.com/eclipse-sts-spring) @@ -11,3 +12,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [A Custom Data Binder in Spring MVC](http://www.baeldung.com/spring-mvc-custom-data-binder) - [Intro to Building an Application with Spring Boot](http://www.baeldung.com/intro-to-spring-boot) - [How to Register a Servlet in a Java Web Application](http://www.baeldung.com/register-servlet) +- [Using Custom Banners in Spring Boot](http://www.baeldung.com/spring-boot-custom-banners) diff --git a/spring-cloud/spring-cloud-bootstrap/README.MD b/spring-cloud/spring-cloud-bootstrap/README.MD index 251c861830..d8eedc3249 100644 --- a/spring-cloud/spring-cloud-bootstrap/README.MD +++ b/spring-cloud/spring-cloud-bootstrap/README.MD @@ -1,6 +1,8 @@ ### Relevant Articles: - [Spring Cloud – Bootstrapping](http://www.baeldung.com/spring-cloud-bootstrapping) - [Spring Cloud – Securing Services](http://www.baeldung.com/spring-cloud-securing-services) +- [Spring Cloud – Tracing Services with Zipkin](http://www.baeldung.com/tracing-services-with-zipkin) + - To run the project: - copy the appliction-config folder to c:\Users\{username}\ on Windows or /Users/{username}/ on *nix. Then open a git bash terminal in application-config and run: diff --git a/spring-hibernate5/README.md b/spring-hibernate5/README.md new file mode 100644 index 0000000000..fd539fdcb6 --- /dev/null +++ b/spring-hibernate5/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Guide to @Immutable Annotation in Hibernate](http://www.baeldung.com/hibernate-immutable) diff --git a/spring-ldap/README.md b/spring-ldap/README.md index 56ffdee617..8dffadb685 100644 --- a/spring-ldap/README.md +++ b/spring-ldap/README.md @@ -1,4 +1,7 @@ -========= +### Relevant articles + +- [Spring LDAP Overview](http://www.baeldung.com/spring-ldap) + ## Spring LDAP Example Project - (http://www.baeldung.com/spring-ldap-overview/) diff --git a/spring-security-openid/README.md b/spring-security-openid/README.md index 79bf44f374..8c65c09f2f 100644 --- a/spring-security-openid/README.md +++ b/spring-security-openid/README.md @@ -1,4 +1,7 @@ -========= +### Relevant articles + +- [Spring Security and OpenID Connect](http://www.baeldung.com/spring-security-openid-connect) + ## OpenID Connect with Spring Security @@ -13,3 +16,4 @@ mvn spring-boot:run - Make sure you set redirect URI to http://localhost:8081/google-login - Once you have your client id and secret, make sure you add them to the `application.properties` of the project + diff --git a/spring-security-stormpath/README.md b/spring-security-stormpath/README.md new file mode 100644 index 0000000000..f83882112f --- /dev/null +++ b/spring-security-stormpath/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Spring Security with Stormpath](http://www.baeldung.com/spring-security-stormpath) diff --git a/struts2/README.md b/struts2/README.md new file mode 100644 index 0000000000..8a1425ccb5 --- /dev/null +++ b/struts2/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [A Quick Struts 2 Intro](http://www.baeldung.com/struts-2-intro) From 8981008cba13bd614d1dfea35805a7bc757c2149 Mon Sep 17 00:00:00 2001 From: mariiakulik Date: Sat, 18 Mar 2017 22:38:31 +0100 Subject: [PATCH 025/149] Update README.md (#1432) --- core-java/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core-java/README.md b/core-java/README.md index 3cdc8c2290..63e3748ec6 100644 --- a/core-java/README.md +++ b/core-java/README.md @@ -79,7 +79,9 @@ - [The Java HashMap Under the Hood](http://www.baeldung.com/java-hashmap) - [A Guide to LinkedHashMap in Java](http://www.baeldung.com/java-linked-hashmap) - [A Guide to TreeMap in Java](http://www.baeldung.com/java-treemap) +- [A Quick JUnit vs TestNG Comparison](http://www.baeldung.com/junit-vs-testng) - [Finding Max/Min of a List or Collection](http://www.baeldung.com/java-collection-min-max) - [Guide to java.util.concurrent.Locks](http://www.baeldung.com/java-concurrent-locks) - [Java Primitive Conversions](http://www.baeldung.com/java-primitive-conversions) - [Java Money and the Currency API](http://www.baeldung.com/java-money-and-currency) + From 11536181b5f6d5ba0060559434c314cf85f5a5e4 Mon Sep 17 00:00:00 2001 From: mariiakulik Date: Sun, 19 Mar 2017 01:05:04 +0100 Subject: [PATCH 026/149] Update README.MD (#1433) --- spring-boot/README.MD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-boot/README.MD b/spring-boot/README.MD index 4c078a5d38..d70e83525b 100644 --- a/spring-boot/README.MD +++ b/spring-boot/README.MD @@ -12,4 +12,6 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [A Custom Data Binder in Spring MVC](http://www.baeldung.com/spring-mvc-custom-data-binder) - [Intro to Building an Application with Spring Boot](http://www.baeldung.com/intro-to-spring-boot) - [How to Register a Servlet in a Java Web Application](http://www.baeldung.com/register-servlet) +- [Guide to Spring WebUtils and ServletRequestUtils](http://www.baeldung.com/spring-webutils-servletrequestutils) - [Using Custom Banners in Spring Boot](http://www.baeldung.com/spring-boot-custom-banners) + From ae6514b5cf0f32b4a6d948e947861eaa1b251825 Mon Sep 17 00:00:00 2001 From: mujahid Date: Sun, 19 Mar 2017 15:22:06 +0800 Subject: [PATCH 027/149] BAEL-347 Fixes (#1361) * solr-fulltext-search module created * solr-fulltext-search modue created * solr-fulltext-search change s * pom changes merged from upstream * removed integration tests from mvn build * Refactoring test class * removed test profile --- solr-fulltext-search/pom.xml | 34 -- .../service/ItemSearchServiceLiveTest.java | 374 ++++++++++++++++++ 2 files changed, 374 insertions(+), 34 deletions(-) create mode 100644 solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java diff --git a/solr-fulltext-search/pom.xml b/solr-fulltext-search/pom.xml index bed6afd48f..5b2950d64c 100644 --- a/solr-fulltext-search/pom.xml +++ b/solr-fulltext-search/pom.xml @@ -53,38 +53,4 @@
- - - integration - - - - org.apache.maven.plugins - maven-surefire-plugin - - - integration-test - - test - - - - **/*LiveTest.java - - - **/*IntegrationTest.java - - - - - - - json - - - - - - - \ No newline at end of file diff --git a/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java b/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java new file mode 100644 index 0000000000..1489d40787 --- /dev/null +++ b/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java @@ -0,0 +1,374 @@ +package com.baeldung.solr.fulltext.search.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.List; +import java.util.Map; + +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.response.FacetField.Count; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.RangeFacet; +import org.apache.solr.client.solrj.response.SpellCheckResponse; +import org.apache.solr.client.solrj.response.SpellCheckResponse.Suggestion; +import org.apache.solr.client.solrj.response.SuggesterResponse; +import org.apache.solr.common.SolrDocument; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.baeldung.solr.fulltext.search.model.Item; + +public class ItemSearchServiceLiveTest { + + private static SolrClient solrClient; + private static ItemSearchService itemSearchService; + private static final String solrUrl = "http://localhost:8987/solr/item"; + + @BeforeClass + public static void initBeans() throws Exception { + solrClient = new HttpSolrClient.Builder(solrUrl).build(); + itemSearchService = new ItemSearchServiceImpl(solrClient); + + solrClient.commit(); + } + + @Before + public void clearSolrData() throws Exception { + solrClient.deleteByQuery("*:*"); + } + + @Test + public void whenIndexing_thenAvailableOnRetrieval() throws Exception { + itemSearchService.index("hm0001", "Washing Machine", "Home Appliances", 450f); + final SolrDocument indexedDoc = solrClient.getById("hm0001"); + assertEquals("hm0001", indexedDoc.get("id")); + } + + @Test + public void whenIndexingBean_thenAvailableOnRetrieval() throws Exception { + Item item = new Item(); + item.setId("hm0002"); + item.setCategory("Televisions"); + item.setDescription("LED TV 32"); + item.setPrice(500); + itemSearchService.indexBean(item); + } + + @Test + public void whenSearchingByBasicQuery_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "LED TV 32", "Brand1 Home Appliances", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("brand1"); + query.setStart(0); + query.setRows(10); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(3, items.size()); + + } + + @Test + public void whenSearchingWithWildCard_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "LED TV 32", "Brand1 Home Appliances", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("*rand?"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(3, items.size()); + } + + @Test + public void whenSearchingWithLogicalOperators_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "Brand2 LED TV 32", "Washing Appliances", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("brand1 AND (Washing OR Refrigerator)"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(2, items.size()); + } + + @Test + public void whenSearchingWithFields_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("0003", "Brand2 LED TV 32", "Brand1 Washing Home Appliances", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("description:Brand* AND category:*Washing*"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(1, items.size()); + } + + @Test + public void whenSearchingWithPhrase_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("washing MachIne"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(2, items.size()); + } + + @Test + public void whenSearchingWithRealPhrase_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("\"washing machine\""); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(1, items.size()); + } + + @Test + public void whenSearchingPhraseWithProximity_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); + itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("\"Washing equipment\"~2"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(1, items.size()); + } + + @Test + public void whenSearchingWithPriceRange_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Dishwasher", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("price:[100 TO 300]"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(3, items.size()); + } + + @Test + public void whenSearchingWithPriceRangeInclusiveExclusive_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Dishwasher", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); + + SolrQuery query = new SolrQuery(); + query.setQuery("price:{100 TO 300]"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(2, items.size()); + } + + @Test + public void whenSearchingWithFilterQuery_thenAllMatchingItemsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("price:[100 TO 300]"); + query.addFilterQuery("description:Brand1", "category:Home Appliances"); + + QueryResponse response = solrClient.query(query); + List items = response.getBeans(Item.class); + + assertEquals(2, items.size()); + } + + @Test + public void whenSearchingWithFacetFields_thenAllMatchingFacetsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + query.addFacetField("category"); + + QueryResponse response = solrClient.query(query); + List facetResults = response.getFacetField("category").getValues(); + + assertEquals(2, facetResults.size()); + + for (Count count : facetResults) { + if ("categorya".equalsIgnoreCase(count.getName())) { + assertEquals(2, count.getCount()); + } else if ("categoryb".equalsIgnoreCase(count.getName())) { + assertEquals(2, count.getCount()); + } else { + fail("unexpected category"); + } + } + } + + @Test + public void whenSearchingWithFacetQuery_thenAllMatchingFacetsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + + query.addFacetQuery("Washing OR Refrigerator"); + query.addFacetQuery("Brand2"); + + QueryResponse response = solrClient.query(query); + Map facetQueryMap = response.getFacetQuery(); + + assertEquals(2, facetQueryMap.size()); + + for (Map.Entry entry : facetQueryMap.entrySet()) { + String facetQuery = entry.getKey(); + + if ("Washing OR Refrigerator".equals(facetQuery)) { + assertEquals(Integer.valueOf(2), entry.getValue()); + } else if ("Brand2".equals(facetQuery)) { + assertEquals(Integer.valueOf(2), entry.getValue()); + } else { + fail("unexpected query"); + } + + } + } + + @Test + public void whenSearchingWithFacetRange_thenAllMatchingFacetsShouldAvialble() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 125f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 150f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + + query.addNumericRangeFacet("price", 100, 275, 25); + + QueryResponse response = solrClient.query(query); + List rangeFacets = response.getFacetRanges().get(0).getCounts(); + + assertEquals(7, rangeFacets.size()); + } + + @Test + public void whenSearchingWithHitHighlighting_thenKeywordsShouldBeHighlighted() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("Appliances"); + query.setHighlight(true); + query.addHighlightField("category"); + query.setHighlightSimplePre(""); + query.setHighlightSimplePost(""); + QueryResponse response = solrClient.query(query); + + Map>> hitHighlightedMap = response.getHighlighting(); + Map> highlightedFieldMap = hitHighlightedMap.get("hm0001"); + List highlightedList = highlightedFieldMap.get("category"); + String highLightedText = highlightedList.get(0); + + assertEquals("Home Appliances", highLightedText); + + } + + @Test + public void whenSearchingWithKeywordWithMistake_thenSpellingSuggestionsShouldBeReturned() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f); + + SolrQuery query = new SolrQuery(); + query.setQuery("hme"); + query.set("spellcheck", "on"); + QueryResponse response = solrClient.query(query); + + SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse(); + + assertEquals(false, spellCheckResponse.isCorrectlySpelled()); + + Suggestion suggestion = spellCheckResponse.getSuggestions().get(0); + + assertEquals("hme", suggestion.getToken()); + + List alternatives = suggestion.getAlternatives(); + String alternative = alternatives.get(0); + + assertEquals("home", alternative); + } + + @Test + public void whenSearchingWithIncompleteKeyword_thenKeywordSuggestionsShouldBeReturned() throws Exception { + itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); + itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); + itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); + itemSearchService.index("hm0004", "Brand2 Dishwasher", "Home washing equipments", 250f); + + SolrQuery query = new SolrQuery(); + query.setRequestHandler("/suggest"); + query.set("suggest", "true"); + query.set("suggest.build", "true"); + query.set("suggest.dictionary", "mySuggester"); + query.set("suggest.q", "Hom"); + QueryResponse response = solrClient.query(query); + + SuggesterResponse suggesterResponse = response.getSuggesterResponse(); + Map> suggestedTerms = suggesterResponse.getSuggestedTerms(); + List suggestions = suggestedTerms.get("mySuggester"); + + assertEquals(2, suggestions.size()); + + for (String term : suggestions) { + if (!"Home Appliances".equals(term) && !"Home washing equipments".equals(term)) { + fail("Unexpected suggestions"); + } + } + + } + +} From 22b5c4924b175b4e0971f816a1dab74c6f3fb729 Mon Sep 17 00:00:00 2001 From: mariiakulik Date: Sun, 19 Mar 2017 08:33:49 +0100 Subject: [PATCH 028/149] Create README.md (#1434) --- spring-5/src/test/java/com/baeldung/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spring-5/src/test/java/com/baeldung/README.md diff --git a/spring-5/src/test/java/com/baeldung/README.md b/spring-5/src/test/java/com/baeldung/README.md new file mode 100644 index 0000000000..f7b358ec3e --- /dev/null +++ b/spring-5/src/test/java/com/baeldung/README.md @@ -0,0 +1,3 @@ +### Relevant articles + +- [Concurrent Test Execution in Spring 5](http://www.baeldung.com/spring-5-concurrent-tests) From 9d0cb1e2aa674f6974f261b62c020f52b44aba9b Mon Sep 17 00:00:00 2001 From: Yasin Date: Sun, 19 Mar 2017 14:21:25 +0530 Subject: [PATCH 029/149] BAEL-722 Intro to JSONassert (#1437) * yasin.bhojawala@gmail.com Evaluation article on Different Types of Bean Injection in Spring * Revert "yasin.bhojawala@gmail.com" This reverts commit 963cc51a7a15b75b550108fe4e198cd65a274032. * Fixing compilation error and removing unused import * Introduction to Java9 StackWalking API - yasin.bhojawala@gmail.com Code examples for the article "Introduction to Java9 StackWalking API" * BAEL-608 Introduction to Java9 StackWalking API * BAEL-608 Introduction to Java9 StackWalking API changing the test names to BDD style * BAEL-608 Introduction to Java9 StackWalking API correcting the typo * BAEL-608 Introduction to Java9 StackWalking API improving method names * BAEL-608 Introduction to Java9 StackWalking API test method names improvements * BAEL-718 Quick intro to javatuples * merging pom from master * BAEL-722 Intro to JSONassert --- .../algorithms/ga/dijkstra/Dijkstra.java | 114 +++++++------- .../algorithms/ga/dijkstra/Graph.java | 42 ++--- .../baeldung/algorithms/ga/dijkstra/Node.java | 116 +++++++------- core-java/0.004102810554955205 | 0 core-java/0.04832801936270381 | 0 core-java/0.5633433244738808 | 0 core-java/0.6256429734439612 | 0 core-java/0.8260098203820962 | 0 core-java/0.9799201796740292 | 0 .../java/com/baeldung/examples/RunGuice.java | 64 ++++---- .../examples/guice/Communication.java | 80 +++++----- .../examples/guice/CommunicationMode.java | 24 +-- .../examples/guice/DefaultCommunicator.java | 102 ++++++------ .../guice/EmailCommunicationMode.java | 48 +++--- .../examples/guice/IMCommunicationMode.java | 60 +++---- .../examples/guice/SMSCommunicationMode.java | 60 +++---- .../examples/guice/aop/MessageLogger.java | 48 +++--- .../guice/aop/MessageSentLoggable.java | 34 ++-- .../examples/guice/binding/AOPModule.java | 46 +++--- .../examples/guice/binding/BasicModule.java | 74 ++++----- .../guice/constant/CommunicationModel.java | 34 ++-- .../examples/guice/marker/Communicator.java | 28 ++-- .../examples/guice/modules/BasicModule.java | 76 ++++----- .../javaeeannotations/LogInFilter.java | 72 ++++----- libraries/pom.xml | 148 +++++++++--------- .../baeldung/jsonassert/JsonAssertTest.java | 115 ++++++++++++++ xml/src/main/resources/customer-binding.xml | 0 27 files changed, 753 insertions(+), 632 deletions(-) delete mode 100644 core-java/0.004102810554955205 delete mode 100644 core-java/0.04832801936270381 delete mode 100644 core-java/0.5633433244738808 delete mode 100644 core-java/0.6256429734439612 delete mode 100644 core-java/0.8260098203820962 delete mode 100644 core-java/0.9799201796740292 create mode 100644 libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java mode change 100755 => 100644 xml/src/main/resources/customer-binding.xml diff --git a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Dijkstra.java b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Dijkstra.java index 046f13983d..0b01e9b48b 100644 --- a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Dijkstra.java +++ b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Dijkstra.java @@ -1,57 +1,57 @@ -package com.baeldung.algorithms.ga.dijkstra; - -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map.Entry; -import java.util.Set; - -public class Dijkstra { - - public static Graph calculateShortestPathFromSource(Graph graph, Node source) { - - source.setDistance(0); - - Set settledNodes = new HashSet<>(); - Set unsettledNodes = new HashSet<>(); - unsettledNodes.add(source); - - while (unsettledNodes.size() != 0) { - Node currentNode = getLowestDistanceNode(unsettledNodes); - unsettledNodes.remove(currentNode); - for (Entry adjacencyPair : currentNode.getAdjacentNodes().entrySet()) { - Node adjacentNode = adjacencyPair.getKey(); - Integer edgeWeigh = adjacencyPair.getValue(); - - if (!settledNodes.contains(adjacentNode)) { - CalculateMinimumDistance(adjacentNode, edgeWeigh, currentNode); - unsettledNodes.add(adjacentNode); - } - } - settledNodes.add(currentNode); - } - return graph; - } - - private static void CalculateMinimumDistance(Node evaluationNode, Integer edgeWeigh, Node sourceNode) { - Integer sourceDistance = sourceNode.getDistance(); - if (sourceDistance + edgeWeigh < evaluationNode.getDistance()) { - evaluationNode.setDistance(sourceDistance + edgeWeigh); - LinkedList shortestPath = new LinkedList<>(sourceNode.getShortestPath()); - shortestPath.add(sourceNode); - evaluationNode.setShortestPath(shortestPath); - } - } - - private static Node getLowestDistanceNode(Set unsettledNodes) { - Node lowestDistanceNode = null; - int lowestDistance = Integer.MAX_VALUE; - for (Node node : unsettledNodes) { - int nodeDistance = node.getDistance(); - if (nodeDistance < lowestDistance) { - lowestDistance = nodeDistance; - lowestDistanceNode = node; - } - } - return lowestDistanceNode; - } -} +package com.baeldung.algorithms.ga.dijkstra; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map.Entry; +import java.util.Set; + +public class Dijkstra { + + public static Graph calculateShortestPathFromSource(Graph graph, Node source) { + + source.setDistance(0); + + Set settledNodes = new HashSet<>(); + Set unsettledNodes = new HashSet<>(); + unsettledNodes.add(source); + + while (unsettledNodes.size() != 0) { + Node currentNode = getLowestDistanceNode(unsettledNodes); + unsettledNodes.remove(currentNode); + for (Entry adjacencyPair : currentNode.getAdjacentNodes().entrySet()) { + Node adjacentNode = adjacencyPair.getKey(); + Integer edgeWeigh = adjacencyPair.getValue(); + + if (!settledNodes.contains(adjacentNode)) { + CalculateMinimumDistance(adjacentNode, edgeWeigh, currentNode); + unsettledNodes.add(adjacentNode); + } + } + settledNodes.add(currentNode); + } + return graph; + } + + private static void CalculateMinimumDistance(Node evaluationNode, Integer edgeWeigh, Node sourceNode) { + Integer sourceDistance = sourceNode.getDistance(); + if (sourceDistance + edgeWeigh < evaluationNode.getDistance()) { + evaluationNode.setDistance(sourceDistance + edgeWeigh); + LinkedList shortestPath = new LinkedList<>(sourceNode.getShortestPath()); + shortestPath.add(sourceNode); + evaluationNode.setShortestPath(shortestPath); + } + } + + private static Node getLowestDistanceNode(Set unsettledNodes) { + Node lowestDistanceNode = null; + int lowestDistance = Integer.MAX_VALUE; + for (Node node : unsettledNodes) { + int nodeDistance = node.getDistance(); + if (nodeDistance < lowestDistance) { + lowestDistance = nodeDistance; + lowestDistanceNode = node; + } + } + return lowestDistanceNode; + } +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Graph.java b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Graph.java index 00db4b01e4..76694ed76e 100644 --- a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Graph.java +++ b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Graph.java @@ -1,21 +1,21 @@ -package com.baeldung.algorithms.ga.dijkstra; - -import java.util.HashSet; -import java.util.Set; - -public class Graph { - - private Set nodes = new HashSet<>(); - - public void addNode(Node nodeA) { - nodes.add(nodeA); - } - - public Set getNodes() { - return nodes; - } - - public void setNodes(Set nodes) { - this.nodes = nodes; - } -} +package com.baeldung.algorithms.ga.dijkstra; + +import java.util.HashSet; +import java.util.Set; + +public class Graph { + + private Set nodes = new HashSet<>(); + + public void addNode(Node nodeA) { + nodes.add(nodeA); + } + + public Set getNodes() { + return nodes; + } + + public void setNodes(Set nodes) { + this.nodes = nodes; + } +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Node.java b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Node.java index 256f12e204..ac34bfadd1 100644 --- a/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Node.java +++ b/algorithms/src/main/java/com/baeldung/algorithms/ga/dijkstra/Node.java @@ -1,58 +1,58 @@ -package com.baeldung.algorithms.ga.dijkstra; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class Node { - - private String name; - - private LinkedList shortestPath = new LinkedList<>(); - - private Integer distance = Integer.MAX_VALUE; - - private Map adjacentNodes = new HashMap<>(); - - public Node(String name) { - this.name = name; - } - - public void addDestination(Node destination, int distance) { - adjacentNodes.put(destination, distance); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getAdjacentNodes() { - return adjacentNodes; - } - - public void setAdjacentNodes(Map adjacentNodes) { - this.adjacentNodes = adjacentNodes; - } - - public Integer getDistance() { - return distance; - } - - public void setDistance(Integer distance) { - this.distance = distance; - } - - public List getShortestPath() { - return shortestPath; - } - - public void setShortestPath(LinkedList shortestPath) { - this.shortestPath = shortestPath; - } - -} +package com.baeldung.algorithms.ga.dijkstra; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class Node { + + private String name; + + private LinkedList shortestPath = new LinkedList<>(); + + private Integer distance = Integer.MAX_VALUE; + + private Map adjacentNodes = new HashMap<>(); + + public Node(String name) { + this.name = name; + } + + public void addDestination(Node destination, int distance) { + adjacentNodes.put(destination, distance); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getAdjacentNodes() { + return adjacentNodes; + } + + public void setAdjacentNodes(Map adjacentNodes) { + this.adjacentNodes = adjacentNodes; + } + + public Integer getDistance() { + return distance; + } + + public void setDistance(Integer distance) { + this.distance = distance; + } + + public List getShortestPath() { + return shortestPath; + } + + public void setShortestPath(LinkedList shortestPath) { + this.shortestPath = shortestPath; + } + +} diff --git a/core-java/0.004102810554955205 b/core-java/0.004102810554955205 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-java/0.04832801936270381 b/core-java/0.04832801936270381 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-java/0.5633433244738808 b/core-java/0.5633433244738808 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-java/0.6256429734439612 b/core-java/0.6256429734439612 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-java/0.8260098203820962 b/core-java/0.8260098203820962 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core-java/0.9799201796740292 b/core-java/0.9799201796740292 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/guice/src/main/java/com/baeldung/examples/RunGuice.java b/guice/src/main/java/com/baeldung/examples/RunGuice.java index a07447cde8..660952f325 100644 --- a/guice/src/main/java/com/baeldung/examples/RunGuice.java +++ b/guice/src/main/java/com/baeldung/examples/RunGuice.java @@ -1,32 +1,32 @@ - -package com.baeldung.examples; - -import com.baeldung.examples.guice.Communication; -import com.baeldung.examples.guice.binding.AOPModule; -import com.baeldung.examples.guice.modules.BasicModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import java.util.Scanner; - -/** - * - * @author baeldung - */ -public class RunGuice { - - public static void main(String[] args) { - Injector injector = Guice.createInjector(new BasicModule(), new AOPModule()); - Communication comms = injector.getInstance(Communication.class); - Scanner scanner = new Scanner(System.in); - while (true) { - String input = scanner.nextLine(); - if (input.equalsIgnoreCase("q")) { - System.exit(0); - } else { - comms.sendMessage(input); - } - - } - - } -} + +package com.baeldung.examples; + +import com.baeldung.examples.guice.Communication; +import com.baeldung.examples.guice.binding.AOPModule; +import com.baeldung.examples.guice.modules.BasicModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.Scanner; + +/** + * + * @author baeldung + */ +public class RunGuice { + + public static void main(String[] args) { + Injector injector = Guice.createInjector(new BasicModule(), new AOPModule()); + Communication comms = injector.getInstance(Communication.class); + Scanner scanner = new Scanner(System.in); + while (true) { + String input = scanner.nextLine(); + if (input.equalsIgnoreCase("q")) { + System.exit(0); + } else { + comms.sendMessage(input); + } + + } + + } +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/Communication.java b/guice/src/main/java/com/baeldung/examples/guice/Communication.java index 7f7cb822d8..464e0c641d 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/Communication.java +++ b/guice/src/main/java/com/baeldung/examples/guice/Communication.java @@ -1,40 +1,40 @@ - -package com.baeldung.examples.guice; - -import com.google.inject.Inject; -import com.google.inject.name.Named; -import java.util.Date; -import java.util.LinkedList; -import java.util.Queue; -import java.util.logging.Logger; - -/** - * - * @author baeldung - */ -public class Communication { - - final Date start = new Date(); - - @Inject - private Logger logger; - - @Inject - private DefaultCommunicator communicator; - - public Communication(Boolean keepRecords) { - if (keepRecords) { - System.out.println("keeping records"); - } - } - - public boolean sendMessage(String message) { - - return communicator.sendMessage(message); - } - - public DefaultCommunicator getCommunicator() { - return this.communicator; - } - -} + +package com.baeldung.examples.guice; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import java.util.Date; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Logger; + +/** + * + * @author baeldung + */ +public class Communication { + + final Date start = new Date(); + + @Inject + private Logger logger; + + @Inject + private DefaultCommunicator communicator; + + public Communication(Boolean keepRecords) { + if (keepRecords) { + System.out.println("keeping records"); + } + } + + public boolean sendMessage(String message) { + + return communicator.sendMessage(message); + } + + public DefaultCommunicator getCommunicator() { + return this.communicator; + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/CommunicationMode.java b/guice/src/main/java/com/baeldung/examples/guice/CommunicationMode.java index 444b775478..7a36f0c276 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/CommunicationMode.java +++ b/guice/src/main/java/com/baeldung/examples/guice/CommunicationMode.java @@ -1,12 +1,12 @@ - -package com.baeldung.examples.guice; - -import com.baeldung.examples.guice.constant.CommunicationModel; - -public interface CommunicationMode { - - public CommunicationModel getMode(); - - public boolean sendMessage(String message); - -} + +package com.baeldung.examples.guice; + +import com.baeldung.examples.guice.constant.CommunicationModel; + +public interface CommunicationMode { + + public CommunicationModel getMode(); + + public boolean sendMessage(String message); + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/DefaultCommunicator.java b/guice/src/main/java/com/baeldung/examples/guice/DefaultCommunicator.java index c65644646a..24e0c28dd1 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/DefaultCommunicator.java +++ b/guice/src/main/java/com/baeldung/examples/guice/DefaultCommunicator.java @@ -1,51 +1,51 @@ - -package com.baeldung.examples.guice; - -import com.baeldung.examples.guice.marker.Communicator; -import com.google.inject.Inject; -import com.google.inject.name.Named; - -/** - * - * @author baeldung - */ -public class DefaultCommunicator implements Communicator { - - private CommunicationMode defaultCommsMode; - @Inject - @Named("SMSComms") - CommunicationMode smsCommsMode; - @Inject - @Named("EmailComms") - CommunicationMode emailCommsMode; - @Inject - @Named("IMComms") - CommunicationMode imCommsMode; - - protected DefaultCommunicator(CommunicationMode defaultComms) { - this.defaultCommsMode = defaultComms; - } - - public DefaultCommunicator() { - - } - - public boolean sendMessage(String message) { - boolean sent = false; - if (defaultCommsMode != null) { - sent = sendMessageByDefault(message); - } else { - sent = smsCommsMode.sendMessage(message); - } - return sent; - } - - private boolean sendMessageByDefault(String message) { - boolean sent = false; - if (message != null && !message.trim().equals("")) { - return defaultCommsMode.sendMessage(message); - } - return sent; - } - -} + +package com.baeldung.examples.guice; + +import com.baeldung.examples.guice.marker.Communicator; +import com.google.inject.Inject; +import com.google.inject.name.Named; + +/** + * + * @author baeldung + */ +public class DefaultCommunicator implements Communicator { + + private CommunicationMode defaultCommsMode; + @Inject + @Named("SMSComms") + CommunicationMode smsCommsMode; + @Inject + @Named("EmailComms") + CommunicationMode emailCommsMode; + @Inject + @Named("IMComms") + CommunicationMode imCommsMode; + + protected DefaultCommunicator(CommunicationMode defaultComms) { + this.defaultCommsMode = defaultComms; + } + + public DefaultCommunicator() { + + } + + public boolean sendMessage(String message) { + boolean sent = false; + if (defaultCommsMode != null) { + sent = sendMessageByDefault(message); + } else { + sent = smsCommsMode.sendMessage(message); + } + return sent; + } + + private boolean sendMessageByDefault(String message) { + boolean sent = false; + if (message != null && !message.trim().equals("")) { + return defaultCommsMode.sendMessage(message); + } + return sent; + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/EmailCommunicationMode.java b/guice/src/main/java/com/baeldung/examples/guice/EmailCommunicationMode.java index 3caca0edcc..06e77a58e2 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/EmailCommunicationMode.java +++ b/guice/src/main/java/com/baeldung/examples/guice/EmailCommunicationMode.java @@ -1,24 +1,24 @@ - -package com.baeldung.examples.guice; - -import com.baeldung.examples.guice.aop.MessageSentLoggable; -import com.baeldung.examples.guice.constant.CommunicationModel; - -/** - * - * @author baeldung - */ -public class EmailCommunicationMode implements CommunicationMode { - - @Override - public CommunicationModel getMode() { - return CommunicationModel.EMAIL; - } - - @Override - @MessageSentLoggable - public boolean sendMessage(String Message) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - -} + +package com.baeldung.examples.guice; + +import com.baeldung.examples.guice.aop.MessageSentLoggable; +import com.baeldung.examples.guice.constant.CommunicationModel; + +/** + * + * @author baeldung + */ +public class EmailCommunicationMode implements CommunicationMode { + + @Override + public CommunicationModel getMode() { + return CommunicationModel.EMAIL; + } + + @Override + @MessageSentLoggable + public boolean sendMessage(String Message) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/IMCommunicationMode.java b/guice/src/main/java/com/baeldung/examples/guice/IMCommunicationMode.java index bc9bd61449..42b0c82b90 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/IMCommunicationMode.java +++ b/guice/src/main/java/com/baeldung/examples/guice/IMCommunicationMode.java @@ -1,30 +1,30 @@ - -package com.baeldung.examples.guice; - -import com.baeldung.examples.guice.aop.MessageSentLoggable; -import com.baeldung.examples.guice.constant.CommunicationModel; -import com.google.inject.Inject; -import java.util.logging.Logger; - -/** - * - * @author baeldung - */ -public class IMCommunicationMode implements CommunicationMode { - - @Inject - private Logger logger; - - @Override - public CommunicationModel getMode() { - return CommunicationModel.IM; - } - - @Override - @MessageSentLoggable - public boolean sendMessage(String message) { - logger.info("IM Message Sent"); - return true; - } - -} + +package com.baeldung.examples.guice; + +import com.baeldung.examples.guice.aop.MessageSentLoggable; +import com.baeldung.examples.guice.constant.CommunicationModel; +import com.google.inject.Inject; +import java.util.logging.Logger; + +/** + * + * @author baeldung + */ +public class IMCommunicationMode implements CommunicationMode { + + @Inject + private Logger logger; + + @Override + public CommunicationModel getMode() { + return CommunicationModel.IM; + } + + @Override + @MessageSentLoggable + public boolean sendMessage(String message) { + logger.info("IM Message Sent"); + return true; + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/SMSCommunicationMode.java b/guice/src/main/java/com/baeldung/examples/guice/SMSCommunicationMode.java index 28475839dd..7a30e51f10 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/SMSCommunicationMode.java +++ b/guice/src/main/java/com/baeldung/examples/guice/SMSCommunicationMode.java @@ -1,30 +1,30 @@ - -package com.baeldung.examples.guice; - -import com.baeldung.examples.guice.aop.MessageSentLoggable; -import com.baeldung.examples.guice.constant.CommunicationModel; -import com.google.inject.Inject; -import java.util.logging.Logger; - -/** - * - * @author baeldung - */ -public class SMSCommunicationMode implements CommunicationMode { - - @Inject - private Logger logger; - - @Override - public CommunicationModel getMode() { - return CommunicationModel.SMS; - } - - @Override - @MessageSentLoggable - public boolean sendMessage(String message) { - logger.info("SMS message sent"); - return true; - } - -} + +package com.baeldung.examples.guice; + +import com.baeldung.examples.guice.aop.MessageSentLoggable; +import com.baeldung.examples.guice.constant.CommunicationModel; +import com.google.inject.Inject; +import java.util.logging.Logger; + +/** + * + * @author baeldung + */ +public class SMSCommunicationMode implements CommunicationMode { + + @Inject + private Logger logger; + + @Override + public CommunicationModel getMode() { + return CommunicationModel.SMS; + } + + @Override + @MessageSentLoggable + public boolean sendMessage(String message) { + logger.info("SMS message sent"); + return true; + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/aop/MessageLogger.java b/guice/src/main/java/com/baeldung/examples/guice/aop/MessageLogger.java index 2ad5f8b92e..379cd5f18b 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/aop/MessageLogger.java +++ b/guice/src/main/java/com/baeldung/examples/guice/aop/MessageLogger.java @@ -1,24 +1,24 @@ - -package com.baeldung.examples.guice.aop; - -import com.google.inject.Inject; -import java.util.logging.Logger; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -/** - * - * @author baeldung - */ -public class MessageLogger implements MethodInterceptor { - - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - Object[] objectArray = invocation.getArguments(); - int i = 0; - for (Object object : objectArray) { - Logger.getAnonymousLogger().info("Sending message: " + object.toString()); - } - return invocation.proceed(); - } -} + +package com.baeldung.examples.guice.aop; + +import com.google.inject.Inject; +import java.util.logging.Logger; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * + * @author baeldung + */ +public class MessageLogger implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + Object[] objectArray = invocation.getArguments(); + int i = 0; + for (Object object : objectArray) { + Logger.getAnonymousLogger().info("Sending message: " + object.toString()); + } + return invocation.proceed(); + } +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/aop/MessageSentLoggable.java b/guice/src/main/java/com/baeldung/examples/guice/aop/MessageSentLoggable.java index 5e5a411d0e..431c4bc0ce 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/aop/MessageSentLoggable.java +++ b/guice/src/main/java/com/baeldung/examples/guice/aop/MessageSentLoggable.java @@ -1,17 +1,17 @@ - -package com.baeldung.examples.guice.aop; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * - * @author baeldung - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface MessageSentLoggable { - -} + +package com.baeldung.examples.guice.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author baeldung + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MessageSentLoggable { + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/binding/AOPModule.java b/guice/src/main/java/com/baeldung/examples/guice/binding/AOPModule.java index 109d9a6389..b41dcf16e5 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/binding/AOPModule.java +++ b/guice/src/main/java/com/baeldung/examples/guice/binding/AOPModule.java @@ -1,23 +1,23 @@ - -package com.baeldung.examples.guice.binding; - -import com.baeldung.examples.guice.aop.MessageLogger; -import com.baeldung.examples.guice.aop.MessageSentLoggable; -import com.google.inject.AbstractModule; -import com.google.inject.matcher.Matchers; - -/** - * - * @author baeldung - */ -public class AOPModule extends AbstractModule { - - @Override - protected void configure() { - bindInterceptor(Matchers.any(), - Matchers.annotatedWith(MessageSentLoggable.class), - new MessageLogger() - ); - } - -} + +package com.baeldung.examples.guice.binding; + +import com.baeldung.examples.guice.aop.MessageLogger; +import com.baeldung.examples.guice.aop.MessageSentLoggable; +import com.google.inject.AbstractModule; +import com.google.inject.matcher.Matchers; + +/** + * + * @author baeldung + */ +public class AOPModule extends AbstractModule { + + @Override + protected void configure() { + bindInterceptor(Matchers.any(), + Matchers.annotatedWith(MessageSentLoggable.class), + new MessageLogger() + ); + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/binding/BasicModule.java b/guice/src/main/java/com/baeldung/examples/guice/binding/BasicModule.java index 93f0fe54ba..1cd9d624ab 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/binding/BasicModule.java +++ b/guice/src/main/java/com/baeldung/examples/guice/binding/BasicModule.java @@ -1,37 +1,37 @@ - -package com.baeldung.examples.guice.binding; - -import com.baeldung.examples.guice.Communication; -import com.baeldung.examples.guice.CommunicationMode; -import com.baeldung.examples.guice.DefaultCommunicator; -import com.baeldung.examples.guice.EmailCommunicationMode; -import com.baeldung.examples.guice.IMCommunicationMode; -import com.baeldung.examples.guice.SMSCommunicationMode; -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author baeldung - */ -public class BasicModule extends AbstractModule { - - @Override - protected void configure() { - try { - bind(Communication.class).toConstructor(Communication.class.getConstructor(Boolean.TYPE)); - } catch (NoSuchMethodException ex) { - Logger.getLogger(BasicModule.class.getName()).log(Level.SEVERE, null, ex); - } catch (SecurityException ex) { - Logger.getLogger(BasicModule.class.getName()).log(Level.SEVERE, null, ex); - } - bind(DefaultCommunicator.class).annotatedWith(Names.named("AnotherCommunicator")).to(DefaultCommunicator.class).asEagerSingleton(); - - bind(CommunicationMode.class).annotatedWith(Names.named("IMComms")).to(IMCommunicationMode.class); - bind(CommunicationMode.class).annotatedWith(Names.named("EmailComms")).to(EmailCommunicationMode.class); - bind(CommunicationMode.class).annotatedWith(Names.named("SMSComms")).to(SMSCommunicationMode.class); - } - -} + +package com.baeldung.examples.guice.binding; + +import com.baeldung.examples.guice.Communication; +import com.baeldung.examples.guice.CommunicationMode; +import com.baeldung.examples.guice.DefaultCommunicator; +import com.baeldung.examples.guice.EmailCommunicationMode; +import com.baeldung.examples.guice.IMCommunicationMode; +import com.baeldung.examples.guice.SMSCommunicationMode; +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author baeldung + */ +public class BasicModule extends AbstractModule { + + @Override + protected void configure() { + try { + bind(Communication.class).toConstructor(Communication.class.getConstructor(Boolean.TYPE)); + } catch (NoSuchMethodException ex) { + Logger.getLogger(BasicModule.class.getName()).log(Level.SEVERE, null, ex); + } catch (SecurityException ex) { + Logger.getLogger(BasicModule.class.getName()).log(Level.SEVERE, null, ex); + } + bind(DefaultCommunicator.class).annotatedWith(Names.named("AnotherCommunicator")).to(DefaultCommunicator.class).asEagerSingleton(); + + bind(CommunicationMode.class).annotatedWith(Names.named("IMComms")).to(IMCommunicationMode.class); + bind(CommunicationMode.class).annotatedWith(Names.named("EmailComms")).to(EmailCommunicationMode.class); + bind(CommunicationMode.class).annotatedWith(Names.named("SMSComms")).to(SMSCommunicationMode.class); + } + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/constant/CommunicationModel.java b/guice/src/main/java/com/baeldung/examples/guice/constant/CommunicationModel.java index d12420a0db..3483e9cefd 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/constant/CommunicationModel.java +++ b/guice/src/main/java/com/baeldung/examples/guice/constant/CommunicationModel.java @@ -1,17 +1,17 @@ - -package com.baeldung.examples.guice.constant; - -/** - * - * @author baeldung - */ -public enum CommunicationModel { - - EMAIL("Email"), SMS("SMS"), IM("IM"), PHONE("Phone"); - - final String name; - - CommunicationModel(String name) { - this.name = name; - } -} + +package com.baeldung.examples.guice.constant; + +/** + * + * @author baeldung + */ +public enum CommunicationModel { + + EMAIL("Email"), SMS("SMS"), IM("IM"), PHONE("Phone"); + + final String name; + + CommunicationModel(String name) { + this.name = name; + } +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/marker/Communicator.java b/guice/src/main/java/com/baeldung/examples/guice/marker/Communicator.java index 7425f1c283..45c729a9a3 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/marker/Communicator.java +++ b/guice/src/main/java/com/baeldung/examples/guice/marker/Communicator.java @@ -1,14 +1,14 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.baeldung.examples.guice.marker; - -/** - * - * @author Tayo - */ -public interface Communicator { - -} +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.baeldung.examples.guice.marker; + +/** + * + * @author Tayo + */ +public interface Communicator { + +} diff --git a/guice/src/main/java/com/baeldung/examples/guice/modules/BasicModule.java b/guice/src/main/java/com/baeldung/examples/guice/modules/BasicModule.java index ed83cf3649..f27d8b3a53 100644 --- a/guice/src/main/java/com/baeldung/examples/guice/modules/BasicModule.java +++ b/guice/src/main/java/com/baeldung/examples/guice/modules/BasicModule.java @@ -1,38 +1,38 @@ - -package com.baeldung.examples.guice.modules; - -import com.baeldung.examples.guice.Communication; -import com.baeldung.examples.guice.CommunicationMode; -import com.baeldung.examples.guice.DefaultCommunicator; -import com.baeldung.examples.guice.EmailCommunicationMode; -import com.baeldung.examples.guice.IMCommunicationMode; -import com.baeldung.examples.guice.SMSCommunicationMode; -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author baeldung - */ -public class BasicModule extends AbstractModule { - - @Override - protected void configure() { - try { - bind(Communication.class).toConstructor(Communication.class.getConstructor(Boolean.class)); - bind(Boolean.class).toInstance(true); - } catch (NoSuchMethodException ex) { - Logger.getLogger(com.baeldung.examples.guice.binding.BasicModule.class.getName()).log(Level.SEVERE, null, ex); - } catch (SecurityException ex) { - Logger.getLogger(com.baeldung.examples.guice.binding.BasicModule.class.getName()).log(Level.SEVERE, null, ex); - } - bind(DefaultCommunicator.class).annotatedWith(Names.named("AnotherCommunicator")).to(DefaultCommunicator.class).asEagerSingleton(); - - bind(CommunicationMode.class).annotatedWith(Names.named("IMComms")).to(IMCommunicationMode.class); - bind(CommunicationMode.class).annotatedWith(Names.named("EmailComms")).to(EmailCommunicationMode.class); - bind(CommunicationMode.class).annotatedWith(Names.named("SMSComms")).to(SMSCommunicationMode.class); - } - -} + +package com.baeldung.examples.guice.modules; + +import com.baeldung.examples.guice.Communication; +import com.baeldung.examples.guice.CommunicationMode; +import com.baeldung.examples.guice.DefaultCommunicator; +import com.baeldung.examples.guice.EmailCommunicationMode; +import com.baeldung.examples.guice.IMCommunicationMode; +import com.baeldung.examples.guice.SMSCommunicationMode; +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author baeldung + */ +public class BasicModule extends AbstractModule { + + @Override + protected void configure() { + try { + bind(Communication.class).toConstructor(Communication.class.getConstructor(Boolean.class)); + bind(Boolean.class).toInstance(true); + } catch (NoSuchMethodException ex) { + Logger.getLogger(com.baeldung.examples.guice.binding.BasicModule.class.getName()).log(Level.SEVERE, null, ex); + } catch (SecurityException ex) { + Logger.getLogger(com.baeldung.examples.guice.binding.BasicModule.class.getName()).log(Level.SEVERE, null, ex); + } + bind(DefaultCommunicator.class).annotatedWith(Names.named("AnotherCommunicator")).to(DefaultCommunicator.class).asEagerSingleton(); + + bind(CommunicationMode.class).annotatedWith(Names.named("IMComms")).to(IMCommunicationMode.class); + bind(CommunicationMode.class).annotatedWith(Names.named("EmailComms")).to(EmailCommunicationMode.class); + bind(CommunicationMode.class).annotatedWith(Names.named("SMSComms")).to(SMSCommunicationMode.class); + } + +} diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java b/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java index 9f1345139d..4e4aef2672 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java @@ -1,36 +1,36 @@ -package com.baeldung.javaeeannotations; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebFilter( - urlPatterns = "/bankAccount/*", - filterName = "LogInFilter", - description = "Filter all account transaction URLs" - ) -public class LogInFilter implements javax.servlet.Filter { - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse res = (HttpServletResponse) response; - - res.sendRedirect(req.getContextPath() + "/login.jsp"); - chain.doFilter(request, response); - } - - @Override - public void destroy() { - } - -} +package com.baeldung.javaeeannotations; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebFilter( + urlPatterns = "/bankAccount/*", + filterName = "LogInFilter", + description = "Filter all account transaction URLs" + ) +public class LogInFilter implements javax.servlet.Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; + + res.sendRedirect(req.getContextPath() + "/login.jsp"); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } + +} diff --git a/libraries/pom.xml b/libraries/pom.xml index eaae539872..85c777b12c 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -1,77 +1,83 @@ - - parent-modules - com.baeldung - 1.0.0-SNAPSHOT - - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + parent-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 - libraries - libraries - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - + libraries + libraries + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + - - - - cglib - cglib - ${cglib.version} - - - org.apache.commons - commons-lang3 - ${commons-lang.version} - - - junit - junit - ${junit.version} - test - - - org.jasypt - jasypt - ${jasypt.version} - - - org.javatuples - javatuples - ${javatuples.version} - - - org.javassist - javassist - ${javaassist.version} - - - - org.assertj - assertj-core - ${assertj.version} - - + + + + cglib + cglib + ${cglib.version} + + + org.apache.commons + commons-lang3 + ${commons-lang.version} + + + junit + junit + ${junit.version} + test + + + org.jasypt + jasypt + ${jasypt.version} + + + org.javatuples + javatuples + ${javatuples.version} + + + org.javassist + javassist + ${javaassist.version} + + + + org.assertj + assertj-core + ${assertj.version} + + + org.skyscreamer + jsonassert + ${jsonassert.version} + + - - 3.2.4 - 3.5 - 4.12 - 1.9.2 - 1.2 - 3.21.0-GA - 3.6.2 - + + 3.2.4 + 3.5 + 4.12 + 1.9.2 + 1.2 + 3.21.0-GA + 3.6.2 + 1.4.0 + - + \ No newline at end of file diff --git a/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java b/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java new file mode 100644 index 0000000000..c169d83897 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java @@ -0,0 +1,115 @@ +package com.baeldung.jsonassert; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.skyscreamer.jsonassert.Customization; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.skyscreamer.jsonassert.RegularExpressionValueMatcher; +import org.skyscreamer.jsonassert.comparator.ArraySizeComparator; +import org.skyscreamer.jsonassert.comparator.CustomComparator; + +public class JsonAssertTest { + + @Test + public void givenLenientode_whenAssertEqualsSameJsonString_thenPass() throws JSONException { + String actual = "{id:123,name:\"John\"}"; + JSONAssert.assertEquals("{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT); + + actual = "{id:123,name:\"John\",zip:\"33025\"}"; + JSONAssert.assertEquals("{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT); + } + + @Test + public void givenStrictMode_whenAssertNotEqualsExtendedJsonString_thenPass() throws JSONException { + String actual = "{id:123,name:\"John\"}"; + JSONAssert.assertNotEquals("{name:\"John\"}", actual, JSONCompareMode.STRICT); + } + + @Test + public void whenUsingCompareModeOrBoolean_thenBothAreSame() throws JSONException { + String actual = "{id:123,name:\"John\",zip:\"33025\"}"; + JSONAssert.assertEquals("{id:123,name:\"John\"}", actual, JSONCompareMode.LENIENT); + JSONAssert.assertEquals("{id:123,name:\"John\"}", actual, false); + + actual = "{id:123,name:\"John\"}"; + JSONAssert.assertNotEquals("{name:\"John\"}", actual, JSONCompareMode.STRICT); + JSONAssert.assertNotEquals("{name:\"John\"}", actual, true); + } + + @Test + public void givenDifferentOrderForJsonObject_whenAssertEquals_thenPass() throws JSONException { + String result = "{id:1,name:\"John\"}"; + + JSONAssert.assertEquals("{name:\"John\",id:1}", result, JSONCompareMode.STRICT); + JSONAssert.assertEquals("{name:\"John\",id:1}", result, JSONCompareMode.LENIENT); + } + + @Test + public void givenDifferentTypes_whenAssertEqualsSameValue_thenPass() throws JSONException { + JSONObject expected = new JSONObject(); + JSONObject actual = new JSONObject(); + expected.put("id", Integer.valueOf(12345)); + actual.put("id", Double.valueOf(12345)); + + JSONAssert.assertEquals(expected, actual, false); + JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT); + } + + @Test + public void givenNestedObjects_whenAssertEquals_thenPass() throws JSONException { + String result = "{id:1,name:\"Juergen\", address:{city:\"Hollywood\", " + + "state:\"LA\", zip:91601}}"; + JSONAssert.assertEquals("{id:1,name:\"Juergen\", address:{city:\"Hollywood\", " + + "state:\"LA\", zip:91601}}", result, false); + } + + @Test + public void givenArray_whenComparing_thenOrderMustMatchForStrict() throws JSONException { + String result = "[Alex, Barbera, Charlie, Xavier]"; + JSONAssert.assertEquals("[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.LENIENT); + JSONAssert.assertEquals("[Alex, Barbera, Charlie, Xavier]", result, JSONCompareMode.STRICT); + JSONAssert.assertNotEquals("[Charlie, Alex, Xavier, Barbera]", result, JSONCompareMode.STRICT); + } + + @Test + public void givenArray_whenComparingExtended_thenNotEqual() throws JSONException { + String result = "[1,2,3,4,5]"; + JSONAssert.assertEquals("[1,2,3,4,5]", result, JSONCompareMode.LENIENT); + JSONAssert.assertNotEquals("[1,2,3]", result, JSONCompareMode.LENIENT); + JSONAssert.assertNotEquals("[1,2,3,4,5,6]", result, JSONCompareMode.LENIENT); + } + + @Test + public void whenComparingSizeOfArray_thenPass() throws JSONException { + String names = "{names:[Alex, Barbera, Charlie, Xavier]}"; + JSONAssert.assertEquals( + "{names:[4]}", + names, + new ArraySizeComparator(JSONCompareMode.LENIENT)); + } + + @Test + public void whenComparingContentsOfArray_thenPass() throws JSONException { + String ratings = "{ratings:[3.2,3.5,4.1,5,1]}"; + JSONAssert.assertEquals( + "{ratings:[1,5]}", + ratings, + new ArraySizeComparator(JSONCompareMode.LENIENT)); + } + + @Test + public void givenValueMatcher_whenComparingUsingRegex_thenPass() throws IllegalArgumentException, JSONException { + JSONAssert.assertEquals("{entry:{id:x}}", "{entry:{id:1, id:2}}", + new CustomComparator( + JSONCompareMode.STRICT, + new Customization("entry.id", + new RegularExpressionValueMatcher("\\d")))); + + JSONAssert.assertNotEquals("{entry:{id:x}}", "{entry:{id:1, id:as}}", + new CustomComparator(JSONCompareMode.STRICT, + new Customization("entry.id", + new RegularExpressionValueMatcher("\\d")))); + } +} diff --git a/xml/src/main/resources/customer-binding.xml b/xml/src/main/resources/customer-binding.xml old mode 100755 new mode 100644 From 202a19acbffe15e8096f7da347d714c6eecfbdf6 Mon Sep 17 00:00:00 2001 From: Mihai Emil Andronache Date: Sun, 19 Mar 2017 11:29:39 +0200 Subject: [PATCH 030/149] Automata indentation and link (#1425) * finite automata example * indentation and link --- algorithms/README.md | 1 + .../baeldung/automata/FiniteStateMachine.java | 12 +- .../automata/RtFiniteStateMachine.java | 34 ++--- .../java/com/baeldung/automata/RtState.java | 10 +- .../com/baeldung/automata/RtTransition.java | 40 +++--- .../java/com/baeldung/automata/State.java | 12 +- .../com/baeldung/automata/Transition.java | 20 +-- .../algorithms/RtFiniteStateMachineTest.java | 122 +++++++++--------- 8 files changed, 126 insertions(+), 125 deletions(-) diff --git a/algorithms/README.md b/algorithms/README.md index 77ef0209c0..a0e10246e0 100644 --- a/algorithms/README.md +++ b/algorithms/README.md @@ -3,3 +3,4 @@ - [Dijkstra Algorithm in Java](http://www.baeldung.com/java-dijkstra) - [Introduction to Cobertura](http://www.baeldung.com/cobertura) - [Ant Colony Optimization](http://www.baeldung.com/java-ant-colony-optimization) +- [Validating Input With Finite Automata in Java](http://www.baeldung.com/finite-automata-java) diff --git a/algorithms/src/main/java/com/baeldung/automata/FiniteStateMachine.java b/algorithms/src/main/java/com/baeldung/automata/FiniteStateMachine.java index cd287ce3d5..943b44fe05 100644 --- a/algorithms/src/main/java/com/baeldung/automata/FiniteStateMachine.java +++ b/algorithms/src/main/java/com/baeldung/automata/FiniteStateMachine.java @@ -5,12 +5,12 @@ package com.baeldung.automata; */ public interface FiniteStateMachine { - /** - * Follow a transition, switch the state of the machine. - * @param c Char. - * @return A new finite state machine with the new state. - */ - FiniteStateMachine switchState(final CharSequence c); + /** + * Follow a transition, switch the state of the machine. + * @param c Char. + * @return A new finite state machine with the new state. + */ + FiniteStateMachine switchState(final CharSequence c); /** * Is the current state a final one? diff --git a/algorithms/src/main/java/com/baeldung/automata/RtFiniteStateMachine.java b/algorithms/src/main/java/com/baeldung/automata/RtFiniteStateMachine.java index 7d2293bb33..090e00c73c 100644 --- a/algorithms/src/main/java/com/baeldung/automata/RtFiniteStateMachine.java +++ b/algorithms/src/main/java/com/baeldung/automata/RtFiniteStateMachine.java @@ -6,25 +6,25 @@ package com.baeldung.automata; */ public final class RtFiniteStateMachine implements FiniteStateMachine { - /** - * Current state. - */ - private State current; + /** + * Current state. + */ + private State current; - /** - * Ctor. - * @param initial Initial state of this machine. - */ - public RtFiniteStateMachine(final State initial) { - this.current = initial; - } + /** + * Ctor. + * @param initial Initial state of this machine. + */ + public RtFiniteStateMachine(final State initial) { + this.current = initial; + } - public FiniteStateMachine switchState(final CharSequence c) { - return new RtFiniteStateMachine(this.current.transit(c)); - } + public FiniteStateMachine switchState(final CharSequence c) { + return new RtFiniteStateMachine(this.current.transit(c)); + } - public boolean canStop() { - return this.current.isFinal(); - } + public boolean canStop() { + return this.current.isFinal(); + } } diff --git a/algorithms/src/main/java/com/baeldung/automata/RtState.java b/algorithms/src/main/java/com/baeldung/automata/RtState.java index 7057335f80..ba785eeff0 100644 --- a/algorithms/src/main/java/com/baeldung/automata/RtState.java +++ b/algorithms/src/main/java/com/baeldung/automata/RtState.java @@ -33,10 +33,10 @@ public final class RtState implements State { return this.isFinal; } - @Override - public State with(Transition tr) { - this.transitions.add(tr); - return this; - } + @Override + public State with(Transition tr) { + this.transitions.add(tr); + return this; + } } diff --git a/algorithms/src/main/java/com/baeldung/automata/RtTransition.java b/algorithms/src/main/java/com/baeldung/automata/RtTransition.java index f895f02e1a..560011e42a 100644 --- a/algorithms/src/main/java/com/baeldung/automata/RtTransition.java +++ b/algorithms/src/main/java/com/baeldung/automata/RtTransition.java @@ -6,26 +6,26 @@ package com.baeldung.automata; */ public final class RtTransition implements Transition { - private String rule; - private State next; - - /** - * Ctor. - * @param rule Rule that a character has to meet - * in order to get to the next state. - * @param next Next state. - */ - public RtTransition (String rule, State next) { - this.rule = rule; - this.next = next; - } - - public State state() { - return this.next; - } + private String rule; + private State next; + + /** + * Ctor. + * @param rule Rule that a character has to meet + * in order to get to the next state. + * @param next Next state. + */ + public RtTransition (String rule, State next) { + this.rule = rule; + this.next = next; + } + + public State state() { + return this.next; + } - public boolean isPossible(CharSequence c) { - return this.rule.equalsIgnoreCase(String.valueOf(c)); - } + public boolean isPossible(CharSequence c) { + return this.rule.equalsIgnoreCase(String.valueOf(c)); + } } diff --git a/algorithms/src/main/java/com/baeldung/automata/State.java b/algorithms/src/main/java/com/baeldung/automata/State.java index 25dff900d2..a25af9d03a 100644 --- a/algorithms/src/main/java/com/baeldung/automata/State.java +++ b/algorithms/src/main/java/com/baeldung/automata/State.java @@ -5,12 +5,12 @@ package com.baeldung.automata; */ public interface State { - /** - * Add a Transition to this state. - * @param tr Given transition. - * @return Modified State. - */ - State with(final Transition tr); + /** + * Add a Transition to this state. + * @param tr Given transition. + * @return Modified State. + */ + State with(final Transition tr); /** * Follow one of the transitions, to get diff --git a/algorithms/src/main/java/com/baeldung/automata/Transition.java b/algorithms/src/main/java/com/baeldung/automata/Transition.java index 9b34617aa1..d57620f911 100644 --- a/algorithms/src/main/java/com/baeldung/automata/Transition.java +++ b/algorithms/src/main/java/com/baeldung/automata/Transition.java @@ -5,16 +5,16 @@ package com.baeldung.automata; */ public interface Transition { - /** - * Is the transition possible with the given character? - * @param c char. - * @return true or false. - */ - boolean isPossible(final CharSequence c); + /** + * Is the transition possible with the given character? + * @param c char. + * @return true or false. + */ + boolean isPossible(final CharSequence c); - /** - * The state to which this transition leads. - * @return State. - */ + /** + * The state to which this transition leads. + * @return State. + */ State state(); } diff --git a/algorithms/src/test/java/algorithms/RtFiniteStateMachineTest.java b/algorithms/src/test/java/algorithms/RtFiniteStateMachineTest.java index bd867cbef9..089b38ec3f 100644 --- a/algorithms/src/test/java/algorithms/RtFiniteStateMachineTest.java +++ b/algorithms/src/test/java/algorithms/RtFiniteStateMachineTest.java @@ -9,74 +9,74 @@ import com.baeldung.automata.*; */ public final class RtFiniteStateMachineTest { - @Test + @Test public void acceptsSimplePair() { - String json = "{\"key\":\"value\"}"; - FiniteStateMachine machine = this.buildJsonStateMachine(); - for (int i=0;i Date: Sun, 19 Mar 2017 08:44:00 -0300 Subject: [PATCH 031/149] BAEL-388 (#1360) * initial push * module Java Web Start (jws) add * pom improvements and fixes --- jws/.gitignore | 7 ++ jws/java-core-samples-lib/jardiff.jar | Bin 0 -> 11346 bytes jws/java-core-samples-lib/jnlp-servlet.jar | Bin 0 -> 54952 bytes jws/pom.xml | 86 +++++++++++++++++++++ jws/src/main/java/com/example/Hello.java | 15 ++++ jws/src/main/webapp/WEB-INF/web.xml | 24 ++++++ jws/src/main/webapp/hello.jnlp | 12 +++ jws/src/main/webapp/index.html | 10 +++ pom.xml | 1 + 9 files changed, 155 insertions(+) create mode 100644 jws/.gitignore create mode 100644 jws/java-core-samples-lib/jardiff.jar create mode 100644 jws/java-core-samples-lib/jnlp-servlet.jar create mode 100644 jws/pom.xml create mode 100644 jws/src/main/java/com/example/Hello.java create mode 100644 jws/src/main/webapp/WEB-INF/web.xml create mode 100644 jws/src/main/webapp/hello.jnlp create mode 100644 jws/src/main/webapp/index.html diff --git a/jws/.gitignore b/jws/.gitignore new file mode 100644 index 0000000000..6346853947 --- /dev/null +++ b/jws/.gitignore @@ -0,0 +1,7 @@ +/target/ +.classpath +.project +.settings +/.settings/ +/settings/ +.tern-project diff --git a/jws/java-core-samples-lib/jardiff.jar b/jws/java-core-samples-lib/jardiff.jar new file mode 100644 index 0000000000000000000000000000000000000000..08b5bb61ab00b5cfe9cd9154f21f42b3a74c10e1 GIT binary patch literal 11346 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 O5A%}>{7c&x>VE(j5h9HM literal 0 HcmV?d00001 diff --git a/jws/java-core-samples-lib/jnlp-servlet.jar b/jws/java-core-samples-lib/jnlp-servlet.jar new file mode 100644 index 0000000000000000000000000000000000000000..c27167c64700a1f2710a1048858695b6454a1037 GIT binary patch literal 54952 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 knbVgl=bxPR{!gr5aS8@7hXMkk2mDPU00H&%|Mu + + 4.0.0 + com.example + jws + war + 0.0.1-SNAPSHOT + + jws-example + + + 3.0.2 + 3.0.0 + 7.0 + 7.0 + + + + + javax.jnlp + jnlp-servlet + ${jnlp-servlet.version} + system + ${project.basedir}/java-core-samples-lib/jnlp-servlet.jar + + + javax.jnlp + jardiff + ${jardiff.version} + system + ${project.basedir}/java-core-samples-lib/jardiff.jar + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + compile + + jar + + + + + com.example.Hello + + + ${project.basedir}/target/jws + + + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + package + + + + + + ${project.basedir}/java-core-samples-lib/ + + **/*.jar + + WEB-INF/lib + + + + + + ${project.artifactId} + + + diff --git a/jws/src/main/java/com/example/Hello.java b/jws/src/main/java/com/example/Hello.java new file mode 100644 index 0000000000..3479277ace --- /dev/null +++ b/jws/src/main/java/com/example/Hello.java @@ -0,0 +1,15 @@ +package com.example; + +import javax.swing.*; + +public class Hello { + public static void main(String[] args) { + JFrame f = new JFrame("main"); + f.setSize(200, 100); + f.setLocationRelativeTo(null); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + JLabel label = new JLabel("Hello, world!"); + f.add(label); + f.setVisible(true); + } +} diff --git a/jws/src/main/webapp/WEB-INF/web.xml b/jws/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..18f29ddd12 --- /dev/null +++ b/jws/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + Java Web Start + JNLP Example for Java Web Start Article + + + JnlpDownloadServlet + jnlp.sample.servlet.JnlpDownloadServlet + + + JnlpDownloadServlet + *.jar + + + JnlpDownloadServlet + *.jnlp + + + + index.html + + diff --git a/jws/src/main/webapp/hello.jnlp b/jws/src/main/webapp/hello.jnlp new file mode 100644 index 0000000000..950c0d716f --- /dev/null +++ b/jws/src/main/webapp/hello.jnlp @@ -0,0 +1,12 @@ + + + + Hello + Example + + + + + + + diff --git a/jws/src/main/webapp/index.html b/jws/src/main/webapp/index.html new file mode 100644 index 0000000000..212607b850 --- /dev/null +++ b/jws/src/main/webapp/index.html @@ -0,0 +1,10 @@ + + +Hello World JNLP + + +

+ Launch the example +

+ + diff --git a/pom.xml b/pom.xml index c12562551f..4e55d4686d 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ json jsoup junit5 + jws kotlin From 04d8fed1c5bfe9f1f42893cf8dddfc3e601fbd3d Mon Sep 17 00:00:00 2001 From: lor6 Date: Sun, 19 Mar 2017 17:25:11 +0200 Subject: [PATCH 032/149] validation with mvc and angular (#1378) * validation with mvc and angular * fix dependency versions --- spring-mvc-forms/pom.xml | 11 ++- .../ApplicationConfiguration.java | 10 +++ .../controller/UserController.java | 63 +++++++++++++++ .../baeldung/springmvcforms/domain/User.java | 71 +++++++++++++++++ .../src/main/webapp/WEB-INF/html/user.html | 68 ++++++++++++++++ spring-mvc-forms/src/main/webapp/css/user.css | 77 +++++++++++++++++++ spring-mvc-forms/src/main/webapp/js/app.js | 70 +++++++++++++++++ 7 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java create mode 100644 spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/domain/User.java create mode 100644 spring-mvc-forms/src/main/webapp/WEB-INF/html/user.html create mode 100644 spring-mvc-forms/src/main/webapp/css/user.css create mode 100644 spring-mvc-forms/src/main/webapp/js/app.js diff --git a/spring-mvc-forms/pom.xml b/spring-mvc-forms/pom.xml index 35ed00c0e9..e5ffb52801 100644 --- a/spring-mvc-forms/pom.xml +++ b/spring-mvc-forms/pom.xml @@ -46,6 +46,12 @@ commons-fileupload ${fileupload.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + @@ -89,16 +95,17 @@ - 4.3.4.RELEASE + 4.3.7.RELEASE 2.6 1.2 2.3.1 3.1.0 3.6.0 1.8 - 5.3.3.Final + 5.4.0.Final enter-location-of-server 1.3.2 + 2.8.7 diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/ApplicationConfiguration.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/ApplicationConfiguration.java index 3f7889422f..7292d95b21 100644 --- a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/ApplicationConfiguration.java +++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/ApplicationConfiguration.java @@ -27,6 +27,16 @@ class ApplicationConfiguration extends WebMvcConfigurerAdapter { bean.setSuffix(".jsp"); return bean; } + + + @Bean + public InternalResourceViewResolver htmlViewResolver() { + InternalResourceViewResolver bean = new InternalResourceViewResolver(); + bean.setPrefix("/WEB-INF/html/"); + bean.setSuffix(".html"); + bean.setOrder(2); + return bean; + } @Bean public MultipartResolver multipartResolver() { diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java new file mode 100644 index 0000000000..880b224dd9 --- /dev/null +++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java @@ -0,0 +1,63 @@ +package com.baeldung.springmvcforms.controller; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.baeldung.springmvcforms.domain.User; + +@Controller +public class UserController { + + List users = new ArrayList() { + { + add(new User("ana@yahoo.com", "pass", "Ana", 20)); + add(new User("bob@yahoo.com", "pass", "Bob", 30)); + add(new User("john@yahoo.com", "pass", "John", 40)); + add(new User("mary@yahoo.com", "pass", "Mary", 30)); + } + }; + + @GetMapping("/userPage") + public String getUserProfilePage() { + return "user"; + } + + @PostMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity saveUser(@Valid User user, BindingResult result, Model model) { + if (result.hasErrors()) { + List errors = new ArrayList<>(); + result.getAllErrors().forEach(item->{ + errors.add(item.getDefaultMessage()); + }); + return new ResponseEntity<>(errors, HttpStatus.OK); + } else { + if (users.stream().anyMatch(it -> user.getEmail().equals(it.getEmail()))) { + return new ResponseEntity<>(Collections.singletonList("Email already exists!"), HttpStatus.CONFLICT); + } else { + users.add(user); + return new ResponseEntity<>(HttpStatus.CREATED); + } + } + } + + @GetMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public List getUsers() { + return users; + } + +} diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/domain/User.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/domain/User.java new file mode 100644 index 0000000000..49f006f422 --- /dev/null +++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/domain/User.java @@ -0,0 +1,71 @@ +package com.baeldung.springmvcforms.domain; + +import javax.validation.constraints.Digits; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.NotBlank; + +public class User { + + @NotNull + @Email + private String email; + + @NotNull + @Size(min = 4, max = 15) + private String password; + + @NotBlank + private String name; + + @Min(18) + @Digits(integer = 2, fraction = 0) + private int age; + + public User() { + + } + + public User(String email, String password, String name, int age) { + super(); + this.email = email; + this.password = password; + this.name = name; + this.age = age; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/html/user.html b/spring-mvc-forms/src/main/webapp/WEB-INF/html/user.html new file mode 100644 index 0000000000..a505f51f32 --- /dev/null +++ b/spring-mvc-forms/src/main/webapp/WEB-INF/html/user.html @@ -0,0 +1,68 @@ + + + + +Users + + + + + + + + +
+ + + +
+

Invalid email!

+

Email is required!

+
+
+ + + +
+

Password must be at least 4 characters!

+

Password must not be longer than 15 characters!

+

Password is required!

+
+
+ + + +
+

Name is required!

+
+
+ + + +
+

Age must be greater than 18!

+

Age must be greater than 18!

+
+
+
+ + +
+ +
+
{{errorMessage}}
+
{{message}}
+
+ +All Users:
+ + + + + + + +
EmailNameAge
{{usr.email}} {{usr.name}} {{usr.age}}
+ + \ No newline at end of file diff --git a/spring-mvc-forms/src/main/webapp/css/user.css b/spring-mvc-forms/src/main/webapp/css/user.css new file mode 100644 index 0000000000..bcfb62a33c --- /dev/null +++ b/spring-mvc-forms/src/main/webapp/css/user.css @@ -0,0 +1,77 @@ +.form-label { + display:block; + margin-top:16px; + font-weight:bold; +} + +.form-input { + border-radius:5px; + display:inline; +} + +.form-input p { + margin:0; +} + +.form-button { + margin:5px; +} + +.form-error input.ng-invalid { + border-color:red; +} + +.check { + display:inline; + color:green; + font-weight:bold; +} + +.error-messages { + color:red; +} + +.error-messages p { + margin:0; +} + +#customers { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; +} + +#users td, #users th { + border: 1px solid dodgerblue; + border-collapse:collapse; + padding: 4px; + width:200px; +} + +#users tr:nth-child(even){background-color: lavender;} + +#users th { + padding-top: 10px; + padding-bottom: 10px; + text-align: left; + background-color: dodgerblue; +} + +#users { + border-collapse:collapse; +} + +button { + border-radius:5px; + cursor:pointer; + margin:10px; + padding:5px; +} + +.form-button-save { + background-color:lightgreen; +} + +.form-button-reset { + background-color:lightpink; +} \ No newline at end of file diff --git a/spring-mvc-forms/src/main/webapp/js/app.js b/spring-mvc-forms/src/main/webapp/js/app.js new file mode 100644 index 0000000000..6a290b3cf0 --- /dev/null +++ b/spring-mvc-forms/src/main/webapp/js/app.js @@ -0,0 +1,70 @@ +var app = angular.module('app', ['ngMessages']); + +app.controller('UserCtrl', ['$scope','UserService', function ($scope,UserService) { + + $scope.submitted = false; + + $scope.getUsers = function() { + UserService.getUsers().then( function(data){ + $scope.users = data; + }); + } + + $scope.saveUser = function () { + $scope.submitted = true; + if ($scope.userForm.$valid){ + UserService.saveUser($scope.user) + .then (function success(response){ + $scope.message = 'User added!'; + $scope.errorMessage = ''; + $scope.getUsers(); + $scope.user = null; + $scope.submitted = false; + }, + function error(response){ + if (response.status == 409){ + $scope.errorMessage = response.data[0]; + } + else { + $scope.errorMessage = 'Error adding user!'; + } + $scope.message = ''; + }); + } + } + + $scope.getUsers(); + + $scope.resetForm = function () { + $scope.userForm.$setPristine(); + $scope.user=null; + $scope.message=''; + $scope.errorMessage=''; + $scope.submitted = false; + } + +}]); + +app.service('UserService',['$http', function ($http) { + + this.saveUser = function saveUser(user){ + return $http({ + method: 'POST', + url: 'user', + params: {email:user.email, password:user.password, name:user.name, age:user.age}, + headers: 'Accept:application/json' + }); + } + + + this.getUsers = function getUsers(){ + return $http({ + method: 'GET', + url: 'users', + headers:'Accept:application/json' + }).then( function(response){ + return response.data; + } ); + } + +}]); \ No newline at end of file From 32147b4459f9b21db59660e04a0885ff86c5f051 Mon Sep 17 00:00:00 2001 From: dhruba619 Date: Sun, 19 Mar 2017 22:28:34 +0530 Subject: [PATCH 033/149] BAEL-702 Intro to Vert.x Initial commit --- pom.xml | 7 +- vertx/pom.xml | 111 ++++++++++++++++++ vertx/src/main/conf/conf.json | 3 + .../main/java/com/baeldung/HelloVerticle.java | 28 +++++ .../com/baeldung/SimpleServerVerticle.java | 26 ++++ .../main/java/com/baeldung/model/Article.java | 59 ++++++++++ .../baledung/rest/RestServiceVerticle.java | 41 +++++++ vertx/src/resources/logback.xml | 14 +++ .../com/baeldung/RestServiceVerticleTest.java | 46 ++++++++ .../baeldung/SimpleServerVerticleTest.java | 47 ++++++++ 10 files changed, 378 insertions(+), 4 deletions(-) create mode 100644 vertx/pom.xml create mode 100644 vertx/src/main/conf/conf.json create mode 100644 vertx/src/main/java/com/baeldung/HelloVerticle.java create mode 100644 vertx/src/main/java/com/baeldung/SimpleServerVerticle.java create mode 100644 vertx/src/main/java/com/baeldung/model/Article.java create mode 100644 vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java create mode 100644 vertx/src/resources/logback.xml create mode 100644 vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java create mode 100644 vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java diff --git a/pom.xml b/pom.xml index 9c24200a0b..312e2aef9f 100644 --- a/pom.xml +++ b/pom.xml @@ -203,9 +203,8 @@ apache-solrj rabbitmq - - - + vertx + @@ -216,4 +215,4 @@ --> - + \ No newline at end of file diff --git a/vertx/pom.xml b/vertx/pom.xml new file mode 100644 index 0000000000..971a61d336 --- /dev/null +++ b/vertx/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + com.baeldung + vertx + 1.0-SNAPSHOT + vertx + http://maven.apache.org + + + junit + junit + 4.12 + test + + + + io.vertx + vertx-core + 3.0.0 + + + + io.vertx + vertx-web + 3.0.0 + + + + io.vertx + vertx-unit + 3.0.0 + test + + + + + + org.slf4j + slf4j-api + ${org.slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + + + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + + + io.vertx.core.Starter + com.baeldung.SimpleServerVerticle + + + + + ${project.build.directory}/${project.artifactId}-${project.version}-app.jar + + + + + + + + + + + 1.7.21 + 1.1.7 + + + 6.10 + + + 3.6.0 + 2.19.1 + + + + diff --git a/vertx/src/main/conf/conf.json b/vertx/src/main/conf/conf.json new file mode 100644 index 0000000000..4fa43ee648 --- /dev/null +++ b/vertx/src/main/conf/conf.json @@ -0,0 +1,3 @@ +{ + "http.port":8080 +} \ No newline at end of file diff --git a/vertx/src/main/java/com/baeldung/HelloVerticle.java b/vertx/src/main/java/com/baeldung/HelloVerticle.java new file mode 100644 index 0000000000..98d1b336a3 --- /dev/null +++ b/vertx/src/main/java/com/baeldung/HelloVerticle.java @@ -0,0 +1,28 @@ +package com.baeldung; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; +import io.vertx.core.Vertx; + +public class HelloVerticle extends AbstractVerticle { + private static final Logger LOGGER = LoggerFactory.getLogger(HelloVerticle.class); + + @Override + public void start(Future future) { + LOGGER.info("Welcome to Vertx"); + } + + @Override + public void stop() { + LOGGER.info("Shutting down application"); + } + + public static void main(String[] args) { + Vertx vertx = Vertx.vertx(); + vertx.deployVerticle(new HelloVerticle()); + } +} + diff --git a/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java new file mode 100644 index 0000000000..2cee37903b --- /dev/null +++ b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java @@ -0,0 +1,26 @@ +package com.baeldung; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; + +public class SimpleServerVerticle extends AbstractVerticle { + + @Override + public void start(Future future) { + vertx.createHttpServer() + .requestHandler(request -> { + request.response() + .end("Welcome to Vert.x Intro"); + }) + .listen(config().getInteger("http.port", 8080), result -> { + if (result.succeeded()) { + future.complete(); + } else { + future.fail(result.cause()); + } + }); + } + +} + + diff --git a/vertx/src/main/java/com/baeldung/model/Article.java b/vertx/src/main/java/com/baeldung/model/Article.java new file mode 100644 index 0000000000..9f1fdb8203 --- /dev/null +++ b/vertx/src/main/java/com/baeldung/model/Article.java @@ -0,0 +1,59 @@ +package com.baeldung.model; + +public class Article { + private String id; + private String content; + private String author; + private String datePublished; + private int wordCount; + + public Article(String id, String content, String author, String datePublished, int wordCount) { + super(); + this.id = id; + this.content = content; + this.author = author; + this.datePublished = datePublished; + this.wordCount = wordCount; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getDatePublished() { + return datePublished; + } + + public void setDatePublished(String datePublished) { + this.datePublished = datePublished; + } + + public int getWordCount() { + return wordCount; + } + + public void setWordCount(int wordCount) { + this.wordCount = wordCount; + } + +} diff --git a/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java b/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java new file mode 100644 index 0000000000..181b3007d5 --- /dev/null +++ b/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java @@ -0,0 +1,41 @@ +package com.baledung.rest; + +import com.baeldung.model.Article; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; +import io.vertx.core.json.Json; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +public class RestServiceVerticle extends AbstractVerticle { + @Override + public void start(Future future) { + + Router router = Router.router(vertx); + router.get("/api/baeldung/articles/article/:id") + .handler(this::getArticles); + + vertx.createHttpServer() + .requestHandler(router::accept) + .listen(config().getInteger("http.port", 8080), result -> { + if (result.succeeded()) { + future.complete(); + } else { + future.fail(result.cause()); + } + }); + } + + private void getArticles(RoutingContext routingContext) { + String articleId = routingContext.request() + .getParam("id"); + Article article = new Article(articleId, "This is an intro to vertx", "baeldung", "01-02-2017", 1578); + + routingContext.response() + .putHeader("content-type", "application/json") + .setStatusCode(200) + .end(Json.encodePrettily(article)); + } + +} diff --git a/vertx/src/resources/logback.xml b/vertx/src/resources/logback.xml new file mode 100644 index 0000000000..e9ae1894a6 --- /dev/null +++ b/vertx/src/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + web - %date [%thread] %-5level %logger{36} - %message%n + + + + + + + + + \ No newline at end of file diff --git a/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java b/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java new file mode 100644 index 0000000000..8eebe1396d --- /dev/null +++ b/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java @@ -0,0 +1,46 @@ +package com.baeldung; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.baledung.rest.RestServiceVerticle; + +import io.vertx.core.Vertx; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; + +@RunWith(VertxUnitRunner.class) +public class RestServiceVerticleTest { + + private Vertx vertx; + + @Before + public void setup(TestContext testContext) { + vertx = Vertx.vertx(); + + vertx.deployVerticle(RestServiceVerticle.class.getName(), testContext.asyncAssertSuccess()); + } + + @After + public void tearDown(TestContext testContext) { + vertx.close(testContext.asyncAssertSuccess()); + } + + @Test + public void givenId_whenReceivedArticle_thenSuccess(TestContext testContext) { + final Async async = testContext.async(); + + vertx.createHttpClient() + .getNow(8080, "localhost", "/api/baeldung/articles/article/12345", response -> { + response.handler(responseBody -> { + testContext.assertTrue(responseBody.toString() + .contains("\"id\" : \"12345\"")); + async.complete(); + }); + }); + } + +} diff --git a/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java b/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java new file mode 100644 index 0000000000..177f8d8435 --- /dev/null +++ b/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java @@ -0,0 +1,47 @@ +package com.baeldung; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; + +@RunWith(VertxUnitRunner.class) +public class SimpleServerVerticleTest { + private Vertx vertx; + + @Before + public void setup(TestContext testContext) { + vertx = Vertx.vertx(); + + vertx.deployVerticle(SimpleServerVerticle.class.getName(), + testContext.asyncAssertSuccess()); + } + + @After + public void tearDown(TestContext testContext) { + vertx.close(testContext.asyncAssertSuccess()); + } + + @Test + public void whenReceivedResponse_thenSuccess(TestContext testContext) { + final Async async = testContext.async(); + + vertx.createHttpClient() + .getNow(8080, "localhost", "/", response -> { + response.handler(responseBody -> { + testContext.assertTrue(responseBody.toString() + .contains("Welcome")); + async.complete(); + }); + }); + } + +} + From 32c21f27221951c78f89782c6579b0814ee3599a Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Sun, 19 Mar 2017 13:55:38 -0500 Subject: [PATCH 034/149] BAEL-731: Updated README (#1452) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md --- spring-boot/README.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot/README.MD b/spring-boot/README.MD index d70e83525b..8aa5957bad 100644 --- a/spring-boot/README.MD +++ b/spring-boot/README.MD @@ -14,4 +14,5 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [How to Register a Servlet in a Java Web Application](http://www.baeldung.com/register-servlet) - [Guide to Spring WebUtils and ServletRequestUtils](http://www.baeldung.com/spring-webutils-servletrequestutils) - [Using Custom Banners in Spring Boot](http://www.baeldung.com/spring-boot-custom-banners) +- [Guide to Internationalization in Spring Boot](http://www.baeldung.com/spring-boot-internationalization) From debde5ad67c020cdcf000a5ebb8b1ca126749e6c Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 20:02:04 +0100 Subject: [PATCH 035/149] Update README.md (#1439) --- spring-security-mvc-login/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-security-mvc-login/README.md b/spring-security-mvc-login/README.md index 35305112b4..f7eb314869 100644 --- a/spring-security-mvc-login/README.md +++ b/spring-security-mvc-login/README.md @@ -11,6 +11,7 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com - [Spring Security Expressions – hasRole Example](http://www.baeldung.com/spring-security-expressions-basic) - [Spring HTTP/HTTPS Channel Security](http://www.baeldung.com/spring-channel-security-https) - [Spring Security - Customize the 403 Forbidden/Access Denied Page](http://www.baeldung.com/spring-security-custom-access-denied-page) +- [Spring Security – Redirect to the Previous URL After Login](http://www.baeldung.com/spring-security-redirect-login) ### Build the Project ``` From 7d670d2be6b1369561b02358cac3f64b5a181595 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 20:02:15 +0100 Subject: [PATCH 036/149] Update README.md (#1442) --- spring-ldap/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-ldap/README.md b/spring-ldap/README.md index 8dffadb685..f77572d982 100644 --- a/spring-ldap/README.md +++ b/spring-ldap/README.md @@ -1,8 +1,9 @@ +## Spring LDAP Example Project + ### Relevant articles - [Spring LDAP Overview](http://www.baeldung.com/spring-ldap) +- [Spring LDAP Example Project](http://www.baeldung.com/spring-ldap-overview/) -## Spring LDAP Example Project -- (http://www.baeldung.com/spring-ldap-overview/) From e693c47019d6d39c15e4596a8816984abcfe08d4 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 20:02:29 +0100 Subject: [PATCH 037/149] Delete README.md (#1440) --- spring-5/src/test/java/com/baeldung/README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 spring-5/src/test/java/com/baeldung/README.md diff --git a/spring-5/src/test/java/com/baeldung/README.md b/spring-5/src/test/java/com/baeldung/README.md deleted file mode 100644 index f7b358ec3e..0000000000 --- a/spring-5/src/test/java/com/baeldung/README.md +++ /dev/null @@ -1,3 +0,0 @@ -### Relevant articles - -- [Concurrent Test Execution in Spring 5](http://www.baeldung.com/spring-5-concurrent-tests) From 365d9b70230c644c5012ed367a39ad06ad3262b0 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 20:02:50 +0100 Subject: [PATCH 038/149] Update README.md (#1441) --- spring-5/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spring-5/README.md b/spring-5/README.md index 0914388b49..47a5314009 100644 --- a/spring-5/README.md +++ b/spring-5/README.md @@ -1,6 +1,8 @@ ## Spring REST Example Project -###The Course +### The Course The "REST With Spring" Classes: http://bit.ly/restwithspring -### Relevant Articles: +### Relevant Articles + +- [Concurrent Test Execution in Spring 5](http://www.baeldung.com/spring-5-concurrent-tests) From e71367b3f3f972558097d56020ec46af26ea78e4 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 20:03:23 +0100 Subject: [PATCH 039/149] Assembly plugin fix (#1449) * Assembly plugin fix * Fix java opts * Fix java opts 2 * Fix java opts 3 * Add sudo: required * Remove sudo: required * Enable incremental builder * Disable incremental builder * Update * Update --- .travis.yml | 13 +++++-------- apache-poi/temp.xlsx | Bin 3510 -> 3492 bytes core-java/pom.xml | 1 + disruptor/pom.xml | 1 + jpa-storedprocedure/pom.xml | 1 + pom.xml | 14 +++++++++++++- xml/pom.xml | 1 + 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6063fbf3e5..120d365569 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: java -install: travis_wait 40 mvn -q clean install -Dgib.enabled=true +install: travis_wait 60 mvn -q clean install + +before_script: + - echo "MAVEN_OPTS='-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:-UseGCOverheadLimit'" > ~/.mavenrc jdk: - oraclejdk8 @@ -14,10 +17,4 @@ cache: directories: - .autoconf - $HOME/.m2 - - sudo: required - - env: - global: - JAVA_OPTS="-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" - MAVEN_OPTS="-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" + diff --git a/apache-poi/temp.xlsx b/apache-poi/temp.xlsx index 50307a28c2ae5c64afdcaf6baae59a0648c28c6b..cbea3a410d193a848a5321af8e311bcf61f7160f 100644 GIT binary patch literal 3492 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 + ${project.basedir} org.baeldung.executable.ExecutableMavenJar diff --git a/disruptor/pom.xml b/disruptor/pom.xml index 7f2c78c9b0..2523cc2125 100644 --- a/disruptor/pom.xml +++ b/disruptor/pom.xml @@ -145,6 +145,7 @@ single + ${project.basedir} org.baeldung.executable.ExecutableMavenJar diff --git a/jpa-storedprocedure/pom.xml b/jpa-storedprocedure/pom.xml index 797303dc29..1672afd217 100644 --- a/jpa-storedprocedure/pom.xml +++ b/jpa-storedprocedure/pom.xml @@ -30,6 +30,7 @@ maven-assembly-plugin + ${project.basedir} jar-with-dependencies diff --git a/pom.xml b/pom.xml index 4e55d4686d..d68a7d0749 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ UTF-8 refs/heads/master - false + @@ -211,6 +211,18 @@ + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + maven + + + + + maven-assembly-plugin + ${project.basedir} jar-with-dependencies From 6bd430c4644e93e7cc400e22b8cfeae712cead12 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 21:37:44 +0100 Subject: [PATCH 040/149] Update .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 120d365569..b5bd4684a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: java install: travis_wait 60 mvn -q clean install +sudo: required + before_script: - echo "MAVEN_OPTS='-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:-UseGCOverheadLimit'" > ~/.mavenrc From 6c3f4d868837fed7e5f880593fdebd8152821487 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 21:37:59 +0100 Subject: [PATCH 041/149] Update .travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5bd4684a3..120d365569 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: java install: travis_wait 60 mvn -q clean install -sudo: required - before_script: - echo "MAVEN_OPTS='-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:-UseGCOverheadLimit'" > ~/.mavenrc From 0b78cc9e4c0b5c32f6d7469f45cd96bd91275fdd Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sun, 19 Mar 2017 21:29:10 +0000 Subject: [PATCH 042/149] Custom aop (#1451) --- spring-custom-aop/pom.xml | 53 +++++++++++++++++++ .../main/java/org/baeldung/Application.java | 13 +++++ .../main/java/org/baeldung/ExampleAspect.java | 25 +++++++++ .../java/org/baeldung/LogExecutionTime.java | 11 ++++ .../src/main/java/org/baeldung/Service.java | 12 +++++ .../org/baeldung/CustomAnnotationTest.java | 21 ++++++++ 6 files changed, 135 insertions(+) create mode 100644 spring-custom-aop/pom.xml create mode 100644 spring-custom-aop/src/main/java/org/baeldung/Application.java create mode 100644 spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java create mode 100644 spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java create mode 100644 spring-custom-aop/src/main/java/org/baeldung/Service.java create mode 100644 spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java diff --git a/spring-custom-aop/pom.xml b/spring-custom-aop/pom.xml new file mode 100644 index 0000000000..1c3b5bdccd --- /dev/null +++ b/spring-custom-aop/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + com.baeldung + spring-custom-aop + 0.0.1-SNAPSHOT + war + spring-custom-aop + + + org.springframework.boot + spring-boot-starter-parent + 1.5.2.RELEASE + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + diff --git a/spring-custom-aop/src/main/java/org/baeldung/Application.java b/spring-custom-aop/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..e5c764ef7e --- /dev/null +++ b/spring-custom-aop/src/main/java/org/baeldung/Application.java @@ -0,0 +1,13 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java b/spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java new file mode 100644 index 0000000000..7c3b5fb599 --- /dev/null +++ b/spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java @@ -0,0 +1,25 @@ +package org.baeldung; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class ExampleAspect { + + @Around("@annotation(LogExecutionTime)") + public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + final long start = System.currentTimeMillis(); + + final Object proceed = joinPoint.proceed(); + + final long executionTime = System.currentTimeMillis() - start; + + System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms"); + + return proceed; + } + +} diff --git a/spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java b/spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java new file mode 100644 index 0000000000..c10f97e78f --- /dev/null +++ b/spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java @@ -0,0 +1,11 @@ +package org.baeldung; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface LogExecutionTime { +} diff --git a/spring-custom-aop/src/main/java/org/baeldung/Service.java b/spring-custom-aop/src/main/java/org/baeldung/Service.java new file mode 100644 index 0000000000..e4bee38438 --- /dev/null +++ b/spring-custom-aop/src/main/java/org/baeldung/Service.java @@ -0,0 +1,12 @@ +package org.baeldung; + +import org.springframework.stereotype.Component; + +@Component +public class Service { + + @LogExecutionTime + public void serve() throws InterruptedException { + Thread.sleep(2000); + } +} diff --git a/spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java b/spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java new file mode 100644 index 0000000000..d4712cc063 --- /dev/null +++ b/spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java @@ -0,0 +1,21 @@ +package org.baeldung; + +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.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest +public class CustomAnnotationTest { + + @Autowired + private Service service; + + @Test + public void shouldApplyCustomAnnotation() throws InterruptedException { + service.serve(); + } + +} From 3accbf88156f1aa4850dd29c7dae8a9b42e0f255 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sun, 19 Mar 2017 22:31:11 +0100 Subject: [PATCH 043/149] UserController refactor (#1450) * Refactor UserController * Refactor UserController --- .../controller/UserController.java | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java index 880b224dd9..f5553cf2b7 100644 --- a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java +++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UserController.java @@ -1,48 +1,44 @@ package com.baeldung.springmvcforms.controller; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.validation.Valid; +import com.baeldung.springmvcforms.domain.User; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; -import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; -import com.baeldung.springmvcforms.domain.User; +import javax.validation.Valid; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @Controller public class UserController { - List users = new ArrayList() { - { - add(new User("ana@yahoo.com", "pass", "Ana", 20)); - add(new User("bob@yahoo.com", "pass", "Bob", 30)); - add(new User("john@yahoo.com", "pass", "John", 40)); - add(new User("mary@yahoo.com", "pass", "Mary", 30)); - } - }; + private List users = Arrays.asList( + new User("ana@yahoo.com", "pass", "Ana", 20), + new User("bob@yahoo.com", "pass", "Bob", 30), + new User("john@yahoo.com", "pass", "John", 40), + new User("mary@yahoo.com", "pass", "Mary", 30)); @GetMapping("/userPage") public String getUserProfilePage() { return "user"; } - @PostMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping("/user") @ResponseBody public ResponseEntity saveUser(@Valid User user, BindingResult result, Model model) { if (result.hasErrors()) { - List errors = new ArrayList<>(); - result.getAllErrors().forEach(item->{ - errors.add(item.getDefaultMessage()); - }); + final List errors = result.getAllErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.toList()); + return new ResponseEntity<>(errors, HttpStatus.OK); } else { if (users.stream().anyMatch(it -> user.getEmail().equals(it.getEmail()))) { @@ -54,7 +50,7 @@ public class UserController { } } - @GetMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping("/users") @ResponseBody public List getUsers() { return users; From ed70f6b3380c3a6e597209e3787e14a0c098f8f0 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 20 Mar 2017 09:15:49 +0100 Subject: [PATCH 044/149] Finite Automata refactor (#1445) --- .../src/main/java/com/baeldung/automata/RtState.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/algorithms/src/main/java/com/baeldung/automata/RtState.java b/algorithms/src/main/java/com/baeldung/automata/RtState.java index ba785eeff0..b4a5df7961 100644 --- a/algorithms/src/main/java/com/baeldung/automata/RtState.java +++ b/algorithms/src/main/java/com/baeldung/automata/RtState.java @@ -21,12 +21,12 @@ public final class RtState implements State { } public State transit(final CharSequence c) { - for(final Transition t : this.transitions) { - if(t.isPossible(c)) { - return t.state(); - } - } - throw new IllegalArgumentException("Input not accepted: " + c); + return transitions + .stream() + .filter(t -> t.isPossible(c)) + .map(Transition::state) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Input not accepted: " + c)); } public boolean isFinal() { From 64c2ef150e7cbbc1e9b9001734698790576e1706 Mon Sep 17 00:00:00 2001 From: Devendra Tiwari Date: Mon, 20 Mar 2017 13:46:34 +0530 Subject: [PATCH 045/149] Guava 21 (#1411) Code for article Guava 21 | common.collect package --- guava21/README.md | 7 + guava21/pom.xml | 41 +++++ .../guava/tutorial/ComparatorsExamples.java | 27 +++ .../guava/tutorial/ConcatStreams.java | 11 ++ .../tutorial/InternerBuilderExample.java | 19 +++ .../guava/tutorial/MoreCollectorsExample.java | 17 ++ .../guava/tutorial/StreamsUtility.java | 42 +++++ .../src/test/java/ComparatorsUnitTests.java | 76 +++++++++ guava21/src/test/java/GauavaStreamsTests.java | 155 ++++++++++++++++++ .../src/test/java/InternBuilderUnitTests.java | 19 +++ .../test/java/MoreCollectorsUnitTests.java | 36 ++++ guava21/src/test/java/StreamUtility.java | 66 ++++++++ 12 files changed, 516 insertions(+) create mode 100644 guava21/README.md create mode 100644 guava21/pom.xml create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java create mode 100644 guava21/src/test/java/ComparatorsUnitTests.java create mode 100644 guava21/src/test/java/GauavaStreamsTests.java create mode 100644 guava21/src/test/java/InternBuilderUnitTests.java create mode 100644 guava21/src/test/java/MoreCollectorsUnitTests.java create mode 100644 guava21/src/test/java/StreamUtility.java diff --git a/guava21/README.md b/guava21/README.md new file mode 100644 index 0000000000..8121cf2ea6 --- /dev/null +++ b/guava21/README.md @@ -0,0 +1,7 @@ +========= + +## Guava 21 + +**Important**: Guava 21.0 requires Java 8. If you need Java 6, 7 or Android compatibility, use Guava 20.0 for now. Guava 22.0 and on will introduce a Java 6/Android compatible backport of Guava that includes all of the latest changes that don't require Java 8. + +Article 1 : Introduction to Guava21 common.collect package. \ No newline at end of file diff --git a/guava21/pom.xml b/guava21/pom.xml new file mode 100644 index 0000000000..f393c65aec --- /dev/null +++ b/guava21/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + com.baeldung.guava + tutorial + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + + com.google.guava + guava + 21.0 + + + + + junit + junit + 4.11 + + + + + + \ No newline at end of file diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java b/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java new file mode 100644 index 0000000000..6eb5c7f5ba --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java @@ -0,0 +1,27 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.collect.Comparators; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class ComparatorsExamples { + + public static void main(String[] args){ + + List integers = Arrays.asList(1,2,3,4,4,6,7,8,9,10); + //This will return true + boolean isInAscendingOrder = Comparators.isInOrder(integers, new AscedingOrderComparator()); + + System.out.println(isInAscendingOrder); + + } + + private static class AscedingOrderComparator implements Comparator { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + } +} diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java b/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java new file mode 100644 index 0000000000..ab95b85751 --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java @@ -0,0 +1,11 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.collect.Streams; + +import java.util.stream.Stream; + +public class ConcatStreams { + public static Stream concatStreams(Stream stream1, Stream stream2, Stream stream3){ + return Streams.concat(stream1,stream2,stream3); + } +} diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java new file mode 100644 index 0000000000..6b935ba2a8 --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java @@ -0,0 +1,19 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; + +import static com.google.common.collect.Interners.newBuilder; + +public class InternerBuilderExample { + + public static void main(String[] args){ + Interner interners = Interners.newBuilder() + .concurrencyLevel(2) + .strong() + .build(); + + + } + +} diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java new file mode 100644 index 0000000000..6cf4b6b0ac --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java @@ -0,0 +1,17 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.collect.MoreCollectors; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class MoreCollectorsExample { + + public static void main(String[] args) { + List numbers = Arrays.asList(1); + Optional number = numbers.stream() + .map(e -> e * 2) + .collect(MoreCollectors.toOptional()); + } +} diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java b/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java new file mode 100644 index 0000000000..4ec3b44ef4 --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java @@ -0,0 +1,42 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.collect.Streams; + +import java.util.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class StreamsUtility { + + public static void main(String[] args){ + + List numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,11,12,13,14,15,16,17,18,19,20); + //Using Collection + Stream streamFromCollection = Streams.stream(numbers); + //Using Iterator + Stream streamFromIterator = Streams.stream(numbers.iterator()); + //Using Iterable + Stream streamFromIterable = Streams.stream((Iterable) numbers); + //Using Optional + Stream streamFromOptional = Streams.stream(Optional.of(1)); + //Using OptionalLong to LongStream + LongStream streamFromOptionalLong = Streams.stream(OptionalLong.of(1)); + //Using OptionalInt to IntStream + IntStream streamFromOptionalInt = Streams.stream(OptionalInt.of(1)); + //Using OptionalDouble to DoubleStream + DoubleStream streamFromOptionalDouble = Streams.stream(OptionalDouble.of(1.0)); + + Stream concatenatedStreams = Streams.concat(streamFromCollection,streamFromIterable,streamFromIterator); + + List integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10); + //This will return 10 + Optional lastItem = Streams.findLast(integers.stream()); + + Streams.zip( + Stream.of("candy", "chocolate", "bar"), + Stream.of("$1", "$2","$3"), + (arg1, arg2) -> arg1 + ":" + arg2); + } +} diff --git a/guava21/src/test/java/ComparatorsUnitTests.java b/guava21/src/test/java/ComparatorsUnitTests.java new file mode 100644 index 0000000000..f196c41a1b --- /dev/null +++ b/guava21/src/test/java/ComparatorsUnitTests.java @@ -0,0 +1,76 @@ +import com.google.common.collect.Comparators; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; +import java.util.function.Function; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +public class ComparatorsUnitTests { + + @Test + public void isInOrderTest(){ + + List numbers = Arrays.asList(1,2,3,4,4,6,7,8,9,10); + + boolean isInAscendingOrder = Comparators.isInOrder(numbers, new AscendingOrderComparator()); + + Assert.assertTrue(isInAscendingOrder); + } + + @Test + public void isInStrictOrderTest(){ + + List numbers = Arrays.asList(1,2,3,4,3,6,7,8,9,10); + + boolean isInAscendingOrder = Comparators.isInOrder(numbers, new AscendingOrderComparator()); + + Assert.assertFalse(isInAscendingOrder); + } + + + private class AscendingOrderComparator implements Comparator{ + + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + + @Override + public Comparator reversed() { + return null; + } + + @Override + public Comparator thenComparing(Comparator other) { + return null; + } + + @Override + public Comparator thenComparing(Function keyExtractor, Comparator keyComparator) { + return null; + } + + @Override + public > Comparator thenComparing(Function keyExtractor) { + return null; + } + + @Override + public Comparator thenComparingInt(ToIntFunction keyExtractor) { + return null; + } + + @Override + public Comparator thenComparingLong(ToLongFunction keyExtractor) { + return null; + } + + @Override + public Comparator thenComparingDouble(ToDoubleFunction keyExtractor) { + return null; + } + } +} diff --git a/guava21/src/test/java/GauavaStreamsTests.java b/guava21/src/test/java/GauavaStreamsTests.java new file mode 100644 index 0000000000..09e3e29b47 --- /dev/null +++ b/guava21/src/test/java/GauavaStreamsTests.java @@ -0,0 +1,155 @@ +import com.google.common.collect.Streams; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class GauavaStreamsTests { + + List numbers; + + @Before + public void setUp(){ + numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,11,12,13,14,15,16,17,18,19,20); + } + + + @Test + public void createStreamsWithCollection(){ + //Deprecated API to create stream from collection + Stream streamFromCollection = Streams.stream(numbers); + + //Assert.assertNotNull(streamFromCollection); + StreamUtility.assertStreamEquals(streamFromCollection, numbers.stream()); + } + + @Test + public void createStreamsWithIterable(){ + Iterable numbersIterable = (Iterable) numbers; + + Stream streamFromIterable = Streams.stream(numbersIterable); + + Assert.assertNotNull(streamFromIterable); + StreamUtility.assertStreamEquals(streamFromIterable, numbers.stream()); + } + + @Test + public void createStreamsWithIterator(){ + Iterator numbersIterator = numbers.iterator(); + + Stream streamFromIterator = Streams.stream(numbersIterator); + + Assert.assertNotNull(streamFromIterator); + StreamUtility.assertStreamEquals(streamFromIterator, numbers.stream()); + } + + @Test + public void createStreamsWithOptional(){ + + Stream streamFromOptional = Streams.stream(Optional.of(1)); + + Assert.assertNotNull(streamFromOptional); + Assert.assertEquals(streamFromOptional.count(), 1); + } + + @Test + public void createStreamsWithOptionalLong(){ + + LongStream streamFromOptionalLong = Streams.stream(OptionalLong.of(1)); + + Assert.assertNotNull(streamFromOptionalLong); + Assert.assertEquals(streamFromOptionalLong.count(), 1); + } + + @Test + public void createStreamsWithOptionalInt(){ + + IntStream streamFromOptionalInt = Streams.stream(OptionalInt.of(1)); + + //Assert.assertNotNull(streamFromOptionalInt); + Assert.assertEquals(streamFromOptionalInt.count(), 1); + } + + @Test + public void createStreamsWithOptionalDouble(){ + + DoubleStream streamFromOptionalDouble = Streams.stream(OptionalDouble.of(1.0)); + + //Assert.assertNotNull(streamFromOptionalDouble); + Assert.assertEquals(streamFromOptionalDouble.count(), 1); + + } + + @Test + public void concatStreamsOfSameType(){ + Stream oddNumbers = Arrays.asList(1,3,5,7,9,11,13,15,17,19).stream(); + Stream evenNumbers = Arrays.asList(2,4,6,8,10,12,14,16,18,20).stream(); + + Stream combinedStreams = Streams.concat(oddNumbers,evenNumbers); + + //Assert.assertNotNull(combinedStreams); + StreamUtility.assertStreamEquals(combinedStreams, Stream.concat(oddNumbers, evenNumbers)); + } + + @Test + public void concatStreamsOfTypeLongStream(){ + LongStream firstTwenty = LongStream.range(1,20); + LongStream nextTwenty = LongStream.range(21,40); + + LongStream combinedStreams = Streams.concat(firstTwenty,nextTwenty); + + Assert.assertNotNull(combinedStreams); + StreamUtility.assertStreamEquals(combinedStreams, LongStream.concat(firstTwenty, nextTwenty)); + } + + @Test + public void concatStreamsOfTypeIntStream(){ + IntStream firstTwenty = IntStream.range(1,20); + IntStream nextTwenty = IntStream.range(21,40); + + IntStream combinedStreams = Streams.concat(firstTwenty,nextTwenty); + + Assert.assertNotNull(combinedStreams); + StreamUtility.assertStreamEquals(combinedStreams, IntStream.concat(firstTwenty, nextTwenty)); + } + + + @Test + public void findLastOfStream(){ + Optional lastElement = Streams.findLast(numbers.stream()); + + Assert.assertNotNull(lastElement.get()); + Assert.assertEquals(lastElement.get(), numbers.get(20)); + } + + @Test + public void mapWithIndexTest(){ + Stream stringSream = Stream.of("a","b","c"); + + Stream mappedStream = Streams.mapWithIndex(stringSream,(str,index) -> str +":"+ index); + + //Assert.assertNotNull(mappedStream); + Assert.assertEquals(mappedStream.findFirst().get(), "a:0"); + + } + + @Test + public void streamsZipTest(){ + Stream stringSream = Stream.of("a","b","c"); + Stream intStream = Stream.of(1,2,3); + Stream mappedStream = Streams.zip(stringSream,intStream, (str,index) -> str +":"+ index); + + //Assert.assertNotNull(mappedStream); + Assert.assertEquals(mappedStream.findFirst().get(), "a:1"); + + } + + + + +} diff --git a/guava21/src/test/java/InternBuilderUnitTests.java b/guava21/src/test/java/InternBuilderUnitTests.java new file mode 100644 index 0000000000..513b44f249 --- /dev/null +++ b/guava21/src/test/java/InternBuilderUnitTests.java @@ -0,0 +1,19 @@ +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import org.junit.Assert; +import org.junit.Test; + +public class InternBuilderUnitTests { + + @Test + public void interBuilderTest(){ + + Interner interners = Interners.newBuilder() + .concurrencyLevel(2) + .strong() + .build(); + + Assert.assertNotNull(interners); + } + +} diff --git a/guava21/src/test/java/MoreCollectorsUnitTests.java b/guava21/src/test/java/MoreCollectorsUnitTests.java new file mode 100644 index 0000000000..956331e25f --- /dev/null +++ b/guava21/src/test/java/MoreCollectorsUnitTests.java @@ -0,0 +1,36 @@ +import com.google.common.collect.MoreCollectors; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class MoreCollectorsUnitTests { + + @Test + public void toOptionalTest(){ + + List numbers = Arrays.asList(1); + + Optional number = numbers.stream() + .map( e -> e*2) + .collect(MoreCollectors.toOptional()); + + Assert.assertEquals(number.get(),new Integer(2)); + } + + + @Test + public void onlyElementTest(){ + List numbers = Arrays.asList(1); + + Integer number = numbers.stream() + .map( e -> e*2) + .collect(MoreCollectors.onlyElement()); + + Assert.assertEquals(number,new Integer(2)); + } + + +} diff --git a/guava21/src/test/java/StreamUtility.java b/guava21/src/test/java/StreamUtility.java new file mode 100644 index 0000000000..91a03be48d --- /dev/null +++ b/guava21/src/test/java/StreamUtility.java @@ -0,0 +1,66 @@ +import org.junit.Assert; + +import java.util.Iterator; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class StreamUtility { + + public static boolean assertStreamEquals(Stream stream1, Stream stream2){ + + Iterator iterator1 = stream1.iterator(); + Iterator iterator2 = stream2.iterator(); + + while (iterator1.hasNext()){ + Assert.assertEquals(iterator1.next(), iterator2.next()); + } + + Assert.assertFalse(iterator2.hasNext()); + + return true; + } + + public static boolean assertStreamEquals(LongStream stream1, LongStream stream2) { + + Iterator iterator1 = stream1.iterator(); + Iterator iterator2 = stream2.iterator(); + + while (iterator1.hasNext()){ + Assert.assertEquals(iterator1.next(), iterator2.next()); + } + + Assert.assertFalse(iterator2.hasNext()); + + return true; + } + + public static boolean assertStreamEquals(DoubleStream stream1, DoubleStream stream2) { + + Iterator iterator1 = stream1.iterator(); + Iterator iterator2 = stream2.iterator(); + + while (iterator1.hasNext()){ + Assert.assertEquals(iterator1.next(), iterator2.next()); + } + + Assert.assertFalse(iterator2.hasNext()); + + return true; + } + + public static boolean assertStreamEquals(IntStream stream1, IntStream stream2) { + + Iterator iterator1 = stream1.iterator(); + Iterator iterator2 = stream2.iterator(); + + while (iterator1.hasNext()){ + Assert.assertEquals(iterator1.next(), iterator2.next()); + } + + Assert.assertFalse(iterator2.hasNext()); + + return true; + } +} From 52c1e6a3fbebf2d400d1a5182a26e7c5a99804ff Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 20 Mar 2017 09:18:03 +0100 Subject: [PATCH 046/149] Update README.md --- guava21/README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/guava21/README.md b/guava21/README.md index 8121cf2ea6..ff12555376 100644 --- a/guava21/README.md +++ b/guava21/README.md @@ -1,7 +1 @@ -========= - -## Guava 21 - -**Important**: Guava 21.0 requires Java 8. If you need Java 6, 7 or Android compatibility, use Guava 20.0 for now. Guava 22.0 and on will introduce a Java 6/Android compatible backport of Guava that includes all of the latest changes that don't require Java 8. - -Article 1 : Introduction to Guava21 common.collect package. \ No newline at end of file +## Relevant articles: From 29645fc0d12fdade2cdc03104aa8a04873ca8505 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Mon, 20 Mar 2017 12:18:41 +0100 Subject: [PATCH 047/149] BAEL-724 (#1422) Property testing with Javaslang * BAEL-724 add javaslang test and property testing example * BAEL-724 make test more readable * BAEL-724 change missspelled word to the Remainder --- javaslang/pom.xml | 9 +++ .../baeldung/javaslang/PropertyBasedTest.java | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java diff --git a/javaslang/pom.xml b/javaslang/pom.xml index e111132aec..7bb23c0daf 100644 --- a/javaslang/pom.xml +++ b/javaslang/pom.xml @@ -22,8 +22,17 @@ javaslang 2.1.0-alpha + + io.javaslang + javaslang-test + ${javaslang.test.version} + + + 2.0.5 + + diff --git a/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java new file mode 100644 index 0000000000..3acac34550 --- /dev/null +++ b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java @@ -0,0 +1,69 @@ +package com.baeldung.javaslang; + + +import javaslang.CheckedFunction1; +import javaslang.collection.Stream; +import javaslang.test.Arbitrary; +import javaslang.test.CheckResult; +import javaslang.test.Property; +import org.junit.Test; + +public class PropertyBasedTest { + + public Stream stringsSupplier() { + return Stream.from(0).map(i -> { + boolean divByTwo = i % 2 == 0; + boolean divByFive = i % 5 == 0; + + if(divByFive && divByTwo){ + return "DividedByTwoAndFiveWithoutRemainder"; + }else if(divByFive){ + return "DividedByFiveWithoutRemainder"; + }else if(divByTwo){ + return "DividedByTwoWithoutRemainder"; + } + return ""; + }); + } + + @Test + public void givenArbitrarySeq_whenCheckThatEverySecondElementIsEqualToString_thenTestPass() { + //given + Arbitrary multiplesOf2 = Arbitrary.integer() + .filter(i -> i > 0) + .filter(i -> i % 2 == 0 && i % 5 != 0); + + //when + CheckedFunction1 mustEquals = + i -> stringsSupplier().get(i).equals("DividedByTwoWithoutRemainder"); + + + //then + CheckResult result = Property + .def("Every second element must equal to DividedByTwoWithoutRemainder") + .forAll(multiplesOf2) + .suchThat(mustEquals) + .check(10_000, 100); + + result.assertIsSatisfied(); + } + + @Test + public void givenArbitrarySeq_whenCheckThatEveryFifthElementIsEqualToString_thenTestPass() { + //given + Arbitrary multiplesOf5 = Arbitrary.integer() + .filter(i -> i > 0) + .filter(i -> i % 5 == 0 && i % 2 == 0); + + //when + CheckedFunction1 mustEquals = i -> + stringsSupplier().get(i).endsWith("DividedByTwoAndFiveWithoutRemainder"); + + //then + Property.def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder") + .forAll(multiplesOf5) + .suchThat(mustEquals) + .check(10_000, 1_000) + .assertIsSatisfied(); + } +} From 5294e7d42587df12774dc50a7304c33d0beea9c8 Mon Sep 17 00:00:00 2001 From: slavisa-baeldung Date: Mon, 20 Mar 2017 15:50:43 +0000 Subject: [PATCH 048/149] BAEL-702 - Intro to Vert.x formatting changes --- .../main/java/com/baeldung/HelloVerticle.java | 10 +++---- .../com/baeldung/SimpleServerVerticle.java | 24 ++++++++--------- .../baledung/rest/RestServiceVerticle.java | 26 +++++++++---------- .../com/baeldung/RestServiceVerticleTest.java | 12 ++++----- .../baeldung/SimpleServerVerticleTest.java | 16 ++++++------ 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/vertx/src/main/java/com/baeldung/HelloVerticle.java b/vertx/src/main/java/com/baeldung/HelloVerticle.java index 98d1b336a3..59baceb0d8 100644 --- a/vertx/src/main/java/com/baeldung/HelloVerticle.java +++ b/vertx/src/main/java/com/baeldung/HelloVerticle.java @@ -10,6 +10,11 @@ import io.vertx.core.Vertx; public class HelloVerticle extends AbstractVerticle { private static final Logger LOGGER = LoggerFactory.getLogger(HelloVerticle.class); + public static void main(String[] args) { + Vertx vertx = Vertx.vertx(); + vertx.deployVerticle(new HelloVerticle()); + } + @Override public void start(Future future) { LOGGER.info("Welcome to Vertx"); @@ -19,10 +24,5 @@ public class HelloVerticle extends AbstractVerticle { public void stop() { LOGGER.info("Shutting down application"); } - - public static void main(String[] args) { - Vertx vertx = Vertx.vertx(); - vertx.deployVerticle(new HelloVerticle()); - } } diff --git a/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java index 2cee37903b..6b56896860 100644 --- a/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java +++ b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java @@ -4,21 +4,21 @@ import io.vertx.core.AbstractVerticle; import io.vertx.core.Future; public class SimpleServerVerticle extends AbstractVerticle { - + @Override public void start(Future future) { vertx.createHttpServer() - .requestHandler(request -> { - request.response() - .end("Welcome to Vert.x Intro"); - }) - .listen(config().getInteger("http.port", 8080), result -> { - if (result.succeeded()) { - future.complete(); - } else { - future.fail(result.cause()); - } - }); + .requestHandler(request -> { + request.response() + .end("Welcome to Vert.x Intro"); + }) + .listen(config().getInteger("http.port", 8080), result -> { + if (result.succeeded()) { + future.complete(); + } else { + future.fail(result.cause()); + } + }); } } diff --git a/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java b/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java index 181b3007d5..802f74942e 100644 --- a/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java +++ b/vertx/src/main/java/com/baledung/rest/RestServiceVerticle.java @@ -14,28 +14,28 @@ public class RestServiceVerticle extends AbstractVerticle { Router router = Router.router(vertx); router.get("/api/baeldung/articles/article/:id") - .handler(this::getArticles); + .handler(this::getArticles); vertx.createHttpServer() - .requestHandler(router::accept) - .listen(config().getInteger("http.port", 8080), result -> { - if (result.succeeded()) { - future.complete(); - } else { - future.fail(result.cause()); - } - }); + .requestHandler(router::accept) + .listen(config().getInteger("http.port", 8080), result -> { + if (result.succeeded()) { + future.complete(); + } else { + future.fail(result.cause()); + } + }); } private void getArticles(RoutingContext routingContext) { String articleId = routingContext.request() - .getParam("id"); + .getParam("id"); Article article = new Article(articleId, "This is an intro to vertx", "baeldung", "01-02-2017", 1578); routingContext.response() - .putHeader("content-type", "application/json") - .setStatusCode(200) - .end(Json.encodePrettily(article)); + .putHeader("content-type", "application/json") + .setStatusCode(200) + .end(Json.encodePrettily(article)); } } diff --git a/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java b/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java index 8eebe1396d..b5be0734f4 100644 --- a/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java +++ b/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java @@ -34,13 +34,13 @@ public class RestServiceVerticleTest { final Async async = testContext.async(); vertx.createHttpClient() - .getNow(8080, "localhost", "/api/baeldung/articles/article/12345", response -> { - response.handler(responseBody -> { - testContext.assertTrue(responseBody.toString() - .contains("\"id\" : \"12345\"")); - async.complete(); + .getNow(8080, "localhost", "/api/baeldung/articles/article/12345", response -> { + response.handler(responseBody -> { + testContext.assertTrue(responseBody.toString() + .contains("\"id\" : \"12345\"")); + async.complete(); + }); }); - }); } } diff --git a/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java b/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java index 177f8d8435..189d2f6604 100644 --- a/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java +++ b/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java @@ -20,8 +20,8 @@ public class SimpleServerVerticleTest { public void setup(TestContext testContext) { vertx = Vertx.vertx(); - vertx.deployVerticle(SimpleServerVerticle.class.getName(), - testContext.asyncAssertSuccess()); + vertx.deployVerticle(SimpleServerVerticle.class.getName(), + testContext.asyncAssertSuccess()); } @After @@ -34,13 +34,13 @@ public class SimpleServerVerticleTest { final Async async = testContext.async(); vertx.createHttpClient() - .getNow(8080, "localhost", "/", response -> { - response.handler(responseBody -> { - testContext.assertTrue(responseBody.toString() - .contains("Welcome")); - async.complete(); + .getNow(8080, "localhost", "/", response -> { + response.handler(responseBody -> { + testContext.assertTrue(responseBody.toString() + .contains("Welcome")); + async.complete(); + }); }); - }); } } From b95a014c8ac1c1566d2725c2055a142bfc701b61 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Mon, 20 Mar 2017 17:54:02 +0100 Subject: [PATCH 049/149] BAEL-738 (#1456) PUT and PATCH * BAEL-724 code for put/patch article * BAEL-724 fix typo --- .../repository/HeavyResourceRepository.java | 14 +++++ .../controller/HeavyResourceController.java | 31 ++++++++++ .../org/baeldung/web/dto/HeavyResource.java | 62 +++++++++++++++++++ .../HeavyResourceAddressPartialUpdate.java | 31 ++++++++++ .../HeavyResourceControllerTest.java | 57 +++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java create mode 100644 spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java create mode 100644 spring-rest/src/main/java/org/baeldung/web/dto/HeavyResource.java create mode 100644 spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressPartialUpdate.java create mode 100644 spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java diff --git a/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java new file mode 100644 index 0000000000..cff78442d0 --- /dev/null +++ b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java @@ -0,0 +1,14 @@ +package org.baeldung.repository; + +import org.baeldung.web.dto.HeavyResource; +import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; + +public class HeavyResourceRepository { + + public void save(HeavyResource heavyResource) { + } + + public void save(HeavyResourceAddressPartialUpdate partialUpdate) { + + } +} diff --git a/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java new file mode 100644 index 0000000000..a2d5cfbd7b --- /dev/null +++ b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java @@ -0,0 +1,31 @@ +package org.baeldung.web.controller; + + +import org.baeldung.repository.HeavyResourceRepository; +import org.baeldung.web.dto.HeavyResource; +import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HeavyResourceController { + + private HeavyResourceRepository heavyResourceRepository = new HeavyResourceRepository(); + + @RequestMapping(value = "/heavy", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity saveResource(@RequestBody HeavyResource heavyResource) { + heavyResourceRepository.save(heavyResource); + return ResponseEntity.ok("resource saved"); + } + + @RequestMapping(value = "/heavy", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity partialUpdateName(@RequestBody HeavyResourceAddressPartialUpdate partialUpdate) { + heavyResourceRepository.save(partialUpdate); + return ResponseEntity.ok("resource address updated"); + } + +} diff --git a/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResource.java b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResource.java new file mode 100644 index 0000000000..934e76606f --- /dev/null +++ b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResource.java @@ -0,0 +1,62 @@ +package org.baeldung.web.dto; + + +public class HeavyResource { + private Integer id; + private String name; + private String surname; + private Integer age; + private String address; + + + public HeavyResource() { + } + + public HeavyResource(Integer id, String name, String surname, Integer age, String address) { + this.id = id; + this.name = name; + this.surname = surname; + this.age = age; + this.address = address; + } + + 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; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressPartialUpdate.java b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressPartialUpdate.java new file mode 100644 index 0000000000..f90f02ea08 --- /dev/null +++ b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressPartialUpdate.java @@ -0,0 +1,31 @@ +package org.baeldung.web.dto; + + +public class HeavyResourceAddressPartialUpdate { + private Integer id; + private String address; + + public HeavyResourceAddressPartialUpdate() { + } + + public HeavyResourceAddressPartialUpdate(Integer id, String address) { + this.id = id; + this.address = address; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java new file mode 100644 index 0000000000..e68506701d --- /dev/null +++ b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java @@ -0,0 +1,57 @@ +package org.baeldung.web.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.baeldung.config.WebConfig; +import org.baeldung.web.dto.HeavyResource; +import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = WebConfig.class) +@WebAppConfiguration +public class HeavyResourceControllerTest { + + private MockMvc mockMvc; + + @Autowired + private WebApplicationContext webApplicationContext; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Before + public void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Test + public void givenHeavyResource_whenSendPutRequest_thenCreateResource() throws Exception { + mockMvc.perform(put("/heavy") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) + ).andExpect(status().isOk()); + } + + @Test + public void givenNewAddressOfResource_whenExecutePutRequest_thenUpdateResourcePartially() throws Exception { + mockMvc.perform(patch("/heavy") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(new HeavyResourceAddressPartialUpdate(1, "5th avenue"))) + ).andExpect(status().isOk()); + } + +} \ No newline at end of file From 9c755ee39c9401f32c3592f9dc664e84a6669aad Mon Sep 17 00:00:00 2001 From: Yasin Date: Mon, 20 Mar 2017 23:43:42 +0530 Subject: [PATCH 050/149] BAEL-722 Intro to JSONassert (#1460) * yasin.bhojawala@gmail.com Evaluation article on Different Types of Bean Injection in Spring * Revert "yasin.bhojawala@gmail.com" This reverts commit 963cc51a7a15b75b550108fe4e198cd65a274032. * Fixing compilation error and removing unused import * Introduction to Java9 StackWalking API - yasin.bhojawala@gmail.com Code examples for the article "Introduction to Java9 StackWalking API" * BAEL-608 Introduction to Java9 StackWalking API * BAEL-608 Introduction to Java9 StackWalking API changing the test names to BDD style * BAEL-608 Introduction to Java9 StackWalking API correcting the typo * BAEL-608 Introduction to Java9 StackWalking API improving method names * BAEL-608 Introduction to Java9 StackWalking API test method names improvements * BAEL-718 Quick intro to javatuples * merging pom from master * BAEL-722 Intro to JSONassert * BAEL-722 Intro to JSONassert Updated to 1.5.0 --- libraries/pom.xml | 2 +- .../com/baeldung/jsonassert/JsonAssertTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/libraries/pom.xml b/libraries/pom.xml index 85c777b12c..71d0e76c8a 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -77,7 +77,7 @@ 1.2 3.21.0-GA 3.6.2 - 1.4.0 + 1.5.0 \ No newline at end of file diff --git a/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java b/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java index c169d83897..b70703cc08 100644 --- a/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java +++ b/libraries/src/test/java/com/baeldung/jsonassert/JsonAssertTest.java @@ -1,5 +1,8 @@ package com.baeldung.jsonassert; + +import static org.assertj.core.api.Assertions.assertThat; + import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; @@ -64,6 +67,17 @@ public class JsonAssertTest { JSONAssert.assertEquals("{id:1,name:\"Juergen\", address:{city:\"Hollywood\", " + "state:\"LA\", zip:91601}}", result, false); } + + @Test + public void whenMessageUsedInAssertion_thenDisplayMessageOnFailure() throws JSONException { + String actual = "{id:123,name:\"John\"}"; + String failureMessage = "Only one field is expected: name"; + try { + JSONAssert.assertEquals(failureMessage, "{name:\"John\"}", actual, JSONCompareMode.STRICT); + } catch (AssertionError ae) { + assertThat(ae.getMessage()).containsIgnoringCase(failureMessage); + } + } @Test public void givenArray_whenComparing_thenOrderMustMatchForStrict() throws JSONException { From b5ef48a1d88085e13e57814edd0b03b296d37e98 Mon Sep 17 00:00:00 2001 From: pivovarit Date: Tue, 21 Mar 2017 10:00:35 +0100 Subject: [PATCH 051/149] Turn off spring-5 module --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d68a7d0749..e0556a7c50 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ handling-spring-static-resources hazelcast - + httpclient hystrix @@ -110,7 +110,7 @@ selenium-junit-testng solr-fulltext-search spark-java - spring-5 + spring-akka spring-amqp spring-all From 07c0e84bf47466dd76eaa3ab3b4c649caf264f04 Mon Sep 17 00:00:00 2001 From: Parth Joshi Date: Tue, 21 Mar 2017 15:54:57 +0530 Subject: [PATCH 052/149] Initial commit for merging modules spring-mvc-forms into (#1222) spring-mvc-simple. --- spring-mvc-forms/README.md | 1 + spring-mvc-simple/pom.xml | 35 ++++++++++++ .../ApplicationConfiguration.java | 37 +++++++++++++ .../spring/configuration/WebInitializer.java | 48 +++++++++++++++++ .../spring/controller/CustomerController.java | 42 +++++++++++++++ .../spring/controller/EmployeeController.java | 47 ++++++++++++++++ .../controller/FileUploadController.java | 52 ++++++++++++++++++ .../com/baeldung/spring/domain/Customer.java | 45 ++++++++++++++++ .../com/baeldung/spring/domain/Employee.java | 46 ++++++++++++++++ .../FileUploadExceptionAdvice.java | 19 +++++++ .../spring/validator/CustomerValidator.java | 28 ++++++++++ ...servlet_AnnotationMethodHandlerAdapter.xml | 50 ++++++++--------- ...g-servlet_RequestMappingHandlerAdapter.xml | 54 +++++++++---------- ...servlet_SimpleControllerHandlerAdapter.xml | 48 ++++++++--------- .../src/main/webapp/WEB-INF/Greeting.jsp | 9 ++-- .../main/webapp/WEB-INF/views/Greeting.jsp | 5 ++ .../webapp/WEB-INF/views/customerHome.jsp | 47 ++++++++++++++++ .../webapp/WEB-INF/views/customerView.jsp | 28 ++++++++++ .../webapp/WEB-INF/views/employeeHome.jsp | 33 ++++++++++++ .../webapp/WEB-INF/views/employeeView.jsp | 24 +++++++++ .../src/main/webapp/WEB-INF/views/error.jsp | 20 +++++++ .../src/main/webapp/WEB-INF/views/file.jsp | 23 ++++++++ 22 files changed, 661 insertions(+), 80 deletions(-) create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/ApplicationConfiguration.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/WebInitializer.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/controller/CustomerController.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/controller/EmployeeController.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/controller/FileUploadController.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Customer.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Employee.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/interceptor/FileUploadExceptionAdvice.java create mode 100644 spring-mvc-simple/src/main/java/com/baeldung/spring/validator/CustomerValidator.java create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/Greeting.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/customerHome.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/customerView.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeHome.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeView.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/error.jsp create mode 100644 spring-mvc-simple/src/main/webapp/WEB-INF/views/file.jsp diff --git a/spring-mvc-forms/README.md b/spring-mvc-forms/README.md index 51dbef9856..745851a102 100644 --- a/spring-mvc-forms/README.md +++ b/spring-mvc-forms/README.md @@ -2,3 +2,4 @@ ### Relevant Articles - [MaxUploadSizeExceededException in Spring](http://www.baeldung.com/spring-maxuploadsizeexceeded) +- [Getting Started with Forms in Spring MVC](http://www.baeldung.com/spring-mvc-form-tutorial) diff --git a/spring-mvc-simple/pom.xml b/spring-mvc-simple/pom.xml index 4ab5bd9d1e..a004eae4d9 100644 --- a/spring-mvc-simple/pom.xml +++ b/spring-mvc-simple/pom.xml @@ -12,6 +12,13 @@ 4.3.4.RELEASE 3.5.1 2.6 + 1.2 + 2.3.1 + 3.1.0 + 1.8 + 5.3.3.Final + enter-location-of-server + 1.3.2 @@ -40,6 +47,33 @@ spring-core ${springframework.version} + + javax.servlet.jsp + javax.servlet.jsp-api + ${javax.servlet.jsp-api.version} + + + + javax.servlet + jstl + ${jstl.version} + + + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + + org.springframework + spring-webmvc + ${springframework.version} + + + commons-fileupload + commons-fileupload + ${fileupload.version} + @@ -61,6 +95,7 @@ src/main/webapp springMvcSimple false + ${deploy-path} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/ApplicationConfiguration.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/ApplicationConfiguration.java new file mode 100644 index 0000000000..9ace968bbe --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/ApplicationConfiguration.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.commons.CommonsMultipartResolver; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.baeldung.springmvcforms") +class ApplicationConfiguration extends WebMvcConfigurerAdapter { + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Bean + public InternalResourceViewResolver jspViewResolver() { + InternalResourceViewResolver bean = new InternalResourceViewResolver(); + bean.setPrefix("/WEB-INF/views/"); + bean.setSuffix(".jsp"); + return bean; + } + + @Bean + public MultipartResolver multipartResolver() { + CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); + multipartResolver.setMaxUploadSize(5242880); + return multipartResolver; + } +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/WebInitializer.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/WebInitializer.java new file mode 100644 index 0000000000..d6bbf5eabd --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/configuration/WebInitializer.java @@ -0,0 +1,48 @@ +package com.baeldung.spring.configuration; + +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +public class WebInitializer implements WebApplicationInitializer { + + public void onStartup(ServletContext container) throws ServletException { + + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.register(ApplicationConfiguration.class); + ctx.setServletContext(container); + + // Manage the lifecycle of the root application context + container.addListener(new ContextLoaderListener(ctx)); + + ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); + + servlet.setLoadOnStartup(1); + servlet.addMapping("/"); + + } +// @Override +// public void onStartup(ServletContext container) { +// // Create the 'root' Spring application context +// AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); +// rootContext.register(ServiceConfig.class, JPAConfig.class, SecurityConfig.class); +// +// // Manage the lifecycle of the root application context +// container.addListener(new ContextLoaderListener(rootContext)); +// +// // Create the dispatcher servlet's Spring application context +// AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext(); +// dispatcherServlet.register(MvcConfig.class); +// +// // Register and map the dispatcher servlet +// ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet)); +// dispatcher.setLoadOnStartup(1); +// dispatcher.addMapping("/"); +// +// } +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/CustomerController.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/CustomerController.java new file mode 100644 index 0000000000..8ecfce58e3 --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/CustomerController.java @@ -0,0 +1,42 @@ +package com.baeldung.spring.controller; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +import com.baeldung.spring.domain.Customer; +import com.baeldung.spring.validator.CustomerValidator; + +@Controller +public class CustomerController { + + @Autowired + CustomerValidator validator; + + @RequestMapping(value = "/customer", method = RequestMethod.GET) + public ModelAndView showForm() { + return new ModelAndView("customerHome", "customer", new Customer()); + } + + @PostMapping("/addCustomer") + public String submit(@Valid @ModelAttribute("customer") final Customer customer, final BindingResult result, final ModelMap model) { + validator.validate(customer, result); + if (result.hasErrors()) { + return "customerHome"; + } + model.addAttribute("customerId", customer.getCustomerId()); + model.addAttribute("customerName", customer.getCustomerName()); + model.addAttribute("customerContact", customer.getCustomerContact()); + model.addAttribute("customerEmail", customer.getCustomerEmail()); + return "customerView"; + } + +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/EmployeeController.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/EmployeeController.java new file mode 100644 index 0000000000..6543a98af1 --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/EmployeeController.java @@ -0,0 +1,47 @@ +package com.baeldung.spring.controller; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.Valid; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +import com.baeldung.spring.domain.Employee; + +@Controller +public class EmployeeController { + + Map employeeMap = new HashMap<>(); + + @RequestMapping(value = "/employee", method = RequestMethod.GET) + public ModelAndView showForm() { + return new ModelAndView("employeeHome", "employee", new Employee()); + } + + @RequestMapping(value = "/employee/{Id}", produces = { "application/json", "application/xml" }, method = RequestMethod.GET) + public @ResponseBody Employee getEmployeeById(@PathVariable final long Id) { + return employeeMap.get(Id); + } + + @RequestMapping(value = "/addEmployee", method = RequestMethod.POST) + public String submit(@Valid @ModelAttribute("employee") final Employee employee, final BindingResult result, final ModelMap model) { + if (result.hasErrors()) { + return "error"; + } + model.addAttribute("name", employee.getName()); + model.addAttribute("contactNumber", employee.getContactNumber()); + model.addAttribute("id", employee.getId()); + employeeMap.put(employee.getId(), employee); + return "employeeView"; + } + +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/FileUploadController.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/FileUploadController.java new file mode 100644 index 0000000000..47af2ab50d --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/controller/FileUploadController.java @@ -0,0 +1,52 @@ +package com.baeldung.spring.controller; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +@Controller +public class FileUploadController implements HandlerExceptionResolver { + + @RequestMapping(value = "/uploadFile", method = RequestMethod.GET) + public String getImageView() { + return "file"; + } + + @RequestMapping(value = "/uploadFile", method = RequestMethod.POST) + public ModelAndView uploadFile(MultipartFile file) throws IOException{ + ModelAndView modelAndView = new ModelAndView("file"); + + InputStream in = file.getInputStream(); + File currDir = new File("."); + String path = currDir.getAbsolutePath(); + FileOutputStream f = new FileOutputStream(path.substring(0, path.length()-1)+ file.getOriginalFilename()); + int ch = 0; + while ((ch = in.read()) != -1) { + f.write(ch); + } + f.flush(); + f.close(); + + modelAndView.getModel().put("message", "File uploaded successfully!"); + return modelAndView; + } + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object object, Exception exc) { + ModelAndView modelAndView = new ModelAndView("file"); + if (exc instanceof MaxUploadSizeExceededException) { + modelAndView.getModel().put("message", "File size exceeds limit!"); + } + return modelAndView; + } +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Customer.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Customer.java new file mode 100644 index 0000000000..09322105f8 --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Customer.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.domain; + +public class Customer { + private String customerId; + private String customerName; + private String customerContact; + private String customerEmail; + + public Customer() { + super(); + } + + public String getCustomerId() { + return customerId; + } + + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getCustomerContact() { + return customerContact; + } + + public void setCustomerContact(String customerContact) { + this.customerContact = customerContact; + } + + public String getCustomerEmail() { + return customerEmail; + } + + public void setCustomerEmail(String customerEmail) { + this.customerEmail = customerEmail; + } + +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Employee.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Employee.java new file mode 100644 index 0000000000..900770b873 --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/domain/Employee.java @@ -0,0 +1,46 @@ +package com.baeldung.spring.domain; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class Employee { + + private long id; + + @NotNull + @Size(min = 5) + private String name; + + @NotNull + @Size(min = 7) + private String contactNumber; + + public Employee() { + super(); + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getContactNumber() { + return contactNumber; + } + + public void setContactNumber(final String contactNumber) { + this.contactNumber = contactNumber; + } + +} diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/interceptor/FileUploadExceptionAdvice.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/interceptor/FileUploadExceptionAdvice.java new file mode 100644 index 0000000000..2f3c44cf12 --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/interceptor/FileUploadExceptionAdvice.java @@ -0,0 +1,19 @@ +package com.baeldung.spring.interceptor; + +import org.springframework.web.servlet.ModelAndView; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class FileUploadExceptionAdvice { + + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ModelAndView handleMaxSizeException(MaxUploadSizeExceededException exc, HttpServletRequest request, HttpServletResponse response){ + ModelAndView modelAndView = new ModelAndView("file"); + modelAndView.getModel().put("message", "File too large!"); + return modelAndView; + } +} \ No newline at end of file diff --git a/spring-mvc-simple/src/main/java/com/baeldung/spring/validator/CustomerValidator.java b/spring-mvc-simple/src/main/java/com/baeldung/spring/validator/CustomerValidator.java new file mode 100644 index 0000000000..2515e8d31f --- /dev/null +++ b/spring-mvc-simple/src/main/java/com/baeldung/spring/validator/CustomerValidator.java @@ -0,0 +1,28 @@ +package com.baeldung.spring.validator; + +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +import com.baeldung.spring.domain.Customer; + +@Component +public class CustomerValidator implements Validator { + + @Override + public boolean supports(Class clazz) { + return Customer.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "customerId", "error.customerId", "Customer Id is required."); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "customerName", "error.customerName", "Customer Name is required."); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "customerContact", "error.customerNumber", "Customer Contact is required."); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "customerEmail", "error.customerEmail", "Customer Email is required."); + + } + +} diff --git a/spring-mvc-simple/src/main/resources/spring-servlet_AnnotationMethodHandlerAdapter.xml b/spring-mvc-simple/src/main/resources/spring-servlet_AnnotationMethodHandlerAdapter.xml index a8071a622f..430b849012 100644 --- a/spring-mvc-simple/src/main/resources/spring-servlet_AnnotationMethodHandlerAdapter.xml +++ b/spring-mvc-simple/src/main/resources/spring-servlet_AnnotationMethodHandlerAdapter.xml @@ -1,26 +1,26 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/resources/spring-servlet_RequestMappingHandlerAdapter.xml b/spring-mvc-simple/src/main/resources/spring-servlet_RequestMappingHandlerAdapter.xml index b32a213eb3..d3783c2e67 100644 --- a/spring-mvc-simple/src/main/resources/spring-servlet_RequestMappingHandlerAdapter.xml +++ b/spring-mvc-simple/src/main/resources/spring-servlet_RequestMappingHandlerAdapter.xml @@ -1,28 +1,28 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/resources/spring-servlet_SimpleControllerHandlerAdapter.xml b/spring-mvc-simple/src/main/resources/spring-servlet_SimpleControllerHandlerAdapter.xml index 60c9b9e3df..1d6e5628df 100644 --- a/spring-mvc-simple/src/main/resources/spring-servlet_SimpleControllerHandlerAdapter.xml +++ b/spring-mvc-simple/src/main/resources/spring-servlet_SimpleControllerHandlerAdapter.xml @@ -1,25 +1,25 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/Greeting.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/Greeting.jsp index 820d2f380f..7f7fbccccd 100644 --- a/spring-mvc-simple/src/main/webapp/WEB-INF/Greeting.jsp +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/Greeting.jsp @@ -1,5 +1,6 @@ - - -

Hello ${message}

- + + +

Hello ${message}

+ + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/Greeting.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/Greeting.jsp new file mode 100644 index 0000000000..efd48179f9 --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/Greeting.jsp @@ -0,0 +1,5 @@ + + +

Hello ${message}

+ + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerHome.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerHome.jsp new file mode 100644 index 0000000000..df34a47cc0 --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerHome.jsp @@ -0,0 +1,47 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> + + + +Form Example - Add Customer + + + +

Welcome, Enter The Customer Details

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Customer Id
Customer Name
Customer Contact
Customer Email
+
+ + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerView.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerView.jsp new file mode 100644 index 0000000000..ab2631bd02 --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/customerView.jsp @@ -0,0 +1,28 @@ +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + +Spring MVC Form Handling + + + +

Submitted Customer Information

+ + + + + + + + + + + + + + + + + +
Customer Id :${customerId}
Customer Name :${customerName}
Customer Contact :${customerContact}
Customer Email :${customerEmail}
+ + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeHome.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeHome.jsp new file mode 100644 index 0000000000..5ed572000a --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeHome.jsp @@ -0,0 +1,33 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> + + + +Form Example - Register an Employee + + +

Welcome, Enter The Employee Details

+ + + + + + + + + + + + + + + + + + +
Name
Id
Contact Number
+
+ + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeView.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeView.jsp new file mode 100644 index 0000000000..1457bc5fc8 --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/employeeView.jsp @@ -0,0 +1,24 @@ +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + +Spring MVC Form Handling + + + +

Submitted Employee Information

+ + + + + + + + + + + + + +
Name :${name}
ID :${id}
Contact Number :${contactNumber}
+ + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/error.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/error.jsp new file mode 100644 index 0000000000..8f3d83af17 --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/error.jsp @@ -0,0 +1,20 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + + + + +SpringMVCExample + + + +

Pleas enter the correct details

+ + + + +
Retry
+ + + + \ No newline at end of file diff --git a/spring-mvc-simple/src/main/webapp/WEB-INF/views/file.jsp b/spring-mvc-simple/src/main/webapp/WEB-INF/views/file.jsp new file mode 100644 index 0000000000..0ed8dae5ed --- /dev/null +++ b/spring-mvc-simple/src/main/webapp/WEB-INF/views/file.jsp @@ -0,0 +1,23 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + +Upload file + + + +
+ + +
+
+${message } +

+
+ +
+ + \ No newline at end of file From 78f87104d66ae0f9d01ba052e58c02cdbec6973f Mon Sep 17 00:00:00 2001 From: Felipe Reis Date: Tue, 21 Mar 2017 08:23:41 -0300 Subject: [PATCH 053/149] BAEL-137 Intro do JHipster (#1427) * refactor: Reorder tests without lambda Moves inner implementations of Answer and ArgumentMatcher to the top of the test classes. Also changes the lambda expression to a regular "pre java 8" expression in one of the tests. Resolves: BAEL-632 * feat: Create basic Monolithic JHipster project Commit just after creating a JHipster project, before making any modifications. Resolves: BAEL-137 * chore: Change the artifactId and name of the project From baeldung to jhipster-monolithic and JHipster Monolithic Application Relates to: BAEL-137 * feat: Create entities Post and Comment Relates to: BAEL-137 * feat: Fix Gatling configuration in pom.xml Relates to: BAEL-137 * feat: Add files for Continuous Integration Relates to: BAEL-137 * feat: Change pom.xml to conform to Baeldung standards - moved the element to the bottom of the file - excluded integration tests in the default surefire configuration - added a new profile, called integration, and added the integration tests there - added Java 8 in the and tags, under maven-compiler solves: BAEL-137 * chore: Add jhipster module to parent pom --- jhipster/.editorconfig | 24 + jhipster/.gitattributes | 22 + jhipster/.gitignore | 143 +++ jhipster/.gitlab-ci.yml | 56 + jhipster/.jhipster/Comment.json | 39 + jhipster/.jhipster/Post.json | 52 + jhipster/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + jhipster/.travis.yml | 41 + jhipster/.yo-rc.json | 36 + jhipster/Jenkinsfile | 50 + jhipster/README.md | 153 +++ jhipster/angular-cli.json | 55 + jhipster/circle.yml | 25 + jhipster/mvnw | 233 ++++ jhipster/mvnw.cmd | 145 +++ jhipster/package.json | 112 ++ jhipster/pom.xml | 1034 +++++++++++++++++ jhipster/postcss.config.js | 3 + jhipster/src/main/docker/Dockerfile | 13 + jhipster/src/main/docker/app.yml | 14 + jhipster/src/main/docker/mysql.yml | 13 + jhipster/src/main/docker/sonar.yml | 7 + .../java/com/baeldung/ApplicationWebXml.java | 21 + .../main/java/com/baeldung/BaeldungApp.java | 84 ++ .../baeldung/aop/logging/LoggingAspect.java | 79 ++ .../config/ApplicationProperties.java | 15 + .../baeldung/config/AsyncConfiguration.java | 46 + .../baeldung/config/CacheConfiguration.java | 48 + .../config/CloudDatabaseConfiguration.java | 23 + .../java/com/baeldung/config/Constants.java | 16 + .../config/DatabaseConfiguration.java | 75 ++ .../config/DateTimeFormatConfiguration.java | 17 + .../baeldung/config/DefaultProfileUtil.java | 48 + .../baeldung/config/LocaleConfiguration.java | 35 + .../config/LoggingAspectConfiguration.java | 19 + .../baeldung/config/LoggingConfiguration.java | 109 ++ .../baeldung/config/MetricsConfiguration.java | 94 ++ .../config/SecurityConfiguration.java | 127 ++ .../config/ThymeleafConfiguration.java | 26 + .../com/baeldung/config/WebConfigurer.java | 186 +++ .../config/audit/AuditEventConverter.java | 91 ++ .../baeldung/config/audit/package-info.java | 4 + .../com/baeldung/config/package-info.java | 4 + .../domain/AbstractAuditingEntity.java | 80 ++ .../java/com/baeldung/domain/Authority.java | 66 ++ .../java/com/baeldung/domain/Comment.java | 114 ++ .../baeldung/domain/PersistentAuditEvent.java | 78 ++ .../main/java/com/baeldung/domain/Post.java | 133 +++ .../main/java/com/baeldung/domain/User.java | 235 ++++ .../com/baeldung/domain/package-info.java | 4 + .../repository/AuthorityRepository.java | 11 + .../repository/CommentRepository.java | 15 + .../CustomAuditEventRepository.java | 81 ++ .../PersistenceAuditEventRepository.java | 26 + .../baeldung/repository/PostRepository.java | 18 + .../baeldung/repository/UserRepository.java | 37 + .../com/baeldung/repository/package-info.java | 4 + .../security/AuthoritiesConstants.java | 16 + .../security/DomainUserDetailsService.java | 51 + .../com/baeldung/security/SecurityUtils.java | 68 ++ .../security/SpringSecurityAuditorAware.java | 19 + .../security/UserNotActivatedException.java | 19 + .../baeldung/security/jwt/JWTConfigurer.java | 23 + .../com/baeldung/security/jwt/JWTFilter.java | 58 + .../baeldung/security/jwt/TokenProvider.java | 109 ++ .../com/baeldung/security/package-info.java | 4 + .../baeldung/service/AuditEventService.java | 50 + .../com/baeldung/service/MailService.java | 108 ++ .../com/baeldung/service/UserService.java | 228 ++++ .../com/baeldung/service/dto/UserDTO.java | 167 +++ .../baeldung/service/dto/package-info.java | 4 + .../baeldung/service/mapper/UserMapper.java | 55 + .../baeldung/service/mapper/package-info.java | 4 + .../com/baeldung/service/package-info.java | 4 + .../com/baeldung/service/util/RandomUtil.java | 41 + .../baeldung/web/rest/AccountResource.java | 202 ++++ .../com/baeldung/web/rest/AuditResource.java | 76 ++ .../baeldung/web/rest/CommentResource.java | 129 ++ .../java/com/baeldung/web/rest/JWTToken.java | 24 + .../com/baeldung/web/rest/LogsResource.java | 39 + .../com/baeldung/web/rest/PostResource.java | 129 ++ .../web/rest/ProfileInfoResource.java | 69 ++ .../baeldung/web/rest/UserJWTController.java | 60 + .../com/baeldung/web/rest/UserResource.java | 187 +++ .../errors/CustomParameterizedException.java | 34 + .../web/rest/errors/ErrorConstants.java | 14 + .../com/baeldung/web/rest/errors/ErrorVM.java | 52 + .../web/rest/errors/ExceptionTranslator.java | 85 ++ .../web/rest/errors/FieldErrorVM.java | 33 + .../web/rest/errors/ParameterizedErrorVM.java | 27 + .../com/baeldung/web/rest/package-info.java | 4 + .../baeldung/web/rest/util/HeaderUtil.java | 45 + .../web/rest/util/PaginationUtil.java | 47 + .../web/rest/vm/KeyAndPasswordVM.java | 27 + .../com/baeldung/web/rest/vm/LoggerVM.java | 48 + .../com/baeldung/web/rest/vm/LoginVM.java | 55 + .../baeldung/web/rest/vm/ManagedUserVM.java | 45 + .../baeldung/web/rest/vm/package-info.java | 4 + .../src/main/resources/.h2.server.properties | 5 + jhipster/src/main/resources/banner.txt | 10 + .../main/resources/config/application-dev.yml | 130 +++ .../resources/config/application-prod.yml | 132 +++ .../src/main/resources/config/application.yml | 106 ++ .../config/liquibase/authorities.csv | 3 + .../00000000000000_initial_schema.xml | 149 +++ .../20170316223211_added_entity_Post.xml | 45 + ...16223211_added_entity_constraints_Post.xml | 18 + .../20170316224021_added_entity_Comment.xml | 41 + ...24021_added_entity_constraints_Comment.xml | 18 + .../resources/config/liquibase/master.xml | 14 + .../main/resources/config/liquibase/users.csv | 5 + .../config/liquibase/users_authorities.csv | 6 + .../main/resources/i18n/messages.properties | 22 + .../resources/i18n/messages_en.properties | 22 + .../src/main/resources/logback-spring.xml | 66 ++ .../main/resources/mails/activationEmail.html | 24 + .../main/resources/mails/creationEmail.html | 24 + .../resources/mails/passwordResetEmail.html | 24 + .../src/main/resources/templates/error.html | 162 +++ jhipster/src/main/webapp/404.html | 60 + .../main/webapp/app/account/account.module.ts | 45 + .../main/webapp/app/account/account.route.ts | 26 + .../account/activate/activate.component.html | 19 + .../account/activate/activate.component.ts | 42 + .../app/account/activate/activate.route.ts | 14 + .../app/account/activate/activate.service.ts | 18 + jhipster/src/main/webapp/app/account/index.ts | 19 + .../password-reset-finish.component.html | 77 ++ .../finish/password-reset-finish.component.ts | 65 ++ .../finish/password-reset-finish.route.ts | 14 + .../finish/password-reset-finish.service.ts | 13 + .../init/password-reset-init.component.html | 47 + .../init/password-reset-init.component.ts | 49 + .../init/password-reset-init.route.ts | 14 + .../init/password-reset-init.service.ts | 13 + .../password-strength-bar.component.ts | 89 ++ .../password/password-strength-bar.scss | 23 + .../account/password/password.component.html | 65 ++ .../account/password/password.component.ts | 49 + .../app/account/password/password.route.ts | 14 + .../app/account/password/password.service.ts | 13 + .../account/register/register.component.html | 122 ++ .../account/register/register.component.ts | 73 ++ .../app/account/register/register.route.ts | 14 + .../app/account/register/register.service.ts | 13 + .../account/settings/settings.component.html | 86 ++ .../account/settings/settings.component.ts | 63 + .../app/account/settings/settings.route.ts | 14 + .../src/main/webapp/app/admin/admin.module.ts | 73 ++ .../src/main/webapp/app/admin/admin.route.ts | 36 + .../app/admin/audits/audit-data.model.ts | 6 + .../webapp/app/admin/audits/audit.model.ts | 10 + .../app/admin/audits/audits.component.html | 45 + .../app/admin/audits/audits.component.ts | 99 ++ .../webapp/app/admin/audits/audits.route.ts | 12 + .../webapp/app/admin/audits/audits.service.ts | 23 + .../configuration.component.html | 46 + .../configuration/configuration.component.ts | 48 + .../configuration/configuration.route.ts | 12 + .../configuration/configuration.service.ts | 53 + .../webapp/app/admin/docs/docs.component.html | 2 + .../webapp/app/admin/docs/docs.component.ts | 14 + .../main/webapp/app/admin/docs/docs.route.ts | 12 + .../admin/health/health-modal.component.html | 36 + .../admin/health/health-modal.component.ts | 37 + .../app/admin/health/health.component.html | 34 + .../app/admin/health/health.component.ts | 69 ++ .../webapp/app/admin/health/health.route.ts | 12 + .../webapp/app/admin/health/health.service.ts | 140 +++ jhipster/src/main/webapp/app/admin/index.ts | 29 + .../main/webapp/app/admin/logs/log.model.ts | 6 + .../webapp/app/admin/logs/logs.component.html | 27 + .../webapp/app/admin/logs/logs.component.ts | 38 + .../main/webapp/app/admin/logs/logs.route.ts | 12 + .../webapp/app/admin/logs/logs.service.ts | 18 + .../metrics/metrics-modal.component.html | 56 + .../admin/metrics/metrics-modal.component.ts | 48 + .../app/admin/metrics/metrics.component.html | 253 ++++ .../app/admin/metrics/metrics.component.ts | 74 ++ .../webapp/app/admin/metrics/metrics.route.ts | 12 + .../app/admin/metrics/metrics.service.ts | 17 + ...er-management-delete-dialog.component.html | 19 + ...user-management-delete-dialog.component.ts | 63 + .../user-management-detail.component.html | 44 + .../user-management-detail.component.ts | 40 + .../user-management-dialog.component.html | 112 ++ .../user-management-dialog.component.ts | 89 ++ .../user-management.component.html | 81 ++ .../user-management.component.ts | 131 +++ .../user-management/user-management.route.ts | 77 ++ .../user-management/user-modal.service.ts | 42 + jhipster/src/main/webapp/app/app.constants.ts | 7 + jhipster/src/main/webapp/app/app.main.ts | 11 + jhipster/src/main/webapp/app/app.module.ts | 58 + jhipster/src/main/webapp/app/app.route.ts | 9 + .../webapp/app/blocks/config/prod.config.ts | 9 + .../blocks/config/uib-pagination.config.ts | 13 + .../interceptor/auth-expired.interceptor.ts | 34 + .../blocks/interceptor/auth.interceptor.ts | 27 + .../interceptor/errorhandler.interceptor.ts | 24 + .../app/blocks/interceptor/http.provider.ts | 45 + .../interceptor/notification.interceptor.ts | 33 + .../comment-delete-dialog.component.html | 19 + .../comment-delete-dialog.component.ts | 67 ++ .../comment/comment-detail.component.html | 35 + .../comment/comment-detail.component.ts | 43 + .../comment/comment-dialog.component.html | 76 ++ .../comment/comment-dialog.component.ts | 107 ++ .../entities/comment/comment-popup.service.ts | 50 + .../entities/comment/comment.component.html | 64 + .../app/entities/comment/comment.component.ts | 110 ++ .../app/entities/comment/comment.model.ts | 10 + .../app/entities/comment/comment.module.ts | 50 + .../app/entities/comment/comment.route.ts | 61 + .../app/entities/comment/comment.service.ts | 78 ++ .../main/webapp/app/entities/comment/index.ts | 8 + .../main/webapp/app/entities/entity.module.ts | 18 + .../main/webapp/app/entities/post/index.ts | 8 + .../post/post-delete-dialog.component.html | 19 + .../post/post-delete-dialog.component.ts | 67 ++ .../entities/post/post-detail.component.html | 37 + .../entities/post/post-detail.component.ts | 43 + .../entities/post/post-dialog.component.html | 96 ++ .../entities/post/post-dialog.component.ts | 107 ++ .../app/entities/post/post-popup.service.ts | 50 + .../app/entities/post/post.component.html | 64 + .../app/entities/post/post.component.ts | 110 ++ .../webapp/app/entities/post/post.model.ts | 11 + .../webapp/app/entities/post/post.module.ts | 52 + .../webapp/app/entities/post/post.route.ts | 61 + .../webapp/app/entities/post/post.service.ts | 78 ++ .../main/webapp/app/home/home.component.html | 42 + .../main/webapp/app/home/home.component.ts | 50 + .../src/main/webapp/app/home/home.module.ts | 23 + .../src/main/webapp/app/home/home.route.ts | 14 + jhipster/src/main/webapp/app/home/home.scss | 26 + jhipster/src/main/webapp/app/home/index.ts | 3 + .../app/layouts/error/error.component.html | 17 + .../app/layouts/error/error.component.ts | 20 + .../webapp/app/layouts/error/error.route.ts | 25 + .../app/layouts/footer/footer.component.html | 4 + .../app/layouts/footer/footer.component.ts | 7 + jhipster/src/main/webapp/app/layouts/index.ts | 10 + .../app/layouts/layout-routing.module.ts | 20 + .../app/layouts/main/main.component.html | 11 + .../webapp/app/layouts/main/main.component.ts | 47 + .../layouts/navbar/active-menu.directive.ts | 26 + .../app/layouts/navbar/navbar.component.html | 168 +++ .../app/layouts/navbar/navbar.component.ts | 81 ++ .../webapp/app/layouts/navbar/navbar.scss | 70 ++ .../layouts/profiles/page-ribbon.component.ts | 25 + .../app/layouts/profiles/page-ribbon.scss | 32 + .../layouts/profiles/profile-info.model.ts | 6 + .../app/layouts/profiles/profile.service.ts | 26 + jhipster/src/main/webapp/app/polyfills.ts | 3 + .../app/shared/alert/alert-error.component.ts | 101 ++ .../app/shared/alert/alert.component.ts | 26 + .../webapp/app/shared/auth/account.service.ts | 16 + .../app/shared/auth/auth-jwt.service.ts | 61 + .../webapp/app/shared/auth/auth.service.ts | 65 ++ .../webapp/app/shared/auth/csrf.service.ts | 13 + .../auth/has-any-authority.directive.ts | 42 + .../app/shared/auth/principal.service.ts | 93 ++ .../app/shared/auth/state-storage.service.ts | 40 + .../shared/auth/user-route-access-service.ts | 15 + .../shared/constants/pagination.constants.ts | 1 + jhipster/src/main/webapp/app/shared/index.ts | 23 + .../app/shared/language/language.constants.ts | 8 + .../app/shared/language/language.helper.ts | 53 + .../app/shared/language/language.pipe.ts | 42 + .../app/shared/login/login-modal.service.ts | 26 + .../app/shared/login/login.component.html | 46 + .../app/shared/login/login.component.ts | 90 ++ .../webapp/app/shared/login/login.service.ts | 45 + .../webapp/app/shared/shared-common.module.ts | 47 + .../webapp/app/shared/shared-libs.module.ts | 27 + .../main/webapp/app/shared/shared.module.ts | 53 + .../webapp/app/shared/user/account.model.ts | 12 + .../main/webapp/app/shared/user/user.model.ts | 44 + .../webapp/app/shared/user/user.service.ts | 45 + jhipster/src/main/webapp/app/vendor.ts | 3 + .../main/webapp/content/images/hipster.png | Bin 0 -> 9499 bytes .../main/webapp/content/images/hipster2x.png | Bin 0 -> 18872 bytes .../webapp/content/images/logo-jhipster.png | Bin 0 -> 4459 bytes .../src/main/webapp/content/scss/global.scss | 211 ++++ .../src/main/webapp/content/scss/vendor.scss | 10 + jhipster/src/main/webapp/favicon.ico | Bin 0 -> 5430 bytes .../src/main/webapp/i18n/en/activate.json | 9 + jhipster/src/main/webapp/i18n/en/audits.json | 27 + jhipster/src/main/webapp/i18n/en/comment.json | 23 + .../main/webapp/i18n/en/configuration.json | 10 + jhipster/src/main/webapp/i18n/en/error.json | 6 + jhipster/src/main/webapp/i18n/en/gateway.json | 15 + jhipster/src/main/webapp/i18n/en/global.json | 133 +++ jhipster/src/main/webapp/i18n/en/health.json | 27 + jhipster/src/main/webapp/i18n/en/home.json | 19 + jhipster/src/main/webapp/i18n/en/login.json | 19 + jhipster/src/main/webapp/i18n/en/logs.json | 11 + jhipster/src/main/webapp/i18n/en/metrics.json | 101 ++ .../src/main/webapp/i18n/en/password.json | 12 + jhipster/src/main/webapp/i18n/en/post.json | 24 + .../src/main/webapp/i18n/en/register.json | 24 + jhipster/src/main/webapp/i18n/en/reset.json | 27 + .../src/main/webapp/i18n/en/sessions.json | 15 + .../src/main/webapp/i18n/en/settings.json | 32 + .../main/webapp/i18n/en/user-management.json | 30 + jhipster/src/main/webapp/index.html | 27 + jhipster/src/main/webapp/robots.txt | 11 + .../webapp/swagger-ui/images/throbber.gif | Bin 0 -> 9257 bytes .../src/main/webapp/swagger-ui/index.html | 177 +++ jhipster/src/test/gatling/conf/gatling.conf | 131 +++ jhipster/src/test/gatling/conf/logback.xml | 22 + .../simulations/CommentGatlingTest.scala | 92 ++ .../gatling/simulations/PostGatlingTest.scala | 92 ++ .../security/SecurityUtilsUnitTest.java | 50 + .../security/jwt/TokenProviderTest.java | 107 ++ .../baeldung/service/UserServiceIntTest.java | 129 ++ .../web/rest/AccountResourceIntTest.java | 395 +++++++ .../web/rest/AuditResourceIntTest.java | 147 +++ .../web/rest/CommentResourceIntTest.java | 279 +++++ .../web/rest/LogsResourceIntTest.java | 59 + .../web/rest/PostResourceIntTest.java | 306 +++++ .../web/rest/ProfileInfoResourceIntTest.java | 86 ++ .../java/com/baeldung/web/rest/TestUtil.java | 120 ++ .../web/rest/UserResourceIntTest.java | 522 +++++++++ .../javascript/e2e/account/account.spec.ts | 108 ++ .../e2e/admin/administration.spec.ts | 80 ++ .../javascript/e2e/entities/comment.spec.ts | 49 + .../test/javascript/e2e/entities/post.spec.ts | 49 + jhipster/src/test/javascript/karma.conf.js | 126 ++ .../src/test/javascript/protractor.conf.js | 48 + .../activate/activate.component.spec.ts | 84 ++ .../password-reset-finish.component.spec.ts | 79 ++ .../password-reset-init.component.spec.ts | 115 ++ .../password-strength-bar.component.spec.ts | 53 + .../password/password.component.spec.ts | 92 ++ .../register/register.component.spec.ts | 138 +++ .../settings/settings.component.spec.ts | 103 ++ .../app/admin/audits/audits.component.spec.ts | 82 ++ .../app/admin/health/health.component.spec.ts | 295 +++++ .../comment/comment-detail.component.spec.ts | 79 ++ .../post/post-detail.component.spec.ts | 79 ++ jhipster/src/test/javascript/spec/entry.ts | 19 + .../spec/helpers/mock-account.service.ts | 26 + .../spec/helpers/mock-language.service.ts | 26 + .../spec/helpers/mock-principal.service.ts | 20 + .../spec/helpers/mock-route.service.ts | 15 + .../test/javascript/spec/helpers/spyobject.ts | 69 ++ .../src/test/javascript/spec/test.module.ts | 24 + .../src/test/resources/config/application.yml | 96 ++ jhipster/src/test/resources/logback-test.xml | 15 + jhipster/tsconfig.json | 22 + jhipster/tslint.json | 107 ++ jhipster/webpack/webpack.common.js | 117 ++ jhipster/webpack/webpack.dev.js | 65 ++ jhipster/webpack/webpack.prod.js | 22 + jhipster/webpack/webpack.vendor.js | 63 + .../ArgumentMatcherWithoutLambdaUnitTest.java | 20 +- .../CustomAnswerWithoutLambdaUnitTest.java | 25 +- pom.xml | 1 + 361 files changed, 20698 insertions(+), 21 deletions(-) create mode 100644 jhipster/.editorconfig create mode 100644 jhipster/.gitattributes create mode 100644 jhipster/.gitignore create mode 100644 jhipster/.gitlab-ci.yml create mode 100644 jhipster/.jhipster/Comment.json create mode 100644 jhipster/.jhipster/Post.json create mode 100644 jhipster/.mvn/wrapper/maven-wrapper.jar create mode 100644 jhipster/.mvn/wrapper/maven-wrapper.properties create mode 100644 jhipster/.travis.yml create mode 100644 jhipster/.yo-rc.json create mode 100644 jhipster/Jenkinsfile create mode 100644 jhipster/README.md create mode 100644 jhipster/angular-cli.json create mode 100644 jhipster/circle.yml create mode 100755 jhipster/mvnw create mode 100644 jhipster/mvnw.cmd create mode 100644 jhipster/package.json create mode 100644 jhipster/pom.xml create mode 100644 jhipster/postcss.config.js create mode 100644 jhipster/src/main/docker/Dockerfile create mode 100644 jhipster/src/main/docker/app.yml create mode 100644 jhipster/src/main/docker/mysql.yml create mode 100644 jhipster/src/main/docker/sonar.yml create mode 100644 jhipster/src/main/java/com/baeldung/ApplicationWebXml.java create mode 100644 jhipster/src/main/java/com/baeldung/BaeldungApp.java create mode 100644 jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java create mode 100644 jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java create mode 100644 jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/Constants.java create mode 100644 jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java create mode 100644 jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java create mode 100644 jhipster/src/main/java/com/baeldung/config/WebConfigurer.java create mode 100644 jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java create mode 100644 jhipster/src/main/java/com/baeldung/config/audit/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/config/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/Authority.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/Comment.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/Post.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/User.java create mode 100644 jhipster/src/main/java/com/baeldung/domain/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/CommentRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/PostRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/UserRepository.java create mode 100644 jhipster/src/main/java/com/baeldung/repository/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java create mode 100644 jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java create mode 100644 jhipster/src/main/java/com/baeldung/security/SecurityUtils.java create mode 100644 jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java create mode 100644 jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java create mode 100644 jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java create mode 100644 jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java create mode 100644 jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java create mode 100644 jhipster/src/main/java/com/baeldung/security/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/service/AuditEventService.java create mode 100644 jhipster/src/main/java/com/baeldung/service/MailService.java create mode 100644 jhipster/src/main/java/com/baeldung/service/UserService.java create mode 100644 jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java create mode 100644 jhipster/src/main/java/com/baeldung/service/dto/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java create mode 100644 jhipster/src/main/java/com/baeldung/service/mapper/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/service/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/PostResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/UserResource.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/package-info.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java create mode 100644 jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java create mode 100644 jhipster/src/main/resources/.h2.server.properties create mode 100644 jhipster/src/main/resources/banner.txt create mode 100644 jhipster/src/main/resources/config/application-dev.yml create mode 100644 jhipster/src/main/resources/config/application-prod.yml create mode 100644 jhipster/src/main/resources/config/application.yml create mode 100644 jhipster/src/main/resources/config/liquibase/authorities.csv create mode 100644 jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml create mode 100644 jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml create mode 100644 jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml create mode 100644 jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml create mode 100644 jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml create mode 100644 jhipster/src/main/resources/config/liquibase/master.xml create mode 100644 jhipster/src/main/resources/config/liquibase/users.csv create mode 100644 jhipster/src/main/resources/config/liquibase/users_authorities.csv create mode 100644 jhipster/src/main/resources/i18n/messages.properties create mode 100644 jhipster/src/main/resources/i18n/messages_en.properties create mode 100644 jhipster/src/main/resources/logback-spring.xml create mode 100644 jhipster/src/main/resources/mails/activationEmail.html create mode 100644 jhipster/src/main/resources/mails/creationEmail.html create mode 100644 jhipster/src/main/resources/mails/passwordResetEmail.html create mode 100644 jhipster/src/main/resources/templates/error.html create mode 100644 jhipster/src/main/webapp/404.html create mode 100644 jhipster/src/main/webapp/app/account/account.module.ts create mode 100644 jhipster/src/main/webapp/app/account/account.route.ts create mode 100644 jhipster/src/main/webapp/app/account/activate/activate.component.html create mode 100644 jhipster/src/main/webapp/app/account/activate/activate.component.ts create mode 100644 jhipster/src/main/webapp/app/account/activate/activate.route.ts create mode 100644 jhipster/src/main/webapp/app/account/activate/activate.service.ts create mode 100644 jhipster/src/main/webapp/app/account/index.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html create mode 100644 jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html create mode 100644 jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts create mode 100644 jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts create mode 100644 jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts create mode 100644 jhipster/src/main/webapp/app/account/password/password-strength-bar.scss create mode 100644 jhipster/src/main/webapp/app/account/password/password.component.html create mode 100644 jhipster/src/main/webapp/app/account/password/password.component.ts create mode 100644 jhipster/src/main/webapp/app/account/password/password.route.ts create mode 100644 jhipster/src/main/webapp/app/account/password/password.service.ts create mode 100644 jhipster/src/main/webapp/app/account/register/register.component.html create mode 100644 jhipster/src/main/webapp/app/account/register/register.component.ts create mode 100644 jhipster/src/main/webapp/app/account/register/register.route.ts create mode 100644 jhipster/src/main/webapp/app/account/register/register.service.ts create mode 100644 jhipster/src/main/webapp/app/account/settings/settings.component.html create mode 100644 jhipster/src/main/webapp/app/account/settings/settings.component.ts create mode 100644 jhipster/src/main/webapp/app/account/settings/settings.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/admin.module.ts create mode 100644 jhipster/src/main/webapp/app/admin/admin.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts create mode 100644 jhipster/src/main/webapp/app/admin/audits/audit.model.ts create mode 100644 jhipster/src/main/webapp/app/admin/audits/audits.component.html create mode 100644 jhipster/src/main/webapp/app/admin/audits/audits.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/audits/audits.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/audits/audits.service.ts create mode 100644 jhipster/src/main/webapp/app/admin/configuration/configuration.component.html create mode 100644 jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts create mode 100644 jhipster/src/main/webapp/app/admin/docs/docs.component.html create mode 100644 jhipster/src/main/webapp/app/admin/docs/docs.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/docs/docs.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/health/health-modal.component.html create mode 100644 jhipster/src/main/webapp/app/admin/health/health-modal.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/health/health.component.html create mode 100644 jhipster/src/main/webapp/app/admin/health/health.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/health/health.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/health/health.service.ts create mode 100644 jhipster/src/main/webapp/app/admin/index.ts create mode 100644 jhipster/src/main/webapp/app/admin/logs/log.model.ts create mode 100644 jhipster/src/main/webapp/app/admin/logs/logs.component.html create mode 100644 jhipster/src/main/webapp/app/admin/logs/logs.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/logs/logs.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/logs/logs.service.ts create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics.component.html create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management.component.html create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts create mode 100644 jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts create mode 100644 jhipster/src/main/webapp/app/app.constants.ts create mode 100644 jhipster/src/main/webapp/app/app.main.ts create mode 100644 jhipster/src/main/webapp/app/app.module.ts create mode 100644 jhipster/src/main/webapp/app/app.route.ts create mode 100644 jhipster/src/main/webapp/app/blocks/config/prod.config.ts create mode 100644 jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts create mode 100644 jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts create mode 100644 jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts create mode 100644 jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts create mode 100644 jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts create mode 100644 jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.component.html create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.model.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.module.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.route.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/comment.service.ts create mode 100644 jhipster/src/main/webapp/app/entities/comment/index.ts create mode 100644 jhipster/src/main/webapp/app/entities/entity.module.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/index.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post-detail.component.html create mode 100644 jhipster/src/main/webapp/app/entities/post/post-detail.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post-dialog.component.html create mode 100644 jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post-popup.service.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post.component.html create mode 100644 jhipster/src/main/webapp/app/entities/post/post.component.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post.model.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post.module.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post.route.ts create mode 100644 jhipster/src/main/webapp/app/entities/post/post.service.ts create mode 100644 jhipster/src/main/webapp/app/home/home.component.html create mode 100644 jhipster/src/main/webapp/app/home/home.component.ts create mode 100644 jhipster/src/main/webapp/app/home/home.module.ts create mode 100644 jhipster/src/main/webapp/app/home/home.route.ts create mode 100644 jhipster/src/main/webapp/app/home/home.scss create mode 100644 jhipster/src/main/webapp/app/home/index.ts create mode 100644 jhipster/src/main/webapp/app/layouts/error/error.component.html create mode 100644 jhipster/src/main/webapp/app/layouts/error/error.component.ts create mode 100644 jhipster/src/main/webapp/app/layouts/error/error.route.ts create mode 100644 jhipster/src/main/webapp/app/layouts/footer/footer.component.html create mode 100644 jhipster/src/main/webapp/app/layouts/footer/footer.component.ts create mode 100644 jhipster/src/main/webapp/app/layouts/index.ts create mode 100644 jhipster/src/main/webapp/app/layouts/layout-routing.module.ts create mode 100644 jhipster/src/main/webapp/app/layouts/main/main.component.html create mode 100644 jhipster/src/main/webapp/app/layouts/main/main.component.ts create mode 100644 jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts create mode 100644 jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html create mode 100644 jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts create mode 100644 jhipster/src/main/webapp/app/layouts/navbar/navbar.scss create mode 100644 jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts create mode 100644 jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss create mode 100644 jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts create mode 100644 jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts create mode 100644 jhipster/src/main/webapp/app/polyfills.ts create mode 100644 jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts create mode 100644 jhipster/src/main/webapp/app/shared/alert/alert.component.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/account.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/auth.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/csrf.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/principal.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts create mode 100644 jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts create mode 100644 jhipster/src/main/webapp/app/shared/index.ts create mode 100644 jhipster/src/main/webapp/app/shared/language/language.constants.ts create mode 100644 jhipster/src/main/webapp/app/shared/language/language.helper.ts create mode 100644 jhipster/src/main/webapp/app/shared/language/language.pipe.ts create mode 100644 jhipster/src/main/webapp/app/shared/login/login-modal.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/login/login.component.html create mode 100644 jhipster/src/main/webapp/app/shared/login/login.component.ts create mode 100644 jhipster/src/main/webapp/app/shared/login/login.service.ts create mode 100644 jhipster/src/main/webapp/app/shared/shared-common.module.ts create mode 100644 jhipster/src/main/webapp/app/shared/shared-libs.module.ts create mode 100644 jhipster/src/main/webapp/app/shared/shared.module.ts create mode 100644 jhipster/src/main/webapp/app/shared/user/account.model.ts create mode 100644 jhipster/src/main/webapp/app/shared/user/user.model.ts create mode 100644 jhipster/src/main/webapp/app/shared/user/user.service.ts create mode 100644 jhipster/src/main/webapp/app/vendor.ts create mode 100644 jhipster/src/main/webapp/content/images/hipster.png create mode 100644 jhipster/src/main/webapp/content/images/hipster2x.png create mode 100644 jhipster/src/main/webapp/content/images/logo-jhipster.png create mode 100644 jhipster/src/main/webapp/content/scss/global.scss create mode 100644 jhipster/src/main/webapp/content/scss/vendor.scss create mode 100644 jhipster/src/main/webapp/favicon.ico create mode 100644 jhipster/src/main/webapp/i18n/en/activate.json create mode 100644 jhipster/src/main/webapp/i18n/en/audits.json create mode 100644 jhipster/src/main/webapp/i18n/en/comment.json create mode 100644 jhipster/src/main/webapp/i18n/en/configuration.json create mode 100644 jhipster/src/main/webapp/i18n/en/error.json create mode 100644 jhipster/src/main/webapp/i18n/en/gateway.json create mode 100644 jhipster/src/main/webapp/i18n/en/global.json create mode 100644 jhipster/src/main/webapp/i18n/en/health.json create mode 100644 jhipster/src/main/webapp/i18n/en/home.json create mode 100644 jhipster/src/main/webapp/i18n/en/login.json create mode 100644 jhipster/src/main/webapp/i18n/en/logs.json create mode 100644 jhipster/src/main/webapp/i18n/en/metrics.json create mode 100644 jhipster/src/main/webapp/i18n/en/password.json create mode 100644 jhipster/src/main/webapp/i18n/en/post.json create mode 100644 jhipster/src/main/webapp/i18n/en/register.json create mode 100644 jhipster/src/main/webapp/i18n/en/reset.json create mode 100644 jhipster/src/main/webapp/i18n/en/sessions.json create mode 100644 jhipster/src/main/webapp/i18n/en/settings.json create mode 100644 jhipster/src/main/webapp/i18n/en/user-management.json create mode 100644 jhipster/src/main/webapp/index.html create mode 100644 jhipster/src/main/webapp/robots.txt create mode 100644 jhipster/src/main/webapp/swagger-ui/images/throbber.gif create mode 100644 jhipster/src/main/webapp/swagger-ui/index.html create mode 100644 jhipster/src/test/gatling/conf/gatling.conf create mode 100644 jhipster/src/test/gatling/conf/logback.xml create mode 100644 jhipster/src/test/gatling/simulations/CommentGatlingTest.scala create mode 100644 jhipster/src/test/gatling/simulations/PostGatlingTest.scala create mode 100644 jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java create mode 100644 jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java create mode 100644 jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java create mode 100644 jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java create mode 100644 jhipster/src/test/javascript/e2e/account/account.spec.ts create mode 100644 jhipster/src/test/javascript/e2e/admin/administration.spec.ts create mode 100644 jhipster/src/test/javascript/e2e/entities/comment.spec.ts create mode 100644 jhipster/src/test/javascript/e2e/entities/post.spec.ts create mode 100644 jhipster/src/test/javascript/karma.conf.js create mode 100644 jhipster/src/test/javascript/protractor.conf.js create mode 100644 jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts create mode 100644 jhipster/src/test/javascript/spec/entry.ts create mode 100644 jhipster/src/test/javascript/spec/helpers/mock-account.service.ts create mode 100644 jhipster/src/test/javascript/spec/helpers/mock-language.service.ts create mode 100644 jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts create mode 100644 jhipster/src/test/javascript/spec/helpers/mock-route.service.ts create mode 100644 jhipster/src/test/javascript/spec/helpers/spyobject.ts create mode 100644 jhipster/src/test/javascript/spec/test.module.ts create mode 100644 jhipster/src/test/resources/config/application.yml create mode 100644 jhipster/src/test/resources/logback-test.xml create mode 100644 jhipster/tsconfig.json create mode 100644 jhipster/tslint.json create mode 100644 jhipster/webpack/webpack.common.js create mode 100644 jhipster/webpack/webpack.dev.js create mode 100644 jhipster/webpack/webpack.prod.js create mode 100644 jhipster/webpack/webpack.vendor.js diff --git a/jhipster/.editorconfig b/jhipster/.editorconfig new file mode 100644 index 0000000000..a03599dd04 --- /dev/null +++ b/jhipster/.editorconfig @@ -0,0 +1,24 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[{package,bower}.json] +indent_style = space +indent_size = 2 diff --git a/jhipster/.gitattributes b/jhipster/.gitattributes new file mode 100644 index 0000000000..2a13efa88f --- /dev/null +++ b/jhipster/.gitattributes @@ -0,0 +1,22 @@ +# All text files should have the "lf" (Unix) line endings +* text eol=lf + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.java text +*.js text +*.css text +*.html text + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.jar binary +*.pdf binary +*.eot binary +*.ttf binary +*.gzip binary +*.gz binary +*.ai binary +*.eps binary +*.swf binary diff --git a/jhipster/.gitignore b/jhipster/.gitignore new file mode 100644 index 0000000000..c9f735a496 --- /dev/null +++ b/jhipster/.gitignore @@ -0,0 +1,143 @@ +###################### +# Project Specific +###################### +/src/main/webapp/content/css/main.css +/target/www/** +/src/test/javascript/coverage/ +/src/test/javascript/PhantomJS*/ + +###################### +# Node +###################### +/node/ +node_tmp/ +node_modules/ +npm-debug.log.* + +###################### +# SASS +###################### +.sass-cache/ + +###################### +# Eclipse +###################### +*.pydevproject +.project +.metadata +tmp/ +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath +.factorypath +/src/main/resources/rebel.xml + +# External tool builders +.externalToolBuilders/** + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +###################### +# Intellij +###################### +.idea/ +*.iml +*.iws +*.ipr +*.ids +*.orig + +###################### +# Visual Studio Code +###################### +.vscode/ + +###################### +# Maven +###################### +/log/ +/target/ + +###################### +# Gradle +###################### +.gradle/ +/build/ + +###################### +# Package Files +###################### +*.jar +*.war +*.ear +*.db + +###################### +# Windows +###################### +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +###################### +# Mac OSX +###################### +.DS_Store +.svn + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +###################### +# Directories +###################### +/bin/ +/deploy/ + +###################### +# Logs +###################### +*.log + +###################### +# Others +###################### +*.class +*.*~ +*~ +.merge_file* + +###################### +# Gradle Wrapper +###################### +!gradle/wrapper/gradle-wrapper.jar + +###################### +# Maven Wrapper +###################### +!.mvn/wrapper/maven-wrapper.jar + +###################### +# ESLint +###################### +.eslintcache +/.apt_generated/ diff --git a/jhipster/.gitlab-ci.yml b/jhipster/.gitlab-ci.yml new file mode 100644 index 0000000000..1cf574251a --- /dev/null +++ b/jhipster/.gitlab-ci.yml @@ -0,0 +1,56 @@ + +cache: + key: "$CI_BUILD_REF_NAME" + paths: + - node_modules + - .maven +stages: + - build + - test + - package + +before_script: + - export MAVEN_USER_HOME=`pwd`/.maven + - chmod +x mvnw + - ./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0 + - ./mvnw com.github.eirslett:frontend-maven-plugin:npm + +maven-build: + stage: build + script: ./mvnw compile -Dmaven.repo.local=$MAVEN_USER_HOME + +maven-test: + stage: test + script: + - ./mvnw test -Dmaven.repo.local=$MAVEN_USER_HOME + artifacts: + paths: + - target/surefire-reports/* +maven-front-test: + stage: test + script: + - ./mvnw com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.yarn.arguments=test + artifacts: + paths: + - target/test-results/karma/* +gatling-test: + stage: test + allow_failure: true + script: + - ./mvnw gatling:execute -Dmaven.repo.local=$MAVEN_USER_HOME + before_script: + - export MAVEN_USER_HOME=`pwd`/.maven + - chmod +x mvnw + - ./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0 + - ./mvnw com.github.eirslett:frontend-maven-plugin:npm + - ./mvnw & + artifacts: + paths: + - target/gatling/* +maven-package: + stage: package + script: + - ./mvnw package -Pprod -DskipTests -Dmaven.repo.local=$MAVEN_USER_HOME + artifacts: + paths: + - target/*.war diff --git a/jhipster/.jhipster/Comment.json b/jhipster/.jhipster/Comment.json new file mode 100644 index 0000000000..c6022daa60 --- /dev/null +++ b/jhipster/.jhipster/Comment.json @@ -0,0 +1,39 @@ +{ + "fluentMethods": true, + "relationships": [ + { + "relationshipName": "post", + "otherEntityName": "post", + "relationshipType": "many-to-one", + "relationshipValidateRules": [ + "required" + ], + "otherEntityField": "title" + } + ], + "fields": [ + { + "fieldName": "text", + "fieldType": "String", + "fieldValidateRules": [ + "required", + "minlength", + "maxlength" + ], + "fieldValidateRulesMinlength": "10", + "fieldValidateRulesMaxlength": "100" + }, + { + "fieldName": "creationDate", + "fieldType": "LocalDate", + "fieldValidateRules": [ + "required" + ] + } + ], + "changelogDate": "20170316224021", + "dto": "no", + "service": "no", + "entityTableName": "comment", + "pagination": "infinite-scroll" +} diff --git a/jhipster/.jhipster/Post.json b/jhipster/.jhipster/Post.json new file mode 100644 index 0000000000..595cf43598 --- /dev/null +++ b/jhipster/.jhipster/Post.json @@ -0,0 +1,52 @@ +{ + "fluentMethods": true, + "relationships": [ + { + "relationshipName": "creator", + "otherEntityName": "user", + "relationshipType": "many-to-one", + "relationshipValidateRules": [ + "required" + ], + "otherEntityField": "login", + "ownerSide": true, + "otherEntityRelationshipName": "post" + } + ], + "fields": [ + { + "fieldName": "title", + "fieldType": "String", + "fieldValidateRules": [ + "required", + "minlength", + "maxlength" + ], + "fieldValidateRulesMinlength": "10", + "fieldValidateRulesMaxlength": "100" + }, + { + "fieldName": "content", + "fieldType": "String", + "fieldValidateRules": [ + "required", + "minlength", + "maxlength" + ], + "fieldValidateRulesMinlength": "10", + "fieldValidateRulesMaxlength": "1000" + }, + { + "fieldName": "creationDate", + "fieldType": "LocalDate", + "fieldValidateRules": [ + "required" + ] + } + ], + "changelogDate": "20170316223211", + "dto": "no", + "service": "no", + "entityTableName": "post", + "pagination": "infinite-scroll" +} diff --git a/jhipster/.mvn/wrapper/maven-wrapper.jar b/jhipster/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/jhipster/.mvn/wrapper/maven-wrapper.properties b/jhipster/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..c954cec91c --- /dev/null +++ b/jhipster/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/jhipster/.travis.yml b/jhipster/.travis.yml new file mode 100644 index 0000000000..c34d1a7da6 --- /dev/null +++ b/jhipster/.travis.yml @@ -0,0 +1,41 @@ +os: + - linux +services: + - docker +language: node_js +node_js: + - "6.10.0" +jdk: + - oraclejdk8 +sudo: false +cache: + directories: + - node + - node_modules + - $HOME/.m2 +env: + global: + - NODE_VERSION=6.10.0 + - SPRING_OUTPUT_ANSI_ENABLED=ALWAYS + - SPRING_JPA_SHOW_SQL=false +before_install: + - jdk_switcher use oraclejdk8 + - java -version + - sudo /etc/init.d/mysql stop + - sudo /etc/init.d/postgresql stop + - nvm install $NODE_VERSION + - npm install -g npm + - node -v + - npm -v +install: + - npm install +script: + - chmod +x mvnw + - ./mvnw clean test + - npm test + - ./mvnw package -Pprod -DskipTests +notifications: + webhooks: + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/jhipster/.yo-rc.json b/jhipster/.yo-rc.json new file mode 100644 index 0000000000..155e33b1c5 --- /dev/null +++ b/jhipster/.yo-rc.json @@ -0,0 +1,36 @@ +{ + "generator-jhipster": { + "jhipsterVersion": "4.0.8", + "baseName": "baeldung", + "packageName": "com.baeldung", + "packageFolder": "com/baeldung", + "serverPort": "8080", + "authenticationType": "jwt", + "hibernateCache": "ehcache", + "clusteredHttpSession": false, + "websocket": false, + "databaseType": "sql", + "devDatabaseType": "h2Disk", + "prodDatabaseType": "mysql", + "searchEngine": false, + "messageBroker": false, + "serviceDiscoveryType": false, + "buildTool": "maven", + "enableSocialSignIn": false, + "jwtSecretKey": "e1d4b69d3f953e3fa622121e882e6f459ca20ca4", + "clientFramework": "angular2", + "useSass": true, + "clientPackageManager": "npm", + "applicationType": "monolith", + "testFrameworks": [ + "gatling", + "protractor" + ], + "jhiPrefix": "jhi", + "enableTranslation": true, + "nativeLanguage": "en", + "languages": [ + "en" + ] + } +} \ No newline at end of file diff --git a/jhipster/Jenkinsfile b/jhipster/Jenkinsfile new file mode 100644 index 0000000000..1f0873a472 --- /dev/null +++ b/jhipster/Jenkinsfile @@ -0,0 +1,50 @@ +#!/usr/bin/env groovy + +node { + stage('checkout') { + checkout scm + } + + stage('check java') { + sh "java -version" + } + + stage('clean') { + sh "chmod +x mvnw" + sh "./mvnw clean" + } + + stage('install tools') { + sh "./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0" + } + + stage('npm install') { + sh "./mvnw com.github.eirslett:frontend-maven-plugin:npm" + } + + stage('backend tests') { + try { + sh "./mvnw test" + } catch(err) { + throw err + } finally { + junit '**/target/surefire-reports/TEST-*.xml' + } + } + + stage('frontend tests') { + try { + sh "./mvnw com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.yarn.arguments=test" + } catch(err) { + throw err + } finally { + junit '**/target/test-results/karma/TESTS-*.xml' + } + } + + stage('packaging') { + sh "./mvnw package -Pprod -DskipTests" + archiveArtifacts artifacts: '**/target/*.war', fingerprint: true + } + +} diff --git a/jhipster/README.md b/jhipster/README.md new file mode 100644 index 0000000000..d45796d867 --- /dev/null +++ b/jhipster/README.md @@ -0,0 +1,153 @@ +# baeldung +This application was generated using JHipster 4.0.8, you can find documentation and help at [https://jhipster.github.io/documentation-archive/v4.0.8](https://jhipster.github.io/documentation-archive/v4.0.8). + +## Development + +Before you can build this project, you must install and configure the following dependencies on your machine: + +1. [Node.js][]: We use Node to run a development web server and build the project. + Depending on your system, you can install Node either from source or as a pre-packaged bundle. + +After installing Node, you should be able to run the following command to install development tools. +You will only need to run this command when dependencies change in [package.json](package.json). + + npm install + +We use npm scripts and [Webpack][] as our build system. + + +Run the following commands in two separate terminals to create a blissful development experience where your browser +auto-refreshes when files change on your hard drive. + + ./mvnw + npm start + +[Npm][] is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by +specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies. +Add the `help` flag on any command to see how you can use it. For example, `npm help update`. + +The `npm run` command will list all of the scripts available to run for this project. + +### Managing dependencies + +For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command: + + npm install --save --save-exact leaflet + +To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command: + + npm install --save-dev --save-exact @types/leaflet + +Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them: + +Edit [src/main/webapp/app/vendor.ts](src/main/webapp/app/vendor.ts) file: +~~~ +import 'leaflet/dist/leaflet.js'; +~~~ + +Edit [src/main/webapp/content/css/vendor.css](src/main/webapp/content/css/vendor.css) file: +~~~ +@import '~leaflet/dist/leaflet.css'; +~~~ + +Note: there are still few other things remaining to do for Leaflet that we won't detail here. + +For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][]. + +### Using angular-cli + +You can also use [Angular CLI][] to generate some custom client code. + +For example, the following command: + + ng generate component my-component + +will generate few files: + + create src/main/webapp/app/my-component/my-component.component.html + create src/main/webapp/app/my-component/my-component.component.ts + update src/main/webapp/app/app.module.ts + +## Building for production + +To optimize the baeldung application for production, run: + + ./mvnw -Pprod clean package + +This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files. +To ensure everything worked, run: + + java -jar target/*.war + +Then navigate to [http://localhost:8080](http://localhost:8080) in your browser. + +Refer to [Using JHipster in production][] for more details. + +## Testing + +To launch your application's tests, run: + + ./mvnw clean test + +### Client tests + +Unit tests are run by [Karma][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with: + + npm test + +UI end-to-end tests are powered by [Protractor][], which is built on top of WebDriverJS. They're located in [src/test/javascript/e2e](src/test/javascript/e2e) +and can be run by starting Spring Boot in one terminal (`./mvnw spring-boot:run`) and running the tests (`gulp itest`) in a second one. +### Other tests + +Performance tests are run by [Gatling][] and written in Scala. They're located in [src/test/gatling](src/test/gatling) and can be run with: + + ./mvnw gatling:execute + +For more information, refer to the [Running tests page][]. + +## Using Docker to simplify development (optional) + +You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services. +For example, to start a mysql database in a docker container, run: + + docker-compose -f src/main/docker/mysql.yml up -d + +To stop it and remove the container, run: + + docker-compose -f src/main/docker/mysql.yml down + +You can also fully dockerize your application and all the services that it depends on. +To achieve this, first build a docker image of your app by running: + + ./mvnw package -Pprod docker:build + +Then run: + + docker-compose -f src/main/docker/app.yml up -d + +For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`yo jhipster:docker-compose`), which is able to generate docker configurations for one or several JHipster applications. + +## Continuous Integration (optional) + +To configure CI for your project, run the ci-cd sub-generator (`yo jhipster:ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information. + +[JHipster Homepage and latest documentation]: https://jhipster.github.io +[JHipster 4.0.8 archive]: https://jhipster.github.io/documentation-archive/v4.0.8 + +[Using JHipster in development]: https://jhipster.github.io/documentation-archive/v4.0.8/development/ +[Using Docker and Docker-Compose]: https://jhipster.github.io/documentation-archive/v4.0.8/docker-compose +[Using JHipster in production]: https://jhipster.github.io/documentation-archive/v4.0.8/production/ +[Running tests page]: https://jhipster.github.io/documentation-archive/v4.0.8/running-tests/ +[Setting up Continuous Integration]: https://jhipster.github.io/documentation-archive/v4.0.8/setting-up-ci/ + +[Gatling]: http://gatling.io/ +[Node.js]: https://nodejs.org/ +[Yarn]: https://yarnpkg.org/ +[Webpack]: https://webpack.github.io/ +[Angular CLI]: https://cli.angular.io/ +[BrowserSync]: http://www.browsersync.io/ +[Karma]: http://karma-runner.github.io/ +[Jasmine]: http://jasmine.github.io/2.0/introduction.html +[Protractor]: https://angular.github.io/protractor/ +[Leaflet]: http://leafletjs.com/ +[DefinitelyTyped]: http://definitelytyped.org/ diff --git a/jhipster/angular-cli.json b/jhipster/angular-cli.json new file mode 100644 index 0000000000..15558a2fae --- /dev/null +++ b/jhipster/angular-cli.json @@ -0,0 +1,55 @@ +{ + "project": { + "version": "1.0.0-beta.24", + "name": "baeldung" + }, + "apps": [ + { + "root": "src/main/webapp/", + "outDir": "target/www/app", + "assets": [ + "content", + "favicon.ico" + ], + "index": "index.html", + "main": "app/app.main.ts", + "test": "", + "tsconfig": "../../../tsconfig.json", + "prefix": "jhi", + "mobile": false, + "styles": [ + "content/css/main.css" + ], + "scripts": [], + "environments": {} + } + ], + "addons": [], + "packages": [], + "e2e": { + "protractor": { + "config": "src/test/javascript/protractor.conf.js" + } + }, + "test": { + "karma": { + "config": "src/test/javascript/karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "prefixInterfaces": false, + "inline": { + "style": true, + "template": false + }, + "spec": { + "class": false, + "component": false, + "directive": false, + "module": false, + "pipe": false, + "service": false + } + } +} diff --git a/jhipster/circle.yml b/jhipster/circle.yml new file mode 100644 index 0000000000..bc9371e94f --- /dev/null +++ b/jhipster/circle.yml @@ -0,0 +1,25 @@ +machine: + services: + - docker + java: + version: oraclejdk8 + node: + version: 6.10.0 +dependencies: + cache_directories: + - node + - node_modules + - ~/.m2 + override: + - java -version + - npm install -g npm + - node -v + - npm -v + - java -version + - npm install +test: + override: + - chmod +x mvnw + - ./mvnw clean test + - npm test + - ./mvnw package -Pprod -DskipTests diff --git a/jhipster/mvnw b/jhipster/mvnw new file mode 100755 index 0000000000..a1ba1bf554 --- /dev/null +++ b/jhipster/mvnw @@ -0,0 +1,233 @@ +#!/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 +# +# http://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 + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 Migwn, 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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +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} "$@" diff --git a/jhipster/mvnw.cmd b/jhipster/mvnw.cmd new file mode 100644 index 0000000000..2b934e89dd --- /dev/null +++ b/jhipster/mvnw.cmd @@ -0,0 +1,145 @@ +@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 http://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 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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +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% \ No newline at end of file diff --git a/jhipster/package.json b/jhipster/package.json new file mode 100644 index 0000000000..40bbd28799 --- /dev/null +++ b/jhipster/package.json @@ -0,0 +1,112 @@ +{ + "name": "baeldung", + "version": "0.0.0", + "description": "Description for baeldung", + "private": true, + "cacheDirectories": [ + "node_modules" + ], + "dependencies": { + "@angular/common": "2.4.7", + "@angular/compiler": "2.4.7", + "@angular/core": "2.4.7", + "@angular/forms": "2.4.7", + "@angular/http": "2.4.7", + "@angular/platform-browser": "2.4.7", + "@angular/platform-browser-dynamic": "2.4.7", + "@angular/router": "3.4.7", + "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.20", + "angular2-infinite-scroll": "0.3.0", + "bootstrap": "4.0.0-alpha.6", + "font-awesome": "4.7.0", + "angular2-cookie": "1.2.6", + "core-js": "2.4.1", + "jquery": "3.1.1", + "ng-jhipster": "0.1.9", + "ng2-webstorage": "1.5.0", + "reflect-metadata": "0.1.9", + "rxjs": "5.1.0", + "swagger-ui": "2.2.10", + "tether": "1.4.0", + "zone.js": "0.7.6" + }, + "devDependencies": { + "@angular/cli": "1.0.0-beta.28.3", + "@types/jasmine": "2.5.42", + "@types/node": "7.0.5", + "@types/selenium-webdriver": "2.53.39", + "add-asset-html-webpack-plugin": "1.0.2", + "angular2-template-loader": "0.6.2", + "awesome-typescript-loader": "3.0.7", + "browser-sync": "2.18.7", + "browser-sync-webpack-plugin": "1.1.4", + "codelyzer": "2.0.0", + "copy-webpack-plugin": "4.0.1", + "css-loader": "0.26.1", + "del": "2.2.2", + "event-stream": "3.3.4", + "exports-loader": "0.6.3", + "extract-text-webpack-plugin": "2.0.0-beta.5", + "file-loader": "0.10.0", + "generator-jhipster": "4.0.8", + "html-webpack-plugin": "2.28.0", + "image-webpack-loader": "3.2.0", + "jasmine-core": "2.5.2", + "jasmine-reporters": "2.2.0", + "karma": "1.4.1", + "karma-chrome-launcher": "2.0.0", + "karma-coverage": "1.1.1", + "karma-intl-shim": "1.0.3", + "karma-jasmine": "1.1.0", + "karma-junit-reporter": "1.2.0", + "karma-phantomjs-launcher": "1.0.2", + "karma-remap-istanbul": "0.6.0", + "karma-sourcemap-loader": "0.3.7", + "karma-webpack": "2.0.2", + "lazypipe": "1.0.1", + "lodash": "4.17.4", + "map-stream": "0.0.6", + "phantomjs-prebuilt": "2.1.14", + "protractor": "5.1.1", + "protractor-jasmine2-screenshot-reporter": "0.3.3", + "ts-node": "2.1.0", + "proxy-middleware": "0.15.0", + "raw-loader": "0.5.1", + "run-sequence": "1.2.2", + "sourcemap-istanbul-instrumenter-loader": "0.2.0", + "string-replace-webpack-plugin": "0.0.5", + "style-loader": "0.13.1", + "to-string-loader": "1.1.5", + "tslint": "4.4.2", + "tslint-loader": "3.4.1", + "typescript": "2.1.6", + "webpack": "2.2.1", + "webpack-dev-server": "2.3.0", + "webpack-merge": "2.6.1", + "webpack-visualizer-plugin": "0.1.10", + "write-file-webpack-plugin": "3.4.2", + "xml2js": "0.4.17", + "sass-loader": "5.0.1", + "node-sass": "4.5.0", + "postcss-loader": "1.3.0", + "yargs": "6.6.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "scripts": { + "lint": "tslint 'src/main/webapp/app/**/*.ts' --force", + "lint:fix": "tslint 'src/main/webapp/app/**/*.ts' --fix --force", + "tsc": "tsc", + "tsc:w": "tsc -w", + "start": "npm run webpack:dev", + "webpack:build": "webpack --config webpack/webpack.vendor.js && webpack --config webpack/webpack.dev.js", + "webpack:build:dev": "webpack --config webpack/webpack.dev.js", + "webpack:dev": "webpack-dev-server --config webpack/webpack.dev.js --progress --inline --hot --profile --port=9060", + "webpack:prod": "npm test && webpack -p --config webpack/webpack.vendor.js && webpack -p --config webpack/webpack.prod.js", + "test": "npm run lint && karma start src/test/javascript/karma.conf.js", + "test:watch": "karma start --watch", + "e2e": "protractor src/test/javascript/protractor.conf.js", + "postinstall": "webdriver-manager update && npm run webpack:build" + } +} diff --git a/jhipster/pom.xml b/jhipster/pom.xml new file mode 100644 index 0000000000..ba78ad4e2b --- /dev/null +++ b/jhipster/pom.xml @@ -0,0 +1,1034 @@ + + + 4.0.0 + + + spring-boot-starter-parent + org.springframework.boot + 1.5.2.RELEASE + + + + com.baeldung + jhipster-monolithic + 0.0.1-SNAPSHOT + war + JHipster Monolithic Application + + + ${maven.version} + + + + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate5 + + + com.fasterxml.jackson.datatype + jackson-datatype-hppc + + + com.fasterxml.jackson.datatype + jackson-datatype-json-org + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.h2database + h2 + + + com.jayway.jsonpath + json-path + test + + + + com.jcraft + jzlib + ${jzlib.version} + + + com.mattbertolini + liquibase-slf4j + ${liquibase-slf4j.version} + + + com.ryantenney.metrics + metrics-spring + ${metrics-spring.version} + + + metrics-annotation + com.codahale.metrics + + + metrics-core + com.codahale.metrics + + + metrics-healthchecks + com.codahale.metrics + + + + + com.zaxxer + HikariCP + + + tools + com.sun + + + + + + commons-io + commons-io + ${commons-io.version} + + + io.dropwizard.metrics + metrics-annotation + ${dropwizard-metrics.version} + + + io.dropwizard.metrics + metrics-core + + + io.dropwizard.metrics + metrics-json + ${dropwizard-metrics.version} + + + io.dropwizard.metrics + metrics-jvm + ${dropwizard-metrics.version} + + + io.dropwizard.metrics + metrics-servlet + ${dropwizard-metrics.version} + + + io.dropwizard.metrics + metrics-servlets + + + io.gatling.highcharts + gatling-charts-highcharts + ${gatling.version} + test + + + io.github.jhipster + jhipster + ${jhipster.server.version} + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + io.springfox + springfox-bean-validators + ${springfox.version} + + + io.springfox + springfox-swagger2 + ${springfox.version} + + + mapstruct + org.mapstruct + + + + + javax.cache + cache-api + + + mysql + mysql-connector-java + + + + net.logstash.logback + logstash-logback-encoder + ${logstash-logback-encoder.version} + + + logback-core + ch.qos.logback + + + logback-classic + ch.qos.logback + + + logback-access + ch.qos.logback + + + + + org.apache.commons + commons-lang3 + ${commons-lang.version} + + + org.assertj + assertj-core + test + + + org.awaitility + awaitility + ${awaitility.version} + test + + + org.ehcache + ehcache + + + org.hibernate + hibernate-envers + + + org.hibernate + hibernate-jcache + ${hibernate.version} + + + org.hibernate + hibernate-validator + + + org.liquibase + liquibase-core + + + jetty-servlet + org.eclipse.jetty + + + + + org.mapstruct + mapstruct-jdk8 + ${mapstruct.version} + + + org.springframework + spring-context-support + + + org.springframework.boot + spring-boot-actuator + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-loader-tools + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-cloud-connectors + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + spring-boot-starter-tomcat + org.springframework.boot + + + + + org.springframework.boot + spring-boot-test + test + + + + org.springframework.security + spring-security-data + + + org.springframework.security + spring-security-test + test + + + + + spring-boot:run + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + prepare-agent + + + + + + + + + com.github.eirslett + frontend-maven-plugin + ${frontend-maven-plugin.version} + + install-node-and-npm + npm + + + + + + + + + + + + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + ${sortpom-maven-plugin.version} + + + verify + + sort + + + + + true + 4 + groupId,artifactId + groupId,artifactId + true + false + + + + com.spotify + docker-maven-plugin + ${docker-maven-plugin.version} + + baeldung + src/main/docker + + + / + ${project.build.directory} + ${project.build.finalName}.war + + + + + + io.gatling + gatling-maven-plugin + ${gatling-maven-plugin.version} + + src/test/gatling/conf + src/test/gatling/data + target/gatling/results + src/test/gatling/bodies + src/test/gatling/simulations + + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + true + true + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-versions + + enforce + + + + + + + You are running an older version of + Maven. JHipster requires at least Maven + ${maven.version} + [${maven.version},) + + + You are running an older version of + Java. JHipster requires at least JDK + ${java.version} + [${java.version}.0,) + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + default-resources + validate + + copy-resources + + + target/classes + false + + # + + + + src/main/resources/ + true + + **/*.xml + **/*.yml + + + + src/main/resources/ + false + + **/*.xml + **/*.yml + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + alphabetical + + **/*IntTest.java + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + pre-unit-tests + + prepare-agent + + + + ${project.testresult.directory}/coverage/jacoco/jacoco.exec + + + + + post-unit-test + test + + report + + + ${project.testresult.directory}/coverage/jacoco/jacoco.exec + ${project.testresult.directory}/coverage/jacoco + + + + + + org.liquibase + liquibase-maven-plugin + ${liquibase.version} + + + javax.validation + validation-api + ${validation-api.version} + + + org.javassist + javassist + ${javassist.version} + + + org.liquibase.ext + liquibase-hibernate5 + ${liquibase-hibernate5.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${project.parent.version} + + + + src/main/resources/config/liquibase/master.xml + src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml + org.h2.Driver + jdbc:h2:file:./target/h2db/db/baeldung + + baeldung + + hibernate:spring:com.baeldung.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${sonar-maven-plugin.version} + + + org.springframework.boot + spring-boot-maven-plugin + + true + true + + + + + + + + + no-liquibase + + ,no-liquibase + + + + swagger + + ,swagger + + + + webpack + + + + com.github.eirslett + frontend-maven-plugin + ${frontend-maven-plugin.version} + + + install node and npm + + install-node-and-npm + + + ${node.version} + ${npm.version} + + + + webpack build dev + generate-resources + + npm + + + run webpack:build:dev + + + + + + + + + dev + + true + + + + + org.apache.maven.plugins + maven-war-plugin + + src/main/webapp/ + + + + + + + DEBUG + + dev${profile.no-liquibase} + + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-undertow + + + + + prod + + + + com.github.eirslett + frontend-maven-plugin + ${frontend-maven-plugin.version} + + + install node and npm + + install-node-and-npm + + + ${node.version} + ${npm.version} + + + + npm install + + npm + + + install + + + + npm rebuild node-sass + + npm + + + rebuild node-sass + + + + webpack build prod + generate-resources + + npm + + + run webpack:prod + + + + + + maven-clean-plugin + + + + target/www/ + + + + + + org.apache.maven.plugins + maven-war-plugin + + target/www/ + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + + true + + + + + + + INFO + + prod${profile.swagger}${profile.no-liquibase} + + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + cc + + + + net.alchim31.maven + scala-maven-plugin + ${scala-maven-plugin.version} + + + compile + compile + + add-source + compile + + + + test-compile + test-compile + + add-source + testCompile + + + + + incremental + true + ${scala.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + + + org.apache.maven.plugins + maven-war-plugin + + src/main/webapp/ + + + + org.springframework.boot + spring-boot-maven-plugin + + true + true + true + + + + + + + + DEBUG + + dev,swagger + + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + graphite + + + io.dropwizard.metrics + metrics-graphite + + + + + + prometheus + + + io.prometheus + simpleclient + ${prometheus-simpleclient.version} + + + io.prometheus + simpleclient_dropwizard + ${prometheus-simpleclient.version} + + + io.prometheus + simpleclient_servlet + ${prometheus-simpleclient.version} + + + + + + IDE + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + integration + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*IntTest.java + + + + + + + + + + + + -Djava.security.egd=file:/dev/./urandom -Xmx256m + 3.6.2 + 2.0.0 + 2.5 + 3.5 + 0.4.13 + 1.3 + 2.2.1 + 2.2.3 + 5.2.8.Final + 2.6.0 + 0.7.9 + 1.8 + 3.21.0-GA + 1.0.0 + 1.1.0 + 0.7.0 + 1.1.3 + 3.6 + 2.0.0 + 4.8 + jdt_apt + 1.1.0.Final + 3.6.0 + 1.4.1 + 3.0.1 + yyyyMMddHHmmss + ${java.version} + ${java.version} + 3.0.0 + 3.1.3 + v6.10.0 + 4.3.0 + + + + + ${project.build.directory}/test-results + 0.0.20 + false + 3.2.2 + 2.12.1 + 3.2 + + src/main/webapp/content/**/*.*, + src/main/webapp/bower_components/**/*.*, + src/main/webapp/i18n/*.js, target/www/**/*.* + + S3437,UndocumentedApi,BoldAndItalicTagsCheck + + + src/main/webapp/app/**/*.* + Web:BoldAndItalicTagsCheck + + src/main/java/**/* + squid:S3437 + + src/main/java/**/* + squid:UndocumentedApi + + ${project.testresult.directory}/coverage/jacoco/jacoco-it.exec + ${project.testresult.directory}/coverage/jacoco/jacoco.exec + jacoco + + ${project.testresult.directory}/karma + + ${project.testresult.directory}/coverage/report-lcov/lcov.info + + ${project.testresult.directory}/coverage/report-lcov/lcov.info + + ${project.basedir}/src/main/ + ${project.testresult.directory}/surefire-reports + ${project.basedir}/src/test/ + + 2.5.0 + + 2.6.1 + 1.4.10.Final + 1.1.0.Final + + diff --git a/jhipster/postcss.config.js b/jhipster/postcss.config.js new file mode 100644 index 0000000000..f549c034d5 --- /dev/null +++ b/jhipster/postcss.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [] +} diff --git a/jhipster/src/main/docker/Dockerfile b/jhipster/src/main/docker/Dockerfile new file mode 100644 index 0000000000..66991b2998 --- /dev/null +++ b/jhipster/src/main/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM openjdk:8-jre-alpine + +ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ + JHIPSTER_SLEEP=0 + +# add directly the war +ADD *.war /app.war + +VOLUME /tmp +EXPOSE 8080 +CMD echo "The application will start in ${JHIPSTER_SLEEP}s..." && \ + sleep ${JHIPSTER_SLEEP} && \ + java -Djava.security.egd=file:/dev/./urandom -jar /app.war diff --git a/jhipster/src/main/docker/app.yml b/jhipster/src/main/docker/app.yml new file mode 100644 index 0000000000..78cd2c1602 --- /dev/null +++ b/jhipster/src/main/docker/app.yml @@ -0,0 +1,14 @@ +version: '2' +services: + baeldung-app: + image: baeldung + environment: + - SPRING_PROFILES_ACTIVE=prod,swagger + - SPRING_DATASOURCE_URL=jdbc:mysql://baeldung-mysql:3306/baeldung?useUnicode=true&characterEncoding=utf8&useSSL=false + - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application + ports: + - 8080:8080 + baeldung-mysql: + extends: + file: mysql.yml + service: baeldung-mysql diff --git a/jhipster/src/main/docker/mysql.yml b/jhipster/src/main/docker/mysql.yml new file mode 100644 index 0000000000..5038d09504 --- /dev/null +++ b/jhipster/src/main/docker/mysql.yml @@ -0,0 +1,13 @@ +version: '2' +services: + baeldung-mysql: + image: mysql:5.7.13 + # volumes: + # - ~/volumes/jhipster/baeldung/mysql/:/var/lib/mysql/ + environment: + - MYSQL_USER=root + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=baeldung + ports: + - 3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8 diff --git a/jhipster/src/main/docker/sonar.yml b/jhipster/src/main/docker/sonar.yml new file mode 100644 index 0000000000..718be83b4d --- /dev/null +++ b/jhipster/src/main/docker/sonar.yml @@ -0,0 +1,7 @@ +version: '2' +services: + baeldung-sonar: + image: sonarqube:6.2-alpine + ports: + - 9000:9000 + - 9092:9092 diff --git a/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java b/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java new file mode 100644 index 0000000000..762d18420c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java @@ -0,0 +1,21 @@ +package com.baeldung; + +import com.baeldung.config.DefaultProfileUtil; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.support.SpringBootServletInitializer; + +/** + * This is a helper Java class that provides an alternative to creating a web.xml. + * This will be invoked only when the application is deployed to a servlet container like Tomcat, JBoss etc. + */ +public class ApplicationWebXml extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + /** + * set a default to use when no profile is configured. + */ + DefaultProfileUtil.addDefaultProfile(application.application()); + return application.sources(BaeldungApp.class); + } +} diff --git a/jhipster/src/main/java/com/baeldung/BaeldungApp.java b/jhipster/src/main/java/com/baeldung/BaeldungApp.java new file mode 100644 index 0000000000..13b6b2b3fa --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/BaeldungApp.java @@ -0,0 +1,84 @@ +package com.baeldung; + +import com.baeldung.config.ApplicationProperties; +import com.baeldung.config.DefaultProfileUtil; + +import io.github.jhipster.config.JHipsterConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.*; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.env.Environment; + +import javax.annotation.PostConstruct; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; + +@ComponentScan +@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class}) +@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class}) +public class BaeldungApp { + + private static final Logger log = LoggerFactory.getLogger(BaeldungApp.class); + + private final Environment env; + + public BaeldungApp(Environment env) { + this.env = env; + } + + /** + * Initializes baeldung. + *

+ * Spring profiles can be configured with a program arguments --spring.profiles.active=your-active-profile + *

+ * You can find more information on how profiles work with JHipster on http://jhipster.github.io/profiles/. + */ + @PostConstruct + public void initApplication() { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + log.error("You have misconfigured your application! It should not run " + + "with both the 'dev' and 'prod' profiles at the same time."); + } + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) { + log.error("You have misconfigured your application! It should not" + + "run with both the 'dev' and 'cloud' profiles at the same time."); + } + } + + /** + * Main method, used to run the application. + * + * @param args the command line arguments + * @throws UnknownHostException if the local host name could not be resolved into an address + */ + public static void main(String[] args) throws UnknownHostException { + SpringApplication app = new SpringApplication(BaeldungApp.class); + DefaultProfileUtil.addDefaultProfile(app); + Environment env = app.run(args).getEnvironment(); + String protocol = "http"; + if (env.getProperty("server.ssl.key-store") != null) { + protocol = "https"; + } + log.info("\n----------------------------------------------------------\n\t" + + "Application '{}' is running! Access URLs:\n\t" + + "Local: \t\t{}://localhost:{}\n\t" + + "External: \t{}://{}:{}\n\t" + + "Profile(s): \t{}\n----------------------------------------------------------", + env.getProperty("spring.application.name"), + protocol, + env.getProperty("server.port"), + protocol, + InetAddress.getLocalHost().getHostAddress(), + env.getProperty("server.port"), + env.getActiveProfiles()); + } +} diff --git a/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java b/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java new file mode 100644 index 0000000000..7fbd352820 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java @@ -0,0 +1,79 @@ +package com.baeldung.aop.logging; + +import io.github.jhipster.config.JHipsterConstants; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +import java.util.Arrays; + +/** + * Aspect for logging execution of service and repository Spring components. + * + * By default, it only runs with the "dev" profile. + */ +@Aspect +public class LoggingAspect { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private final Environment env; + + public LoggingAspect(Environment env) { + this.env = env; + } + + /** + * Pointcut that matches all repositories, services and Web REST endpoints. + */ + @Pointcut("within(com.baeldung.repository..*) || within(com.baeldung.service..*) || within(com.baeldung.web.rest..*)") + public void loggingPointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Advice that logs methods throwing exceptions. + */ + @AfterThrowing(pointcut = "loggingPointcut()", throwing = "e") + public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { + if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) { + log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e); + + } else { + log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL"); + } + } + + /** + * Advice that logs when a method is entered and exited. + */ + @Around("loggingPointcut()") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); + } + try { + Object result = joinPoint.proceed(); + if (log.isDebugEnabled()) { + log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), result); + } + return result; + } catch (IllegalArgumentException e) { + log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()), + joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); + + throw e; + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java b/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java new file mode 100644 index 0000000000..add1782678 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java @@ -0,0 +1,15 @@ +package com.baeldung.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties specific to JHipster. + * + *

+ * Properties are configured in the application.yml file. + *

+ */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties { + +} diff --git a/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java b/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java new file mode 100644 index 0000000000..dc1ba37514 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java @@ -0,0 +1,46 @@ +package com.baeldung.config; + +import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor; +import io.github.jhipster.config.JHipsterProperties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.*; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +@EnableScheduling +public class AsyncConfiguration implements AsyncConfigurer { + + private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class); + + private final JHipsterProperties jHipsterProperties; + + public AsyncConfiguration(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @Override + @Bean(name = "taskExecutor") + public Executor getAsyncExecutor() { + log.debug("Creating Async Task Executor"); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize()); + executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize()); + executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity()); + executor.setThreadNamePrefix("baeldung-Executor-"); + return new ExceptionHandlingAsyncTaskExecutor(executor); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new SimpleAsyncUncaughtExceptionHandler(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java b/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java new file mode 100644 index 0000000000..4323fa076a --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java @@ -0,0 +1,48 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterProperties; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; +import org.ehcache.expiry.Duration; +import org.ehcache.expiry.Expirations; +import org.ehcache.jsr107.Eh107Configuration; + +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.*; + +@Configuration +@EnableCaching +@AutoConfigureAfter(value = { MetricsConfiguration.class }) +@AutoConfigureBefore(value = { WebConfigurer.class, DatabaseConfiguration.class }) +public class CacheConfiguration { + + private final javax.cache.configuration.Configuration jcacheConfiguration; + + public CacheConfiguration(JHipsterProperties jHipsterProperties) { + JHipsterProperties.Cache.Ehcache ehcache = + jHipsterProperties.getCache().getEhcache(); + + jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, + ResourcePoolsBuilder.heap(ehcache.getMaxEntries())) + .withExpiry(Expirations.timeToLiveExpiration(Duration.of(ehcache.getTimeToLiveSeconds(), TimeUnit.SECONDS))) + .build()); + } + + @Bean + public JCacheManagerCustomizer cacheManagerCustomizer() { + return cm -> { + cm.createCache(com.baeldung.domain.User.class.getName(), jcacheConfiguration); + cm.createCache(com.baeldung.domain.Authority.class.getName(), jcacheConfiguration); + cm.createCache(com.baeldung.domain.User.class.getName() + ".authorities", jcacheConfiguration); + cm.createCache(com.baeldung.domain.Post.class.getName(), jcacheConfiguration); + cm.createCache(com.baeldung.domain.Comment.class.getName(), jcacheConfiguration); + // jhipster-needle-ehcache-add-entry + }; + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java b/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java new file mode 100644 index 0000000000..cae9ec1e82 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java @@ -0,0 +1,23 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.config.java.AbstractCloudConfig; +import org.springframework.context.annotation.*; + +import javax.sql.DataSource; + +@Configuration +@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD) +public class CloudDatabaseConfiguration extends AbstractCloudConfig { + + private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class); + + @Bean + public DataSource dataSource() { + log.info("Configuring JDBC datasource from a cloud provider"); + return connectionFactory().dataSource(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/Constants.java b/jhipster/src/main/java/com/baeldung/config/Constants.java new file mode 100644 index 0000000000..9d797c1934 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/Constants.java @@ -0,0 +1,16 @@ +package com.baeldung.config; + +/** + * Application constants. + */ +public final class Constants { + + //Regex for acceptable logins + public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$"; + + public static final String SYSTEM_ACCOUNT = "system"; + public static final String ANONYMOUS_USER = "anonymoususer"; + + private Constants() { + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java b/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java new file mode 100644 index 0000000000..b64fd7cc82 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java @@ -0,0 +1,75 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.liquibase.AsyncSpringLiquibase; + +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; +import liquibase.integration.spring.SpringLiquibase; +import org.h2.tools.Server; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.core.task.TaskExecutor; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.sql.SQLException; + +@Configuration +@EnableJpaRepositories("com.baeldung.repository") +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); + + private final Environment env; + + public DatabaseConfiguration(Environment env) { + this.env = env; + } + + /** + * Open the TCP port for the H2 database, so it is available remotely. + * + * @return the H2 database TCP server + * @throws SQLException if the server failed to start + */ + @Bean(initMethod = "start", destroyMethod = "stop") + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public Server h2TCPServer() throws SQLException { + return Server.createTcpServer("-tcp","-tcpAllowOthers"); + } + + @Bean + public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor, + DataSource dataSource, LiquibaseProperties liquibaseProperties) { + + // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously + SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env); + liquibase.setDataSource(dataSource); + liquibase.setChangeLog("classpath:config/liquibase/master.xml"); + liquibase.setContexts(liquibaseProperties.getContexts()); + liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); + liquibase.setDropFirst(liquibaseProperties.isDropFirst()); + if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) { + liquibase.setShouldRun(false); + } else { + liquibase.setShouldRun(liquibaseProperties.isEnabled()); + log.debug("Configuring Liquibase"); + } + return liquibase; + } + + @Bean + public Hibernate5Module hibernate5Module() { + return new Hibernate5Module(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java b/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java new file mode 100644 index 0000000000..aab57adfb0 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java @@ -0,0 +1,17 @@ +package com.baeldung.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter { + + @Override + public void addFormatters(FormatterRegistry registry) { + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(registry); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java b/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java new file mode 100644 index 0000000000..7918fb49a4 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java @@ -0,0 +1,48 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterConstants; + +import org.springframework.boot.SpringApplication; +import org.springframework.core.env.Environment; + +import java.util.*; + +/** + * Utility class to load a Spring profile to be used as default + * when there is no spring.profiles.active set in the environment or as command line argument. + * If the value is not available in application.yml then dev profile will be used as default. + */ +public final class DefaultProfileUtil { + + private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default"; + + private DefaultProfileUtil() { + } + + /** + * Set a default to use when no profile is configured. + * + * @param app the Spring application + */ + public static void addDefaultProfile(SpringApplication app) { + Map defProperties = new HashMap<>(); + /* + * The default profile to use when no other profiles are defined + * This cannot be set in the application.yml file. + * See https://github.com/spring-projects/spring-boot/issues/1219 + */ + defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT); + app.setDefaultProperties(defProperties); + } + + /** + * Get the profiles that are applied else get default profiles. + */ + public static String[] getActiveProfiles(Environment env) { + String[] profiles = env.getActiveProfiles(); + if (profiles.length == 0) { + return env.getDefaultProfiles(); + } + return profiles; + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java new file mode 100644 index 0000000000..dd3a499d56 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java @@ -0,0 +1,35 @@ +package com.baeldung.config; + +import io.github.jhipster.config.locale.AngularCookieLocaleResolver; + +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; + +@Configuration +public class LocaleConfiguration extends WebMvcConfigurerAdapter implements EnvironmentAware { + + @Override + public void setEnvironment(Environment environment) { + // unused + } + + @Bean(name = "localeResolver") + public LocaleResolver localeResolver() { + AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver(); + cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY"); + return cookieLocaleResolver; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + localeChangeInterceptor.setParamName("language"); + registry.addInterceptor(localeChangeInterceptor); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java new file mode 100644 index 0000000000..936f54709a --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java @@ -0,0 +1,19 @@ +package com.baeldung.config; + +import com.baeldung.aop.logging.LoggingAspect; + +import io.github.jhipster.config.JHipsterConstants; + +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingAspectConfiguration { + + @Bean + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public LoggingAspect loggingAspect(Environment env) { + return new LoggingAspect(env); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java new file mode 100644 index 0000000000..3ca6a48821 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java @@ -0,0 +1,109 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterProperties; + +import ch.qos.logback.classic.AsyncAppender; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggerContextListener; +import ch.qos.logback.core.spi.ContextAwareBase; +import net.logstash.logback.appender.LogstashSocketAppender; +import net.logstash.logback.stacktrace.ShortenedThrowableConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class LoggingConfiguration { + + private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class); + + private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + @Value("${spring.application.name}") + private String appName; + + @Value("${server.port}") + private String serverPort; + + private final JHipsterProperties jHipsterProperties; + + public LoggingConfiguration(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + if (jHipsterProperties.getLogging().getLogstash().isEnabled()) { + addLogstashAppender(context); + + // Add context listener + LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener(); + loggerContextListener.setContext(context); + context.addListener(loggerContextListener); + } + } + + public void addLogstashAppender(LoggerContext context) { + log.info("Initializing Logstash logging"); + + LogstashSocketAppender logstashAppender = new LogstashSocketAppender(); + logstashAppender.setName("LOGSTASH"); + logstashAppender.setContext(context); + String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"}"; + + // Set the Logstash appender config from JHipster properties + logstashAppender.setSyslogHost(jHipsterProperties.getLogging().getLogstash().getHost()); + logstashAppender.setPort(jHipsterProperties.getLogging().getLogstash().getPort()); + logstashAppender.setCustomFields(customFields); + + // Limit the maximum length of the forwarded stacktrace so that it won't exceed the 8KB UDP limit of logstash + ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter(); + throwableConverter.setMaxLength(7500); + throwableConverter.setRootCauseFirst(true); + logstashAppender.setThrowableConverter(throwableConverter); + + logstashAppender.start(); + + // Wrap the appender in an Async appender for performance + AsyncAppender asyncLogstashAppender = new AsyncAppender(); + asyncLogstashAppender.setContext(context); + asyncLogstashAppender.setName("ASYNC_LOGSTASH"); + asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize()); + asyncLogstashAppender.addAppender(logstashAppender); + asyncLogstashAppender.start(); + + context.getLogger("ROOT").addAppender(asyncLogstashAppender); + } + + /** + * Logback configuration is achieved by configuration file and API. + * When configuration file change is detected, the configuration is reset. + * This listener ensures that the programmatic configuration is also re-applied after reset. + */ + class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener { + + @Override + public boolean isResetResistant() { + return true; + } + + @Override + public void onStart(LoggerContext context) { + addLogstashAppender(context); + } + + @Override + public void onReset(LoggerContext context) { + addLogstashAppender(context); + } + + @Override + public void onStop(LoggerContext context) { + // Nothing to do. + } + + @Override + public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) { + // Nothing to do. + } + } + +} diff --git a/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java b/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java new file mode 100644 index 0000000000..94a50bed91 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java @@ -0,0 +1,94 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.config.jcache.JCacheGaugeSet; + +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Slf4jReporter; +import com.codahale.metrics.health.HealthCheckRegistry; +import com.codahale.metrics.jvm.*; +import com.ryantenney.metrics.spring.config.annotation.EnableMetrics; +import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter; +import com.zaxxer.hikari.HikariDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.*; + +import javax.annotation.PostConstruct; +import java.lang.management.ManagementFactory; +import java.util.concurrent.TimeUnit; + +@Configuration +@EnableMetrics(proxyTargetClass = true) +public class MetricsConfiguration extends MetricsConfigurerAdapter { + + private static final String PROP_METRIC_REG_JVM_MEMORY = "jvm.memory"; + private static final String PROP_METRIC_REG_JVM_GARBAGE = "jvm.garbage"; + private static final String PROP_METRIC_REG_JVM_THREADS = "jvm.threads"; + private static final String PROP_METRIC_REG_JVM_FILES = "jvm.files"; + private static final String PROP_METRIC_REG_JVM_BUFFERS = "jvm.buffers"; + + private static final String PROP_METRIC_REG_JCACHE_STATISTICS = "jcache.statistics"; + private final Logger log = LoggerFactory.getLogger(MetricsConfiguration.class); + + private MetricRegistry metricRegistry = new MetricRegistry(); + + private HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry(); + + private final JHipsterProperties jHipsterProperties; + + private HikariDataSource hikariDataSource; + + public MetricsConfiguration(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @Autowired(required = false) + public void setHikariDataSource(HikariDataSource hikariDataSource) { + this.hikariDataSource = hikariDataSource; + } + + @Override + @Bean + public MetricRegistry getMetricRegistry() { + return metricRegistry; + } + + @Override + @Bean + public HealthCheckRegistry getHealthCheckRegistry() { + return healthCheckRegistry; + } + + @PostConstruct + public void init() { + log.debug("Registering JVM gauges"); + metricRegistry.register(PROP_METRIC_REG_JVM_MEMORY, new MemoryUsageGaugeSet()); + metricRegistry.register(PROP_METRIC_REG_JVM_GARBAGE, new GarbageCollectorMetricSet()); + metricRegistry.register(PROP_METRIC_REG_JVM_THREADS, new ThreadStatesGaugeSet()); + metricRegistry.register(PROP_METRIC_REG_JVM_FILES, new FileDescriptorRatioGauge()); + metricRegistry.register(PROP_METRIC_REG_JVM_BUFFERS, new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())); + + metricRegistry.register(PROP_METRIC_REG_JCACHE_STATISTICS, new JCacheGaugeSet()); + if (hikariDataSource != null) { + log.debug("Monitoring the datasource"); + hikariDataSource.setMetricRegistry(metricRegistry); + } + if (jHipsterProperties.getMetrics().getJmx().isEnabled()) { + log.debug("Initializing Metrics JMX reporting"); + JmxReporter jmxReporter = JmxReporter.forRegistry(metricRegistry).build(); + jmxReporter.start(); + } + if (jHipsterProperties.getMetrics().getLogs().isEnabled()) { + log.info("Initializing Metrics Log reporting"); + final Slf4jReporter reporter = Slf4jReporter.forRegistry(metricRegistry) + .outputTo(LoggerFactory.getLogger("metrics")) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(jHipsterProperties.getMetrics().getLogs().getReportFrequency(), TimeUnit.SECONDS); + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java b/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java new file mode 100644 index 0000000000..118d302fcf --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java @@ -0,0 +1,127 @@ +package com.baeldung.config; + +import com.baeldung.security.*; +import com.baeldung.security.jwt.*; + +import io.github.jhipster.security.*; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +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.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.filter.CorsFilter; + +import javax.annotation.PostConstruct; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + private final AuthenticationManagerBuilder authenticationManagerBuilder; + + private final UserDetailsService userDetailsService; + + private final TokenProvider tokenProvider; + + private final CorsFilter corsFilter; + + public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, + TokenProvider tokenProvider, + CorsFilter corsFilter) { + + this.authenticationManagerBuilder = authenticationManagerBuilder; + this.userDetailsService = userDetailsService; + this.tokenProvider = tokenProvider; + this.corsFilter = corsFilter; + } + + @PostConstruct + public void init() { + try { + authenticationManagerBuilder + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + } catch (Exception e) { + throw new BeanInitializationException("Security configuration failed", e); + } + } + + @Bean + public Http401UnauthorizedEntryPoint http401UnauthorizedEntryPoint() { + return new Http401UnauthorizedEntryPoint(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring() + .antMatchers(HttpMethod.OPTIONS, "/**") + .antMatchers("/app/**/*.{js,html}") + .antMatchers("/bower_components/**") + .antMatchers("/i18n/**") + .antMatchers("/content/**") + .antMatchers("/swagger-ui/index.html") + .antMatchers("/test/**") + .antMatchers("/h2-console/**"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling() + .authenticationEntryPoint(http401UnauthorizedEntryPoint()) + .and() + .csrf() + .disable() + .headers() + .frameOptions() + .disable() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/register").permitAll() + .antMatchers("/api/activate").permitAll() + .antMatchers("/api/authenticate").permitAll() + .antMatchers("/api/account/reset_password/init").permitAll() + .antMatchers("/api/account/reset_password/finish").permitAll() + .antMatchers("/api/profile-info").permitAll() + .antMatchers("/api/**").authenticated() + .antMatchers("/management/health").permitAll() + .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/v2/api-docs/**").permitAll() + .antMatchers("/swagger-resources/configuration/ui").permitAll() + .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN) + .and() + .apply(securityConfigurerAdapter()); + + } + + private JWTConfigurer securityConfigurerAdapter() { + return new JWTConfigurer(tokenProvider); + } + + @Bean + public SecurityEvaluationContextExtension securityEvaluationContextExtension() { + return new SecurityEvaluationContextExtension(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java b/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java new file mode 100644 index 0000000000..68afcae71a --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java @@ -0,0 +1,26 @@ +package com.baeldung.config; + +import org.apache.commons.lang3.CharEncoding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.*; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; + +@Configuration +public class ThymeleafConfiguration { + + @SuppressWarnings("unused") + private final Logger log = LoggerFactory.getLogger(ThymeleafConfiguration.class); + + @Bean + @Description("Thymeleaf template resolver serving HTML 5 emails") + public ClassLoaderTemplateResolver emailTemplateResolver() { + ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver(); + emailTemplateResolver.setPrefix("mails/"); + emailTemplateResolver.setSuffix(".html"); + emailTemplateResolver.setTemplateMode("HTML5"); + emailTemplateResolver.setCharacterEncoding(CharEncoding.UTF_8); + emailTemplateResolver.setOrder(1); + return emailTemplateResolver; + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java b/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java new file mode 100644 index 0000000000..bd80a063eb --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java @@ -0,0 +1,186 @@ +package com.baeldung.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.web.filter.CachingHttpHeadersFilter; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.servlet.InstrumentedFilter; +import com.codahale.metrics.servlets.MetricsServlet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.*; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; +import io.undertow.UndertowOptions; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import java.io.File; +import java.nio.file.Paths; +import java.util.*; +import javax.servlet.*; + +/** + * Configuration of web application with Servlet 3.0 APIs. + */ +@Configuration +public class WebConfigurer implements ServletContextInitializer, EmbeddedServletContainerCustomizer { + + private final Logger log = LoggerFactory.getLogger(WebConfigurer.class); + + private final Environment env; + + private final JHipsterProperties jHipsterProperties; + + private MetricRegistry metricRegistry; + + public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) { + + this.env = env; + this.jHipsterProperties = jHipsterProperties; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (env.getActiveProfiles().length != 0) { + log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); + } + EnumSet disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC); + initMetrics(servletContext, disps); + if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + initCachingHttpHeadersFilter(servletContext, disps); + } + if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) { + initH2Console(servletContext); + } + log.info("Web application fully configured"); + } + + /** + * Customize the Servlet engine: Mime types, the document root, the cache. + */ + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); + // IE issue, see https://github.com/jhipster/generator-jhipster/pull/711 + mappings.add("html", "text/html;charset=utf-8"); + // CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64 + mappings.add("json", "text/html;charset=utf-8"); + container.setMimeMappings(mappings); + // When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets. + setLocationForStaticAssets(container); + + /* + * Enable HTTP/2 for Undertow - https://twitter.com/ankinson/status/829256167700492288 + * HTTP/2 requires HTTPS, so HTTP requests will fallback to HTTP/1.1. + * See the JHipsterProperties class and your application-*.yml configuration files + * for more information. + */ + if (jHipsterProperties.getHttp().getVersion().equals(JHipsterProperties.Http.Version.V_2_0) && + container instanceof UndertowEmbeddedServletContainerFactory) { + + ((UndertowEmbeddedServletContainerFactory) container) + .addBuilderCustomizers(builder -> + builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true)); + } + } + + private void setLocationForStaticAssets(ConfigurableEmbeddedServletContainer container) { + File root; + String prefixPath = resolvePathPrefix(); + root = new File(prefixPath + "target/www/"); + if (root.exists() && root.isDirectory()) { + container.setDocumentRoot(root); + } + } + + /** + * Resolve path prefix to static resources. + */ + private String resolvePathPrefix() { + String fullExecutablePath = this.getClass().getResource("").getPath(); + String rootPath = Paths.get(".").toUri().normalize().getPath(); + String extractedPath = fullExecutablePath.replace(rootPath, ""); + int extractionEndIndex = extractedPath.indexOf("target/"); + if(extractionEndIndex <= 0) { + return ""; + } + return extractedPath.substring(0, extractionEndIndex); + } + + /** + * Initializes the caching HTTP Headers Filter. + */ + private void initCachingHttpHeadersFilter(ServletContext servletContext, + EnumSet disps) { + log.debug("Registering Caching HTTP Headers Filter"); + FilterRegistration.Dynamic cachingHttpHeadersFilter = + servletContext.addFilter("cachingHttpHeadersFilter", + new CachingHttpHeadersFilter(jHipsterProperties)); + + cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*"); + cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*"); + cachingHttpHeadersFilter.setAsyncSupported(true); + } + + /** + * Initializes Metrics. + */ + private void initMetrics(ServletContext servletContext, EnumSet disps) { + log.debug("Initializing Metrics registries"); + servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE, + metricRegistry); + servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY, + metricRegistry); + + log.debug("Registering Metrics Filter"); + FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter", + new InstrumentedFilter()); + + metricsFilter.addMappingForUrlPatterns(disps, true, "/*"); + metricsFilter.setAsyncSupported(true); + + log.debug("Registering Metrics Servlet"); + ServletRegistration.Dynamic metricsAdminServlet = + servletContext.addServlet("metricsServlet", new MetricsServlet()); + + metricsAdminServlet.addMapping("/management/metrics/*"); + metricsAdminServlet.setAsyncSupported(true); + metricsAdminServlet.setLoadOnStartup(2); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = jHipsterProperties.getCors(); + if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { + log.debug("Registering CORS filter"); + source.registerCorsConfiguration("/api/**", config); + source.registerCorsConfiguration("/v2/api-docs", config); + } + return new CorsFilter(source); + } + + /** + * Initializes H2 console. + */ + private void initH2Console(ServletContext servletContext) { + log.debug("Initialize H2 console"); + ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console", new org.h2.server.web.WebServlet()); + h2ConsoleServlet.addMapping("/h2-console/*"); + h2ConsoleServlet.setInitParameter("-properties", "src/main/resources/"); + h2ConsoleServlet.setLoadOnStartup(1); + } + + @Autowired(required = false) + public void setMetricRegistry(MetricRegistry metricRegistry) { + this.metricRegistry = metricRegistry; + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java b/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java new file mode 100644 index 0000000000..2062320751 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java @@ -0,0 +1,91 @@ +package com.baeldung.config.audit; + +import com.baeldung.domain.PersistentAuditEvent; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.*; + +@Component +public class AuditEventConverter { + + /** + * Convert a list of PersistentAuditEvent to a list of AuditEvent + * + * @param persistentAuditEvents the list to convert + * @return the converted list. + */ + public List convertToAuditEvent(Iterable persistentAuditEvents) { + if (persistentAuditEvents == null) { + return Collections.emptyList(); + } + List auditEvents = new ArrayList<>(); + for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) { + auditEvents.add(convertToAuditEvent(persistentAuditEvent)); + } + return auditEvents; + } + + /** + * Convert a PersistentAuditEvent to an AuditEvent + * + * @param persistentAuditEvent the event to convert + * @return the converted list. + */ + public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) { + Instant instant = persistentAuditEvent.getAuditEventDate().atZone(ZoneId.systemDefault()).toInstant(); + return new AuditEvent(Date.from(instant), persistentAuditEvent.getPrincipal(), + persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData())); + } + + /** + * Internal conversion. This is needed to support the current SpringBoot actuator AuditEventRepository interface + * + * @param data the data to convert + * @return a map of String, Object + */ + public Map convertDataToObjects(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + results.put(entry.getKey(), entry.getValue()); + } + } + return results; + } + + /** + * Internal conversion. This method will allow to save additional data. + * By default, it will save the object as string + * + * @param data the data to convert + * @return a map of String, String + */ + public Map convertDataToStrings(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + Object object = entry.getValue(); + + // Extract the data that will be saved. + if (object instanceof WebAuthenticationDetails) { + WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) object; + results.put("remoteAddress", authenticationDetails.getRemoteAddress()); + results.put("sessionId", authenticationDetails.getSessionId()); + } else if (object != null) { + results.put(entry.getKey(), object.toString()); + } else { + results.put(entry.getKey(), "null"); + } + } + } + + return results; + } +} diff --git a/jhipster/src/main/java/com/baeldung/config/audit/package-info.java b/jhipster/src/main/java/com/baeldung/config/audit/package-info.java new file mode 100644 index 0000000000..8ed4f851f0 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/audit/package-info.java @@ -0,0 +1,4 @@ +/** + * Audit specific code. + */ +package com.baeldung.config.audit; diff --git a/jhipster/src/main/java/com/baeldung/config/package-info.java b/jhipster/src/main/java/com/baeldung/config/package-info.java new file mode 100644 index 0000000000..5e54ad6d03 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Framework configuration files. + */ +package com.baeldung.config; diff --git a/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java b/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java new file mode 100644 index 0000000000..2d181e5dc8 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java @@ -0,0 +1,80 @@ +package com.baeldung.domain; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.hibernate.envers.Audited; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.time.ZonedDateTime; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; + +/** + * Base abstract class for entities which will hold definitions for created, last modified by and created, + * last modified by date. + */ +@MappedSuperclass +@Audited +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @CreatedBy + @Column(name = "created_by", nullable = false, length = 50, updatable = false) + @JsonIgnore + private String createdBy; + + @CreatedDate + @Column(name = "created_date", nullable = false) + @JsonIgnore + private ZonedDateTime createdDate = ZonedDateTime.now(); + + @LastModifiedBy + @Column(name = "last_modified_by", length = 50) + @JsonIgnore + private String lastModifiedBy; + + @LastModifiedDate + @Column(name = "last_modified_date") + @JsonIgnore + private ZonedDateTime lastModifiedDate = ZonedDateTime.now(); + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public ZonedDateTime getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(ZonedDateTime createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public ZonedDateTime getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(ZonedDateTime lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/Authority.java b/jhipster/src/main/java/com/baeldung/domain/Authority.java new file mode 100644 index 0000000000..8a94ad0bb2 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/Authority.java @@ -0,0 +1,66 @@ +package com.baeldung.domain; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Column; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * An authority (a security role) used by Spring Security. + */ +@Entity +@Table(name = "jhi_authority") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class Authority implements Serializable { + + private static final long serialVersionUID = 1L; + + @NotNull + @Size(min = 0, max = 50) + @Id + @Column(length = 50) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Authority authority = (Authority) o; + + if (name != null ? !name.equals(authority.name) : authority.name != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } + + @Override + public String toString() { + return "Authority{" + + "name='" + name + '\'' + + "}"; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/Comment.java b/jhipster/src/main/java/com/baeldung/domain/Comment.java new file mode 100644 index 0000000000..c4587b6231 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/Comment.java @@ -0,0 +1,114 @@ +package com.baeldung.domain; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +import javax.persistence.*; +import javax.validation.constraints.*; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; + +/** + * A Comment. + */ +@Entity +@Table(name = "comment") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class Comment implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Size(min = 10, max = 100) + @Column(name = "text", length = 100, nullable = false) + private String text; + + @NotNull + @Column(name = "creation_date", nullable = false) + private LocalDate creationDate; + + @ManyToOne(optional = false) + @NotNull + private Post post; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getText() { + return text; + } + + public Comment text(String text) { + this.text = text; + return this; + } + + public void setText(String text) { + this.text = text; + } + + public LocalDate getCreationDate() { + return creationDate; + } + + public Comment creationDate(LocalDate creationDate) { + this.creationDate = creationDate; + return this; + } + + public void setCreationDate(LocalDate creationDate) { + this.creationDate = creationDate; + } + + public Post getPost() { + return post; + } + + public Comment post(Post post) { + this.post = post; + return this; + } + + public void setPost(Post post) { + this.post = post; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Comment comment = (Comment) o; + if (comment.id == null || id == null) { + return false; + } + return Objects.equals(id, comment.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } + + @Override + public String toString() { + return "Comment{" + + "id=" + id + + ", text='" + text + "'" + + ", creationDate='" + creationDate + "'" + + '}'; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java b/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java new file mode 100644 index 0000000000..c0fb0cb146 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java @@ -0,0 +1,78 @@ +package com.baeldung.domain; + + +import java.io.Serializable; +import java.time.LocalDateTime; +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +/** + * Persist AuditEvent managed by the Spring Boot actuator + * @see org.springframework.boot.actuate.audit.AuditEvent + */ +@Entity +@Table(name = "jhi_persistent_audit_event") +public class PersistentAuditEvent implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "event_id") + private Long id; + + @NotNull + @Column(nullable = false) + private String principal; + + @Column(name = "event_date") + private LocalDateTime auditEventDate; + @Column(name = "event_type") + private String auditEventType; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value") + @CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id")) + private Map data = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPrincipal() { + return principal; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public LocalDateTime getAuditEventDate() { + return auditEventDate; + } + + public void setAuditEventDate(LocalDateTime auditEventDate) { + this.auditEventDate = auditEventDate; + } + + public String getAuditEventType() { + return auditEventType; + } + + public void setAuditEventType(String auditEventType) { + this.auditEventType = auditEventType; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/Post.java b/jhipster/src/main/java/com/baeldung/domain/Post.java new file mode 100644 index 0000000000..7f084dc177 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/Post.java @@ -0,0 +1,133 @@ +package com.baeldung.domain; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +import javax.persistence.*; +import javax.validation.constraints.*; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; + +/** + * A Post. + */ +@Entity +@Table(name = "post") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class Post implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Size(min = 10, max = 100) + @Column(name = "title", length = 100, nullable = false) + private String title; + + @NotNull + @Size(min = 10, max = 1000) + @Column(name = "content", length = 1000, nullable = false) + private String content; + + @NotNull + @Column(name = "creation_date", nullable = false) + private LocalDate creationDate; + + @ManyToOne(optional = false) + @NotNull + private User creator; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public Post title(String title) { + this.title = title; + return this; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public Post content(String content) { + this.content = content; + return this; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDate getCreationDate() { + return creationDate; + } + + public Post creationDate(LocalDate creationDate) { + this.creationDate = creationDate; + return this; + } + + public void setCreationDate(LocalDate creationDate) { + this.creationDate = creationDate; + } + + public User getCreator() { + return creator; + } + + public Post creator(User user) { + this.creator = user; + return this; + } + + public void setCreator(User user) { + this.creator = user; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Post post = (Post) o; + if (post.id == null || id == null) { + return false; + } + return Objects.equals(id, post.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } + + @Override + public String toString() { + return "Post{" + + "id=" + id + + ", title='" + title + "'" + + ", content='" + content + "'" + + ", creationDate='" + creationDate + "'" + + '}'; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/User.java b/jhipster/src/main/java/com/baeldung/domain/User.java new file mode 100644 index 0000000000..76aa3a5209 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/User.java @@ -0,0 +1,235 @@ +package com.baeldung.domain; + +import com.baeldung.config.Constants; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.validator.constraints.Email; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.time.ZonedDateTime; + +/** + * A user. + */ +@Entity +@Table(name = "jhi_user") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class User extends AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @JsonIgnore + @NotNull + @Size(min = 60, max = 60) + @Column(name = "password_hash",length = 60) + private String password; + + @Size(max = 50) + @Column(name = "first_name", length = 50) + private String firstName; + + @Size(max = 50) + @Column(name = "last_name", length = 50) + private String lastName; + + @Email + @Size(max = 100) + @Column(length = 100, unique = true) + private String email; + + @NotNull + @Column(nullable = false) + private boolean activated = false; + + @Size(min = 2, max = 5) + @Column(name = "lang_key", length = 5) + private String langKey; + + @Size(max = 256) + @Column(name = "image_url", length = 256) + private String imageUrl; + + @Size(max = 20) + @Column(name = "activation_key", length = 20) + @JsonIgnore + private String activationKey; + + @Size(max = 20) + @Column(name = "reset_key", length = 20) + private String resetKey; + + @Column(name = "reset_date") + private ZonedDateTime resetDate = null; + + @JsonIgnore + @ManyToMany + @JoinTable( + name = "jhi_user_authority", + joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, + inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")}) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @BatchSize(size = 20) + private Set authorities = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + //Lowercase the login before saving it in database + public void setLogin(String login) { + this.login = login.toLowerCase(Locale.ENGLISH); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean getActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getActivationKey() { + return activationKey; + } + + public void setActivationKey(String activationKey) { + this.activationKey = activationKey; + } + + public String getResetKey() { + return resetKey; + } + + public void setResetKey(String resetKey) { + this.resetKey = resetKey; + } + + public ZonedDateTime getResetDate() { + return resetDate; + } + + public void setResetDate(ZonedDateTime resetDate) { + this.resetDate = resetDate; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + User user = (User) o; + + if (!login.equals(user.login)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return login.hashCode(); + } + + @Override + public String toString() { + return "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}"; + } +} diff --git a/jhipster/src/main/java/com/baeldung/domain/package-info.java b/jhipster/src/main/java/com/baeldung/domain/package-info.java new file mode 100644 index 0000000000..2492048c77 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * JPA domain objects. + */ +package com.baeldung.domain; diff --git a/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java b/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java new file mode 100644 index 0000000000..fab63174aa --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java @@ -0,0 +1,11 @@ +package com.baeldung.repository; + +import com.baeldung.domain.Authority; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the Authority entity. + */ +public interface AuthorityRepository extends JpaRepository { +} diff --git a/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java b/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java new file mode 100644 index 0000000000..d2d2618c36 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java @@ -0,0 +1,15 @@ +package com.baeldung.repository; + +import com.baeldung.domain.Comment; + +import org.springframework.data.jpa.repository.*; + +import java.util.List; + +/** + * Spring Data JPA repository for the Comment entity. + */ +@SuppressWarnings("unused") +public interface CommentRepository extends JpaRepository { + +} diff --git a/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java b/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java new file mode 100644 index 0000000000..fcfa9baeec --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java @@ -0,0 +1,81 @@ +package com.baeldung.repository; + +import com.baeldung.config.Constants; +import com.baeldung.config.audit.AuditEventConverter; +import com.baeldung.domain.PersistentAuditEvent; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +/** + * An implementation of Spring Boot's AuditEventRepository. + */ +@Repository +public class CustomAuditEventRepository implements AuditEventRepository { + + private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE"; + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter) { + + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + } + + @Override + public List find(Date after) { + Iterable persistentAuditEvents = + persistenceAuditEventRepository.findByAuditEventDateAfter(LocalDateTime.from(after.toInstant())); + return auditEventConverter.convertToAuditEvent(persistentAuditEvents); + } + + @Override + public List find(String principal, Date after) { + Iterable persistentAuditEvents; + if (principal == null && after == null) { + persistentAuditEvents = persistenceAuditEventRepository.findAll(); + } else if (after == null) { + persistentAuditEvents = persistenceAuditEventRepository.findByPrincipal(principal); + } else { + persistentAuditEvents = + persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfter(principal, LocalDateTime.from(after.toInstant())); + } + return auditEventConverter.convertToAuditEvent(persistentAuditEvents); + } + + @Override + public List find(String principal, Date after, String type) { + Iterable persistentAuditEvents = + persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, LocalDateTime.from(after.toInstant()), type); + return auditEventConverter.convertToAuditEvent(persistentAuditEvents); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void add(AuditEvent event) { + if (!AUTHORIZATION_FAILURE.equals(event.getType()) && + !Constants.ANONYMOUS_USER.equals(event.getPrincipal())) { + + PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent(); + persistentAuditEvent.setPrincipal(event.getPrincipal()); + persistentAuditEvent.setAuditEventType(event.getType()); + Instant instant = Instant.ofEpochMilli(event.getTimestamp().getTime()); + persistentAuditEvent.setAuditEventDate(LocalDateTime.ofInstant(instant, ZoneId.systemDefault())); + persistentAuditEvent.setData(auditEventConverter.convertDataToStrings(event.getData())); + persistenceAuditEventRepository.save(persistentAuditEvent); + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java b/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java new file mode 100644 index 0000000000..f4b7f1c5bf --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java @@ -0,0 +1,26 @@ +package com.baeldung.repository; + +import com.baeldung.domain.PersistentAuditEvent; + +import java.time.LocalDateTime; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +/** + * Spring Data JPA repository for the PersistentAuditEvent entity. + */ +public interface PersistenceAuditEventRepository extends JpaRepository { + + List findByPrincipal(String principal); + + List findByAuditEventDateAfter(LocalDateTime after); + + List findByPrincipalAndAuditEventDateAfter(String principal, LocalDateTime after); + + List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principle, LocalDateTime after, String type); + + Page findAllByAuditEventDateBetween(LocalDateTime fromDate, LocalDateTime toDate, Pageable pageable); +} diff --git a/jhipster/src/main/java/com/baeldung/repository/PostRepository.java b/jhipster/src/main/java/com/baeldung/repository/PostRepository.java new file mode 100644 index 0000000000..22e8aa1372 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/PostRepository.java @@ -0,0 +1,18 @@ +package com.baeldung.repository; + +import com.baeldung.domain.Post; + +import org.springframework.data.jpa.repository.*; + +import java.util.List; + +/** + * Spring Data JPA repository for the Post entity. + */ +@SuppressWarnings("unused") +public interface PostRepository extends JpaRepository { + + @Query("select post from Post post where post.creator.login = ?#{principal.username}") + List findByCreatorIsCurrentUser(); + +} diff --git a/jhipster/src/main/java/com/baeldung/repository/UserRepository.java b/jhipster/src/main/java/com/baeldung/repository/UserRepository.java new file mode 100644 index 0000000000..a8f8149910 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/UserRepository.java @@ -0,0 +1,37 @@ +package com.baeldung.repository; + +import com.baeldung.domain.User; + +import java.time.ZonedDateTime; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * Spring Data JPA repository for the User entity. + */ +public interface UserRepository extends JpaRepository { + + Optional findOneByActivationKey(String activationKey); + + List findAllByActivatedIsFalseAndCreatedDateBefore(ZonedDateTime dateTime); + + Optional findOneByResetKey(String resetKey); + + Optional findOneByEmail(String email); + + Optional findOneByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + User findOneWithAuthoritiesById(Long id); + + @EntityGraph(attributePaths = "authorities") + Optional findOneWithAuthoritiesByLogin(String login); + + Page findAllByLoginNot(Pageable pageable, String login); +} diff --git a/jhipster/src/main/java/com/baeldung/repository/package-info.java b/jhipster/src/main/java/com/baeldung/repository/package-info.java new file mode 100644 index 0000000000..9d1fb837c3 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Data JPA repositories. + */ +package com.baeldung.repository; diff --git a/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java b/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java new file mode 100644 index 0000000000..40cf54e4f6 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java @@ -0,0 +1,16 @@ +package com.baeldung.security; + +/** + * Constants for Spring Security authorities. + */ +public final class AuthoritiesConstants { + + public static final String ADMIN = "ROLE_ADMIN"; + + public static final String USER = "ROLE_USER"; + + public static final String ANONYMOUS = "ROLE_ANONYMOUS"; + + private AuthoritiesConstants() { + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java b/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java new file mode 100644 index 0000000000..6ce07739f7 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java @@ -0,0 +1,51 @@ +package com.baeldung.security; + +import com.baeldung.domain.User; +import com.baeldung.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Authenticate a user from the database. + */ +@Component("userDetailsService") +public class DomainUserDetailsService implements UserDetailsService { + + private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class); + + private final UserRepository userRepository; + + public DomainUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional + public UserDetails loadUserByUsername(final String login) { + log.debug("Authenticating {}", login); + String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); + Optional userFromDatabase = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin); + return userFromDatabase.map(user -> { + if (!user.getActivated()) { + throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); + } + List grantedAuthorities = user.getAuthorities().stream() + .map(authority -> new SimpleGrantedAuthority(authority.getName())) + .collect(Collectors.toList()); + return new org.springframework.security.core.userdetails.User(lowercaseLogin, + user.getPassword(), + grantedAuthorities); + }).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + + "database")); + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java b/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java new file mode 100644 index 0000000000..96aaef3805 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java @@ -0,0 +1,68 @@ +package com.baeldung.security; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * Utility class for Spring Security. + */ +public final class SecurityUtils { + + private SecurityUtils() { + } + + /** + * Get the login of the current user. + * + * @return the login of the current user + */ + public static String getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication authentication = securityContext.getAuthentication(); + String userName = null; + if (authentication != null) { + if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + userName = springSecurityUser.getUsername(); + } else if (authentication.getPrincipal() instanceof String) { + userName = (String) authentication.getPrincipal(); + } + } + return userName; + } + + /** + * Check if a user is authenticated. + * + * @return true if the user is authenticated, false otherwise + */ + public static boolean isAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + return authentication.getAuthorities().stream() + .noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)); + } + return false; + } + + /** + * If the current user has a specific authority (security role). + * + *

The name of this method comes from the isUserInRole() method in the Servlet API

+ * + * @param authority the authority to check + * @return true if the current user has the authority, false otherwise + */ + public static boolean isCurrentUserInRole(String authority) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + return authentication.getAuthorities().stream() + .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)); + } + return false; + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java b/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java new file mode 100644 index 0000000000..d59917153c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java @@ -0,0 +1,19 @@ +package com.baeldung.security; + +import com.baeldung.config.Constants; + +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; + +/** + * Implementation of AuditorAware based on Spring Security. + */ +@Component +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public String getCurrentAuditor() { + String userName = SecurityUtils.getCurrentUserLogin(); + return userName != null ? userName : Constants.SYSTEM_ACCOUNT; + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java b/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java new file mode 100644 index 0000000000..7265188cad --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java @@ -0,0 +1,19 @@ +package com.baeldung.security; + +import org.springframework.security.core.AuthenticationException; + +/** + * This exception is thrown in case of a not activated user trying to authenticate. + */ +public class UserNotActivatedException extends AuthenticationException { + + private static final long serialVersionUID = 1L; + + public UserNotActivatedException(String message) { + super(message); + } + + public UserNotActivatedException(String message, Throwable t) { + super(message, t); + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java b/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java new file mode 100644 index 0000000000..5720812b9f --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java @@ -0,0 +1,23 @@ +package com.baeldung.security.jwt; + +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class JWTConfigurer extends SecurityConfigurerAdapter { + + public static final String AUTHORIZATION_HEADER = "Authorization"; + + private TokenProvider tokenProvider; + + public JWTConfigurer(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void configure(HttpSecurity http) throws Exception { + JWTFilter customFilter = new JWTFilter(tokenProvider); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java b/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java new file mode 100644 index 0000000000..89b1947e05 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java @@ -0,0 +1,58 @@ +package com.baeldung.security.jwt; + +import java.io.IOException; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +import io.jsonwebtoken.ExpiredJwtException; + +/** + * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is + * found. + */ +public class JWTFilter extends GenericFilterBean { + + private final Logger log = LoggerFactory.getLogger(JWTFilter.class); + + private TokenProvider tokenProvider; + + public JWTFilter(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + try { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String jwt = resolveToken(httpServletRequest); + if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { + Authentication authentication = this.tokenProvider.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(servletRequest, servletResponse); + } catch (ExpiredJwtException eje) { + log.info("Security exception for user {} - {}", + eje.getClaims().getSubject(), eje.getMessage()); + + log.trace("Security exception trace: {}", eje); + ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + + private String resolveToken(HttpServletRequest request){ + String bearerToken = request.getHeader(JWTConfigurer.AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7, bearerToken.length()); + } + return null; + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java b/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java new file mode 100644 index 0000000000..3ba4d7c793 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java @@ -0,0 +1,109 @@ +package com.baeldung.security.jwt; + +import io.github.jhipster.config.JHipsterProperties; + +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.*; + +@Component +public class TokenProvider { + + private final Logger log = LoggerFactory.getLogger(TokenProvider.class); + + private static final String AUTHORITIES_KEY = "auth"; + + private String secretKey; + + private long tokenValidityInMilliseconds; + + private long tokenValidityInMillisecondsForRememberMe; + + private final JHipsterProperties jHipsterProperties; + + public TokenProvider(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @PostConstruct + public void init() { + this.secretKey = + jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret(); + + this.tokenValidityInMilliseconds = + 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds(); + this.tokenValidityInMillisecondsForRememberMe = + 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe(); + } + + public String createToken(Authentication authentication, Boolean rememberMe) { + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + long now = (new Date()).getTime(); + Date validity; + if (rememberMe) { + validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); + } else { + validity = new Date(now + this.tokenValidityInMilliseconds); + } + + return Jwts.builder() + .setSubject(authentication.getName()) + .claim(AUTHORITIES_KEY, authorities) + .signWith(SignatureAlgorithm.HS512, secretKey) + .setExpiration(validity) + .compact(); + } + + public Authentication getAuthentication(String token) { + Claims claims = Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody(); + + Collection authorities = + Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + User principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + public boolean validateToken(String authToken) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken); + return true; + } catch (SignatureException e) { + log.info("Invalid JWT signature."); + log.trace("Invalid JWT signature trace: {}", e); + } catch (MalformedJwtException e) { + log.info("Invalid JWT token."); + log.trace("Invalid JWT token trace: {}", e); + } catch (ExpiredJwtException e) { + log.info("Expired JWT token."); + log.trace("Expired JWT token trace: {}", e); + } catch (UnsupportedJwtException e) { + log.info("Unsupported JWT token."); + log.trace("Unsupported JWT token trace: {}", e); + } catch (IllegalArgumentException e) { + log.info("JWT token compact of handler are invalid."); + log.trace("JWT token compact of handler are invalid trace: {}", e); + } + return false; + } +} diff --git a/jhipster/src/main/java/com/baeldung/security/package-info.java b/jhipster/src/main/java/com/baeldung/security/package-info.java new file mode 100644 index 0000000000..1e29c15b4e --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Security configuration. + */ +package com.baeldung.security; diff --git a/jhipster/src/main/java/com/baeldung/service/AuditEventService.java b/jhipster/src/main/java/com/baeldung/service/AuditEventService.java new file mode 100644 index 0000000000..8701854922 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/AuditEventService.java @@ -0,0 +1,50 @@ +package com.baeldung.service; + +import com.baeldung.config.audit.AuditEventConverter; +import com.baeldung.repository.PersistenceAuditEventRepository; +import java.time.LocalDateTime; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * Service for managing audit events. + *

+ * This is the default implementation to support SpringBoot Actuator AuditEventRepository + *

+ */ +@Service +@Transactional +public class AuditEventService { + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + public AuditEventService( + PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter) { + + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + } + + public Page findAll(Pageable pageable) { + return persistenceAuditEventRepository.findAll(pageable) + .map(auditEventConverter::convertToAuditEvent); + } + + public Page findByDates(LocalDateTime fromDate, LocalDateTime toDate, Pageable pageable) { + return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable) + .map(auditEventConverter::convertToAuditEvent); + } + + public Optional find(Long id) { + return Optional.ofNullable(persistenceAuditEventRepository.findOne(id)).map + (auditEventConverter::convertToAuditEvent); + } +} diff --git a/jhipster/src/main/java/com/baeldung/service/MailService.java b/jhipster/src/main/java/com/baeldung/service/MailService.java new file mode 100644 index 0000000000..9f85531da7 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/MailService.java @@ -0,0 +1,108 @@ +package com.baeldung.service; + +import com.baeldung.domain.User; + +import io.github.jhipster.config.JHipsterProperties; + +import org.apache.commons.lang3.CharEncoding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring4.SpringTemplateEngine; + +import javax.mail.internet.MimeMessage; +import java.util.Locale; + +/** + * Service for sending e-mails. + *

+ * We use the @Async annotation to send e-mails asynchronously. + *

+ */ +@Service +public class MailService { + + private final Logger log = LoggerFactory.getLogger(MailService.class); + + private static final String USER = "user"; + + private static final String BASE_URL = "baseUrl"; + + private final JHipsterProperties jHipsterProperties; + + private final JavaMailSender javaMailSender; + + private final MessageSource messageSource; + + private final SpringTemplateEngine templateEngine; + + public MailService(JHipsterProperties jHipsterProperties, JavaMailSender javaMailSender, + MessageSource messageSource, SpringTemplateEngine templateEngine) { + + this.jHipsterProperties = jHipsterProperties; + this.javaMailSender = javaMailSender; + this.messageSource = messageSource; + this.templateEngine = templateEngine; + } + + @Async + public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) { + log.debug("Send e-mail[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}", + isMultipart, isHtml, to, subject, content); + + // Prepare message using a Spring helper + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, CharEncoding.UTF_8); + message.setTo(to); + message.setFrom(jHipsterProperties.getMail().getFrom()); + message.setSubject(subject); + message.setText(content, isHtml); + javaMailSender.send(mimeMessage); + log.debug("Sent e-mail to User '{}'", to); + } catch (Exception e) { + log.warn("E-mail could not be sent to user '{}'", to, e); + } + } + + @Async + public void sendActivationEmail(User user) { + log.debug("Sending activation e-mail to '{}'", user.getEmail()); + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + context.setVariable(USER, user); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process("activationEmail", context); + String subject = messageSource.getMessage("email.activation.title", null, locale); + sendEmail(user.getEmail(), subject, content, false, true); + } + + @Async + public void sendCreationEmail(User user) { + log.debug("Sending creation e-mail to '{}'", user.getEmail()); + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + context.setVariable(USER, user); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process("creationEmail", context); + String subject = messageSource.getMessage("email.activation.title", null, locale); + sendEmail(user.getEmail(), subject, content, false, true); + } + + @Async + public void sendPasswordResetMail(User user) { + log.debug("Sending password reset e-mail to '{}'", user.getEmail()); + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + context.setVariable(USER, user); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process("passwordResetEmail", context); + String subject = messageSource.getMessage("email.reset.title", null, locale); + sendEmail(user.getEmail(), subject, content, false, true); + } +} diff --git a/jhipster/src/main/java/com/baeldung/service/UserService.java b/jhipster/src/main/java/com/baeldung/service/UserService.java new file mode 100644 index 0000000000..7b9096e0da --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/UserService.java @@ -0,0 +1,228 @@ +package com.baeldung.service; + +import com.baeldung.domain.Authority; +import com.baeldung.domain.User; +import com.baeldung.repository.AuthorityRepository; +import com.baeldung.config.Constants; +import com.baeldung.repository.UserRepository; +import com.baeldung.security.AuthoritiesConstants; +import com.baeldung.security.SecurityUtils; +import com.baeldung.service.util.RandomUtil; +import com.baeldung.service.dto.UserDTO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.ZonedDateTime; +import java.util.*; + +/** + * Service class for managing users. + */ +@Service +@Transactional +public class UserService { + + private final Logger log = LoggerFactory.getLogger(UserService.class); + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + private final AuthorityRepository authorityRepository; + + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.authorityRepository = authorityRepository; + } + + public Optional activateRegistration(String key) { + log.debug("Activating user for activation key {}", key); + return userRepository.findOneByActivationKey(key) + .map(user -> { + // activate given user for the registration key. + user.setActivated(true); + user.setActivationKey(null); + log.debug("Activated user: {}", user); + return user; + }); + } + + public Optional completePasswordReset(String newPassword, String key) { + log.debug("Reset user password for reset key {}", key); + + return userRepository.findOneByResetKey(key) + .filter(user -> { + ZonedDateTime oneDayAgo = ZonedDateTime.now().minusHours(24); + return user.getResetDate().isAfter(oneDayAgo); + }) + .map(user -> { + user.setPassword(passwordEncoder.encode(newPassword)); + user.setResetKey(null); + user.setResetDate(null); + return user; + }); + } + + public Optional requestPasswordReset(String mail) { + return userRepository.findOneByEmail(mail) + .filter(User::getActivated) + .map(user -> { + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(ZonedDateTime.now()); + return user; + }); + } + + public User createUser(String login, String password, String firstName, String lastName, String email, + String imageUrl, String langKey) { + + User newUser = new User(); + Authority authority = authorityRepository.findOne(AuthoritiesConstants.USER); + Set authorities = new HashSet<>(); + String encryptedPassword = passwordEncoder.encode(password); + newUser.setLogin(login); + // new user gets initially a generated password + newUser.setPassword(encryptedPassword); + newUser.setFirstName(firstName); + newUser.setLastName(lastName); + newUser.setEmail(email); + newUser.setImageUrl(imageUrl); + newUser.setLangKey(langKey); + // new user is not active + newUser.setActivated(false); + // new user gets registration key + newUser.setActivationKey(RandomUtil.generateActivationKey()); + authorities.add(authority); + newUser.setAuthorities(authorities); + userRepository.save(newUser); + log.debug("Created Information for User: {}", newUser); + return newUser; + } + + public User createUser(UserDTO userDTO) { + User user = new User(); + user.setLogin(userDTO.getLogin()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setImageUrl(userDTO.getImageUrl()); + if (userDTO.getLangKey() == null) { + user.setLangKey("en"); // default language + } else { + user.setLangKey(userDTO.getLangKey()); + } + if (userDTO.getAuthorities() != null) { + Set authorities = new HashSet<>(); + userDTO.getAuthorities().forEach( + authority -> authorities.add(authorityRepository.findOne(authority)) + ); + user.setAuthorities(authorities); + } + String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()); + user.setPassword(encryptedPassword); + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(ZonedDateTime.now()); + user.setActivated(true); + userRepository.save(user); + log.debug("Created Information for User: {}", user); + return user; + } + + /** + * Update basic information (first name, last name, email, language) for the current user. + */ + public void updateUser(String firstName, String lastName, String email, String langKey) { + userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> { + user.setFirstName(firstName); + user.setLastName(lastName); + user.setEmail(email); + user.setLangKey(langKey); + log.debug("Changed Information for User: {}", user); + }); + } + + /** + * Update all information for a specific user, and return the modified user. + */ + public Optional updateUser(UserDTO userDTO) { + return Optional.of(userRepository + .findOne(userDTO.getId())) + .map(user -> { + user.setLogin(userDTO.getLogin()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set managedAuthorities = user.getAuthorities(); + managedAuthorities.clear(); + userDTO.getAuthorities().stream() + .map(authorityRepository::findOne) + .forEach(managedAuthorities::add); + log.debug("Changed Information for User: {}", user); + return user; + }) + .map(UserDTO::new); + } + + public void deleteUser(String login) { + userRepository.findOneByLogin(login).ifPresent(user -> { + userRepository.delete(user); + log.debug("Deleted User: {}", user); + }); + } + + public void changePassword(String password) { + userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> { + String encryptedPassword = passwordEncoder.encode(password); + user.setPassword(encryptedPassword); + log.debug("Changed password for User: {}", user); + }); + } + + @Transactional(readOnly = true) + public Page getAllManagedUsers(Pageable pageable) { + return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthoritiesByLogin(String login) { + return userRepository.findOneWithAuthoritiesByLogin(login); + } + + @Transactional(readOnly = true) + public User getUserWithAuthorities(Long id) { + return userRepository.findOneWithAuthoritiesById(id); + } + + @Transactional(readOnly = true) + public User getUserWithAuthorities() { + return userRepository.findOneWithAuthoritiesByLogin(SecurityUtils.getCurrentUserLogin()).orElse(null); + } + + + /** + * Not activated users should be automatically deleted after 3 days. + *

+ * This is scheduled to get fired everyday, at 01:00 (am). + *

+ */ + @Scheduled(cron = "0 0 1 * * ?") + public void removeNotActivatedUsers() { + ZonedDateTime now = ZonedDateTime.now(); + List users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3)); + for (User user : users) { + log.debug("Deleting not activated user {}", user.getLogin()); + userRepository.delete(user); + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java b/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java new file mode 100644 index 0000000000..1080bab7b8 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java @@ -0,0 +1,167 @@ +package com.baeldung.service.dto; + +import com.baeldung.config.Constants; + +import com.baeldung.domain.Authority; +import com.baeldung.domain.User; + +import org.hibernate.validator.constraints.Email; + +import javax.validation.constraints.*; +import java.time.ZonedDateTime; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A DTO representing a user, with his authorities. + */ +public class UserDTO { + + private Long id; + + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + private String login; + + @Size(max = 50) + private String firstName; + + @Size(max = 50) + private String lastName; + + @Email + @Size(min = 5, max = 100) + private String email; + + @Size(max = 256) + private String imageUrl; + + private boolean activated = false; + + @Size(min = 2, max = 5) + private String langKey; + + private String createdBy; + + private ZonedDateTime createdDate; + + private String lastModifiedBy; + + private ZonedDateTime lastModifiedDate; + + private Set authorities; + + public UserDTO() { + // Empty constructor needed for MapStruct. + } + + public UserDTO(User user) { + this(user.getId(), user.getLogin(), user.getFirstName(), user.getLastName(), + user.getEmail(), user.getActivated(), user.getImageUrl(), user.getLangKey(), + user.getCreatedBy(), user.getCreatedDate(), user.getLastModifiedBy(), user.getLastModifiedDate(), + user.getAuthorities().stream().map(Authority::getName) + .collect(Collectors.toSet())); + } + + public UserDTO(Long id, String login, String firstName, String lastName, + String email, boolean activated, String imageUrl, String langKey, + String createdBy, ZonedDateTime createdDate, String lastModifiedBy, ZonedDateTime lastModifiedDate, + Set authorities) { + + this.id = id; + this.login = login; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.activated = activated; + this.imageUrl = imageUrl; + this.langKey = langKey; + this.createdBy = createdBy; + this.createdDate = createdDate; + this.lastModifiedBy = lastModifiedBy; + this.lastModifiedDate = lastModifiedDate; + this.authorities = authorities; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getEmail() { + return email; + } + + public String getImageUrl() { + return imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public String getLangKey() { + return langKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public ZonedDateTime getCreatedDate() { + return createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public ZonedDateTime getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(ZonedDateTime lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public Set getAuthorities() { + return authorities; + } + + @Override + public String toString() { + return "UserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + } +} diff --git a/jhipster/src/main/java/com/baeldung/service/dto/package-info.java b/jhipster/src/main/java/com/baeldung/service/dto/package-info.java new file mode 100644 index 0000000000..12d048eb7a --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data Transfer Objects. + */ +package com.baeldung.service.dto; diff --git a/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java b/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java new file mode 100644 index 0000000000..aaada37162 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java @@ -0,0 +1,55 @@ +package com.baeldung.service.mapper; + +import com.baeldung.domain.Authority; +import com.baeldung.domain.User; +import com.baeldung.service.dto.UserDTO; +import org.mapstruct.*; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Mapper for the entity User and its DTO UserDTO. + */ +@Mapper(componentModel = "spring", uses = {}) +public interface UserMapper { + + UserDTO userToUserDTO(User user); + + List usersToUserDTOs(List users); + + @Mapping(target = "createdBy", ignore = true) + @Mapping(target = "createdDate", ignore = true) + @Mapping(target = "lastModifiedBy", ignore = true) + @Mapping(target = "lastModifiedDate", ignore = true) + @Mapping(target = "activationKey", ignore = true) + @Mapping(target = "resetKey", ignore = true) + @Mapping(target = "resetDate", ignore = true) + @Mapping(target = "password", ignore = true) + User userDTOToUser(UserDTO userDTO); + + List userDTOsToUsers(List userDTOs); + + default User userFromId(Long id) { + if (id == null) { + return null; + } + User user = new User(); + user.setId(id); + return user; + } + + default Set stringsFromAuthorities (Set authorities) { + return authorities.stream().map(Authority::getName) + .collect(Collectors.toSet()); + } + + default Set authoritiesFromStrings(Set strings) { + return strings.stream().map(string -> { + Authority auth = new Authority(); + auth.setName(string); + return auth; + }).collect(Collectors.toSet()); + } +} diff --git a/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java b/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java new file mode 100644 index 0000000000..75f8d24f08 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java @@ -0,0 +1,4 @@ +/** + * MapStruct mappers for mapping domain objects and Data Transfer Objects. + */ +package com.baeldung.service.mapper; diff --git a/jhipster/src/main/java/com/baeldung/service/package-info.java b/jhipster/src/main/java/com/baeldung/service/package-info.java new file mode 100644 index 0000000000..0695bc1473 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service layer beans. + */ +package com.baeldung.service; diff --git a/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java b/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java new file mode 100644 index 0000000000..1f4d9f8b5d --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java @@ -0,0 +1,41 @@ +package com.baeldung.service.util; + +import org.apache.commons.lang3.RandomStringUtils; + +/** + * Utility class for generating random Strings. + */ +public final class RandomUtil { + + private static final int DEF_COUNT = 20; + + private RandomUtil() { + } + + /** + * Generate a password. + * + * @return the generated password + */ + public static String generatePassword() { + return RandomStringUtils.randomAlphanumeric(DEF_COUNT); + } + + /** + * Generate an activation key. + * + * @return the generated activation key + */ + public static String generateActivationKey() { + return RandomStringUtils.randomNumeric(DEF_COUNT); + } + + /** + * Generate a reset key. + * + * @return the generated reset key + */ + public static String generateResetKey() { + return RandomStringUtils.randomNumeric(DEF_COUNT); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java b/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java new file mode 100644 index 0000000000..c1009fc918 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java @@ -0,0 +1,202 @@ +package com.baeldung.web.rest; + +import com.codahale.metrics.annotation.Timed; + +import com.baeldung.domain.User; +import com.baeldung.repository.UserRepository; +import com.baeldung.security.SecurityUtils; +import com.baeldung.service.MailService; +import com.baeldung.service.UserService; +import com.baeldung.service.dto.UserDTO; +import com.baeldung.web.rest.vm.KeyAndPasswordVM; +import com.baeldung.web.rest.vm.ManagedUserVM; +import com.baeldung.web.rest.util.HeaderUtil; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.*; + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +public class AccountResource { + + private final Logger log = LoggerFactory.getLogger(AccountResource.class); + + private final UserRepository userRepository; + + private final UserService userService; + + private final MailService mailService; + + public AccountResource(UserRepository userRepository, UserService userService, + MailService mailService) { + + this.userRepository = userRepository; + this.userService = userService; + this.mailService = mailService; + } + + /** + * POST /register : register the user. + * + * @param managedUserVM the managed user View Model + * @return the ResponseEntity with status 201 (Created) if the user is registered or 400 (Bad Request) if the login or e-mail is already in use + */ + @PostMapping(path = "/register", + produces={MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE}) + @Timed + public ResponseEntity registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { + + HttpHeaders textPlainHeaders = new HttpHeaders(); + textPlainHeaders.setContentType(MediaType.TEXT_PLAIN); + + return userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase()) + .map(user -> new ResponseEntity<>("login already in use", textPlainHeaders, HttpStatus.BAD_REQUEST)) + .orElseGet(() -> userRepository.findOneByEmail(managedUserVM.getEmail()) + .map(user -> new ResponseEntity<>("e-mail address already in use", textPlainHeaders, HttpStatus.BAD_REQUEST)) + .orElseGet(() -> { + User user = userService + .createUser(managedUserVM.getLogin(), managedUserVM.getPassword(), + managedUserVM.getFirstName(), managedUserVM.getLastName(), + managedUserVM.getEmail().toLowerCase(), managedUserVM.getImageUrl(), managedUserVM.getLangKey()); + + mailService.sendActivationEmail(user); + return new ResponseEntity<>(HttpStatus.CREATED); + }) + ); + } + + /** + * GET /activate : activate the registered user. + * + * @param key the activation key + * @return the ResponseEntity with status 200 (OK) and the activated user in body, or status 500 (Internal Server Error) if the user couldn't be activated + */ + @GetMapping("/activate") + @Timed + public ResponseEntity activateAccount(@RequestParam(value = "key") String key) { + return userService.activateRegistration(key) + .map(user -> new ResponseEntity(HttpStatus.OK)) + .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + + /** + * GET /authenticate : check if the user is authenticated, and return its login. + * + * @param request the HTTP request + * @return the login if the user is authenticated + */ + @GetMapping("/authenticate") + @Timed + public String isAuthenticated(HttpServletRequest request) { + log.debug("REST request to check if the current user is authenticated"); + return request.getRemoteUser(); + } + + /** + * GET /account : get the current user. + * + * @return the ResponseEntity with status 200 (OK) and the current user in body, or status 500 (Internal Server Error) if the user couldn't be returned + */ + @GetMapping("/account") + @Timed + public ResponseEntity getAccount() { + return Optional.ofNullable(userService.getUserWithAuthorities()) + .map(user -> new ResponseEntity<>(new UserDTO(user), HttpStatus.OK)) + .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + + /** + * POST /account : update the current user information. + * + * @param userDTO the current user information + * @return the ResponseEntity with status 200 (OK), or status 400 (Bad Request) or 500 (Internal Server Error) if the user couldn't be updated + */ + @PostMapping("/account") + @Timed + public ResponseEntity saveAccount(@Valid @RequestBody UserDTO userDTO) { + Optional existingUser = userRepository.findOneByEmail(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userDTO.getLogin()))) { + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert("user-management", "emailexists", "Email already in use")).body(null); + } + return userRepository + .findOneByLogin(SecurityUtils.getCurrentUserLogin()) + .map(u -> { + userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(), + userDTO.getLangKey()); + return new ResponseEntity(HttpStatus.OK); + }) + .orElseGet(() -> new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + + /** + * POST /account/change_password : changes the current user's password + * + * @param password the new password + * @return the ResponseEntity with status 200 (OK), or status 400 (Bad Request) if the new password is not strong enough + */ + @PostMapping(path = "/account/change_password", + produces = MediaType.TEXT_PLAIN_VALUE) + @Timed + public ResponseEntity changePassword(@RequestBody String password) { + if (!checkPasswordLength(password)) { + return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST); + } + userService.changePassword(password); + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * POST /account/reset_password/init : Send an e-mail to reset the password of the user + * + * @param mail the mail of the user + * @return the ResponseEntity with status 200 (OK) if the e-mail was sent, or status 400 (Bad Request) if the e-mail address is not registered + */ + @PostMapping(path = "/account/reset_password/init", + produces = MediaType.TEXT_PLAIN_VALUE) + @Timed + public ResponseEntity requestPasswordReset(@RequestBody String mail) { + return userService.requestPasswordReset(mail) + .map(user -> { + mailService.sendPasswordResetMail(user); + return new ResponseEntity<>("e-mail was sent", HttpStatus.OK); + }).orElse(new ResponseEntity<>("e-mail address not registered", HttpStatus.BAD_REQUEST)); + } + + /** + * POST /account/reset_password/finish : Finish to reset the password of the user + * + * @param keyAndPassword the generated key and the new password + * @return the ResponseEntity with status 200 (OK) if the password has been reset, + * or status 400 (Bad Request) or 500 (Internal Server Error) if the password could not be reset + */ + @PostMapping(path = "/account/reset_password/finish", + produces = MediaType.TEXT_PLAIN_VALUE) + @Timed + public ResponseEntity finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { + if (!checkPasswordLength(keyAndPassword.getNewPassword())) { + return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST); + } + return userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()) + .map(user -> new ResponseEntity(HttpStatus.OK)) + .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + + private boolean checkPasswordLength(String password) { + return !StringUtils.isEmpty(password) && + password.length() >= ManagedUserVM.PASSWORD_MIN_LENGTH && + password.length() <= ManagedUserVM.PASSWORD_MAX_LENGTH; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java b/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java new file mode 100644 index 0000000000..3101d134ed --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java @@ -0,0 +1,76 @@ +package com.baeldung.web.rest; + +import com.baeldung.service.AuditEventService; +import com.baeldung.web.rest.util.PaginationUtil; + +import io.github.jhipster.web.util.ResponseUtil; +import io.swagger.annotations.ApiParam; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URISyntaxException; +import java.time.LocalDate; +import java.util.List; + +/** + * REST controller for getting the audit events. + */ +@RestController +@RequestMapping("/management/audits") +public class AuditResource { + + private final AuditEventService auditEventService; + + public AuditResource(AuditEventService auditEventService) { + this.auditEventService = auditEventService; + } + + /** + * GET /audits : get a page of AuditEvents. + * + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body + */ + @GetMapping + public ResponseEntity> getAll(@ApiParam Pageable pageable) { + Page page = auditEventService.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits"); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * GET /audits : get a page of AuditEvents between the fromDate and toDate. + * + * @param fromDate the start of the time period of AuditEvents to get + * @param toDate the end of the time period of AuditEvents to get + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body + */ + + @GetMapping(params = {"fromDate", "toDate"}) + public ResponseEntity> getByDates( + @RequestParam(value = "fromDate") LocalDate fromDate, + @RequestParam(value = "toDate") LocalDate toDate, + @ApiParam Pageable pageable) { + + Page page = auditEventService.findByDates(fromDate.atTime(0, 0), toDate.atTime(23, 59), pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits"); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * GET /audits/:id : get an AuditEvent by id. + * + * @param id the id of the entity to get + * @return the ResponseEntity with status 200 (OK) and the AuditEvent in body, or status 404 (Not Found) + */ + @GetMapping("/{id:.+}") + public ResponseEntity get(@PathVariable Long id) { + return ResponseUtil.wrapOrNotFound(auditEventService.find(id)); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java b/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java new file mode 100644 index 0000000000..3e3f13b5e9 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java @@ -0,0 +1,129 @@ +package com.baeldung.web.rest; + +import com.codahale.metrics.annotation.Timed; +import com.baeldung.domain.Comment; + +import com.baeldung.repository.CommentRepository; +import com.baeldung.web.rest.util.HeaderUtil; +import com.baeldung.web.rest.util.PaginationUtil; +import io.swagger.annotations.ApiParam; +import io.github.jhipster.web.util.ResponseUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; + +/** + * REST controller for managing Comment. + */ +@RestController +@RequestMapping("/api") +public class CommentResource { + + private final Logger log = LoggerFactory.getLogger(CommentResource.class); + + private static final String ENTITY_NAME = "comment"; + + private final CommentRepository commentRepository; + + public CommentResource(CommentRepository commentRepository) { + this.commentRepository = commentRepository; + } + + /** + * POST /comments : Create a new comment. + * + * @param comment the comment to create + * @return the ResponseEntity with status 201 (Created) and with body the new comment, or with status 400 (Bad Request) if the comment has already an ID + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PostMapping("/comments") + @Timed + public ResponseEntity createComment(@Valid @RequestBody Comment comment) throws URISyntaxException { + log.debug("REST request to save Comment : {}", comment); + if (comment.getId() != null) { + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new comment cannot already have an ID")).body(null); + } + Comment result = commentRepository.save(comment); + return ResponseEntity.created(new URI("/api/comments/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * PUT /comments : Updates an existing comment. + * + * @param comment the comment to update + * @return the ResponseEntity with status 200 (OK) and with body the updated comment, + * or with status 400 (Bad Request) if the comment is not valid, + * or with status 500 (Internal Server Error) if the comment couldnt be updated + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PutMapping("/comments") + @Timed + public ResponseEntity updateComment(@Valid @RequestBody Comment comment) throws URISyntaxException { + log.debug("REST request to update Comment : {}", comment); + if (comment.getId() == null) { + return createComment(comment); + } + Comment result = commentRepository.save(comment); + return ResponseEntity.ok() + .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, comment.getId().toString())) + .body(result); + } + + /** + * GET /comments : get all the comments. + * + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and the list of comments in body + * @throws URISyntaxException if there is an error to generate the pagination HTTP headers + */ + @GetMapping("/comments") + @Timed + public ResponseEntity> getAllComments(@ApiParam Pageable pageable) { + log.debug("REST request to get a page of Comments"); + Page page = commentRepository.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/comments"); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * GET /comments/:id : get the "id" comment. + * + * @param id the id of the comment to retrieve + * @return the ResponseEntity with status 200 (OK) and with body the comment, or with status 404 (Not Found) + */ + @GetMapping("/comments/{id}") + @Timed + public ResponseEntity getComment(@PathVariable Long id) { + log.debug("REST request to get Comment : {}", id); + Comment comment = commentRepository.findOne(id); + return ResponseUtil.wrapOrNotFound(Optional.ofNullable(comment)); + } + + /** + * DELETE /comments/:id : delete the "id" comment. + * + * @param id the id of the comment to delete + * @return the ResponseEntity with status 200 (OK) + */ + @DeleteMapping("/comments/{id}") + @Timed + public ResponseEntity deleteComment(@PathVariable Long id) { + log.debug("REST request to delete Comment : {}", id); + commentRepository.delete(id); + return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build(); + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java b/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java new file mode 100644 index 0000000000..c0804851f3 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java @@ -0,0 +1,24 @@ +package com.baeldung.web.rest; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Object to return as body in JWT Authentication. + */ +public class JWTToken { + + private String idToken; + + public JWTToken(String idToken) { + this.idToken = idToken; + } + + @JsonProperty("id_token") + public String getIdToken() { + return idToken; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java b/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java new file mode 100644 index 0000000000..3d556e2609 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java @@ -0,0 +1,39 @@ +package com.baeldung.web.rest; + +import com.baeldung.web.rest.vm.LoggerVM; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import com.codahale.metrics.annotation.Timed; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Controller for view and managing Log Level at runtime. + */ +@RestController +@RequestMapping("/management") +public class LogsResource { + + @GetMapping("/logs") + @Timed + public List getList() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + return context.getLoggerList() + .stream() + .map(LoggerVM::new) + .collect(Collectors.toList()); + } + + @PutMapping("/logs") + @ResponseStatus(HttpStatus.NO_CONTENT) + @Timed + public void changeLevel(@RequestBody LoggerVM jsonLogger) { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel())); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java b/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java new file mode 100644 index 0000000000..7505e1fa46 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java @@ -0,0 +1,129 @@ +package com.baeldung.web.rest; + +import com.codahale.metrics.annotation.Timed; +import com.baeldung.domain.Post; + +import com.baeldung.repository.PostRepository; +import com.baeldung.web.rest.util.HeaderUtil; +import com.baeldung.web.rest.util.PaginationUtil; +import io.swagger.annotations.ApiParam; +import io.github.jhipster.web.util.ResponseUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; + +/** + * REST controller for managing Post. + */ +@RestController +@RequestMapping("/api") +public class PostResource { + + private final Logger log = LoggerFactory.getLogger(PostResource.class); + + private static final String ENTITY_NAME = "post"; + + private final PostRepository postRepository; + + public PostResource(PostRepository postRepository) { + this.postRepository = postRepository; + } + + /** + * POST /posts : Create a new post. + * + * @param post the post to create + * @return the ResponseEntity with status 201 (Created) and with body the new post, or with status 400 (Bad Request) if the post has already an ID + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PostMapping("/posts") + @Timed + public ResponseEntity createPost(@Valid @RequestBody Post post) throws URISyntaxException { + log.debug("REST request to save Post : {}", post); + if (post.getId() != null) { + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new post cannot already have an ID")).body(null); + } + Post result = postRepository.save(post); + return ResponseEntity.created(new URI("/api/posts/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * PUT /posts : Updates an existing post. + * + * @param post the post to update + * @return the ResponseEntity with status 200 (OK) and with body the updated post, + * or with status 400 (Bad Request) if the post is not valid, + * or with status 500 (Internal Server Error) if the post couldnt be updated + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PutMapping("/posts") + @Timed + public ResponseEntity updatePost(@Valid @RequestBody Post post) throws URISyntaxException { + log.debug("REST request to update Post : {}", post); + if (post.getId() == null) { + return createPost(post); + } + Post result = postRepository.save(post); + return ResponseEntity.ok() + .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, post.getId().toString())) + .body(result); + } + + /** + * GET /posts : get all the posts. + * + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and the list of posts in body + * @throws URISyntaxException if there is an error to generate the pagination HTTP headers + */ + @GetMapping("/posts") + @Timed + public ResponseEntity> getAllPosts(@ApiParam Pageable pageable) { + log.debug("REST request to get a page of Posts"); + Page page = postRepository.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/posts"); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * GET /posts/:id : get the "id" post. + * + * @param id the id of the post to retrieve + * @return the ResponseEntity with status 200 (OK) and with body the post, or with status 404 (Not Found) + */ + @GetMapping("/posts/{id}") + @Timed + public ResponseEntity getPost(@PathVariable Long id) { + log.debug("REST request to get Post : {}", id); + Post post = postRepository.findOne(id); + return ResponseUtil.wrapOrNotFound(Optional.ofNullable(post)); + } + + /** + * DELETE /posts/:id : delete the "id" post. + * + * @param id the id of the post to delete + * @return the ResponseEntity with status 200 (OK) + */ + @DeleteMapping("/posts/{id}") + @Timed + public ResponseEntity deletePost(@PathVariable Long id) { + log.debug("REST request to delete Post : {}", id); + postRepository.delete(id); + return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build(); + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java b/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java new file mode 100644 index 0000000000..b5e26c8281 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java @@ -0,0 +1,69 @@ +package com.baeldung.web.rest; + +import com.baeldung.config.DefaultProfileUtil; + +import io.github.jhipster.config.JHipsterProperties; + +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Resource to return information about the currently running Spring profiles. + */ +@RestController +@RequestMapping("/api") +public class ProfileInfoResource { + + private final Environment env; + + private final JHipsterProperties jHipsterProperties; + + public ProfileInfoResource(Environment env, JHipsterProperties jHipsterProperties) { + this.env = env; + this.jHipsterProperties = jHipsterProperties; + } + + @GetMapping("/profile-info") + public ProfileInfoVM getActiveProfiles() { + String[] activeProfiles = DefaultProfileUtil.getActiveProfiles(env); + return new ProfileInfoVM(activeProfiles, getRibbonEnv(activeProfiles)); + } + + private String getRibbonEnv(String[] activeProfiles) { + String[] displayOnActiveProfiles = jHipsterProperties.getRibbon().getDisplayOnActiveProfiles(); + if (displayOnActiveProfiles == null) { + return null; + } + List ribbonProfiles = new ArrayList<>(Arrays.asList(displayOnActiveProfiles)); + List springBootProfiles = Arrays.asList(activeProfiles); + ribbonProfiles.retainAll(springBootProfiles); + if (!ribbonProfiles.isEmpty()) { + return ribbonProfiles.get(0); + } + return null; + } + + class ProfileInfoVM { + + private String[] activeProfiles; + + private String ribbonEnv; + + ProfileInfoVM(String[] activeProfiles, String ribbonEnv) { + this.activeProfiles = activeProfiles; + this.ribbonEnv = ribbonEnv; + } + + public String[] getActiveProfiles() { + return activeProfiles; + } + + public String getRibbonEnv() { + return ribbonEnv; + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java b/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java new file mode 100644 index 0000000000..02b77c51a6 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java @@ -0,0 +1,60 @@ +package com.baeldung.web.rest; + +import com.baeldung.security.jwt.JWTConfigurer; +import com.baeldung.security.jwt.TokenProvider; +import com.baeldung.web.rest.vm.LoginVM; + +import java.util.Collections; + +import com.codahale.metrics.annotation.Timed; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; + +@RestController +@RequestMapping("/api") +public class UserJWTController { + + private final Logger log = LoggerFactory.getLogger(UserJWTController.class); + + private final TokenProvider tokenProvider; + + private final AuthenticationManager authenticationManager; + + public UserJWTController(TokenProvider tokenProvider, AuthenticationManager authenticationManager) { + this.tokenProvider = tokenProvider; + this.authenticationManager = authenticationManager; + } + + @PostMapping("/authenticate") + @Timed + public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM, HttpServletResponse response) { + + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword()); + + try { + Authentication authentication = this.authenticationManager.authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe(); + String jwt = tokenProvider.createToken(authentication, rememberMe); + response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt); + return ResponseEntity.ok(new JWTToken(jwt)); + } catch (AuthenticationException ae) { + log.trace("Authentication exception trace: {}", ae); + return new ResponseEntity<>(Collections.singletonMap("AuthenticationException", + ae.getLocalizedMessage()), HttpStatus.UNAUTHORIZED); + } + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java b/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java new file mode 100644 index 0000000000..296d3e30ba --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java @@ -0,0 +1,187 @@ +package com.baeldung.web.rest; + +import com.baeldung.config.Constants; +import com.codahale.metrics.annotation.Timed; +import com.baeldung.domain.User; +import com.baeldung.repository.UserRepository; +import com.baeldung.security.AuthoritiesConstants; +import com.baeldung.service.MailService; +import com.baeldung.service.UserService; +import com.baeldung.service.dto.UserDTO; +import com.baeldung.web.rest.vm.ManagedUserVM; +import com.baeldung.web.rest.util.HeaderUtil; +import com.baeldung.web.rest.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import io.swagger.annotations.ApiParam; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +/** + * REST controller for managing users. + * + *

This class accesses the User entity, and needs to fetch its collection of authorities.

+ *

+ * For a normal use-case, it would be better to have an eager relationship between User and Authority, + * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join + * which would be good for performance. + *

+ *

+ * We use a View Model and a DTO for 3 reasons: + *

    + *
  • We want to keep a lazy association between the user and the authorities, because people will + * quite often do relationships with the user, and we don't want them to get the authorities all + * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' + * application because of this use-case.
  • + *
  • Not having an outer join causes n+1 requests to the database. This is not a real issue as + * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, + * but then all authorities come from the cache, so in fact it's much better than doing an outer join + * (which will get lots of data from the database, for each HTTP call).
  • + *
  • As this manages users, for security reasons, we'd rather have a DTO layer.
  • + *
+ *

Another option would be to have a specific JPA entity graph to handle this case.

+ */ +@RestController +@RequestMapping("/api") +public class UserResource { + + private final Logger log = LoggerFactory.getLogger(UserResource.class); + + private static final String ENTITY_NAME = "userManagement"; + + private final UserRepository userRepository; + + private final MailService mailService; + + private final UserService userService; + + public UserResource(UserRepository userRepository, MailService mailService, + UserService userService) { + + this.userRepository = userRepository; + this.mailService = mailService; + this.userService = userService; + } + + /** + * POST /users : Creates a new user. + *

+ * Creates a new user if the login and email are not already used, and sends an + * mail with an activation link. + * The user needs to be activated on creation. + *

+ * + * @param managedUserVM the user to create + * @return the ResponseEntity with status 201 (Created) and with body the new user, or with status 400 (Bad Request) if the login or email is already in use + * @throws URISyntaxException if the Location URI syntax is incorrect + */ + @PostMapping("/users") + @Timed + @Secured(AuthoritiesConstants.ADMIN) + public ResponseEntity createUser(@RequestBody ManagedUserVM managedUserVM) throws URISyntaxException { + log.debug("REST request to save User : {}", managedUserVM); + + if (managedUserVM.getId() != null) { + return ResponseEntity.badRequest() + .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new user cannot already have an ID")) + .body(null); + // Lowercase the user login before comparing with database + } else if (userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase()).isPresent()) { + return ResponseEntity.badRequest() + .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "userexists", "Login already in use")) + .body(null); + } else if (userRepository.findOneByEmail(managedUserVM.getEmail()).isPresent()) { + return ResponseEntity.badRequest() + .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "emailexists", "Email already in use")) + .body(null); + } else { + User newUser = userService.createUser(managedUserVM); + mailService.sendCreationEmail(newUser); + return ResponseEntity.created(new URI("/api/users/" + newUser.getLogin())) + .headers(HeaderUtil.createAlert( "userManagement.created", newUser.getLogin())) + .body(newUser); + } + } + + /** + * PUT /users : Updates an existing User. + * + * @param managedUserVM the user to update + * @return the ResponseEntity with status 200 (OK) and with body the updated user, + * or with status 400 (Bad Request) if the login or email is already in use, + * or with status 500 (Internal Server Error) if the user couldn't be updated + */ + @PutMapping("/users") + @Timed + @Secured(AuthoritiesConstants.ADMIN) + public ResponseEntity updateUser(@RequestBody ManagedUserVM managedUserVM) { + log.debug("REST request to update User : {}", managedUserVM); + Optional existingUser = userRepository.findOneByEmail(managedUserVM.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(managedUserVM.getId()))) { + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "emailexists", "E-mail already in use")).body(null); + } + existingUser = userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(managedUserVM.getId()))) { + return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "userexists", "Login already in use")).body(null); + } + Optional updatedUser = userService.updateUser(managedUserVM); + + return ResponseUtil.wrapOrNotFound(updatedUser, + HeaderUtil.createAlert("userManagement.updated", managedUserVM.getLogin())); + } + + /** + * GET /users : get all users. + * + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and with body all users + */ + @GetMapping("/users") + @Timed + public ResponseEntity> getAllUsers(@ApiParam Pageable pageable) { + final Page page = userService.getAllManagedUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/users"); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * GET /users/:login : get the "login" user. + * + * @param login the login of the user to find + * @return the ResponseEntity with status 200 (OK) and with body the "login" user, or with status 404 (Not Found) + */ + @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") + @Timed + public ResponseEntity getUser(@PathVariable String login) { + log.debug("REST request to get User : {}", login); + return ResponseUtil.wrapOrNotFound( + userService.getUserWithAuthoritiesByLogin(login) + .map(UserDTO::new)); + } + + /** + * DELETE /users/:login : delete the "login" User. + * + * @param login the login of the user to delete + * @return the ResponseEntity with status 200 (OK) + */ + @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") + @Timed + @Secured(AuthoritiesConstants.ADMIN) + public ResponseEntity deleteUser(@PathVariable String login) { + log.debug("REST request to delete User: {}", login); + userService.deleteUser(login); + return ResponseEntity.ok().headers(HeaderUtil.createAlert( "userManagement.deleted", login)).build(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java new file mode 100644 index 0000000000..ab7754476b --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java @@ -0,0 +1,34 @@ +package com.baeldung.web.rest.errors; + +/** + * Custom, parameterized exception, which can be translated on the client side. + * For example: + * + *
+ * throw new CustomParameterizedException("myCustomError", "hello", "world");
+ * 
+ * + * Can be translated with: + * + *
+ * "error.myCustomError" :  "The server says {{params[0]}} to {{params[1]}}"
+ * 
+ */ +public class CustomParameterizedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String message; + private final String[] params; + + public CustomParameterizedException(String message, String... params) { + super(message); + this.message = message; + this.params = params; + } + + public ParameterizedErrorVM getErrorVM() { + return new ParameterizedErrorVM(message, params); + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java new file mode 100644 index 0000000000..69f21ed96c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java @@ -0,0 +1,14 @@ +package com.baeldung.web.rest.errors; + +public final class ErrorConstants { + + public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; + public static final String ERR_ACCESS_DENIED = "error.accessDenied"; + public static final String ERR_VALIDATION = "error.validation"; + public static final String ERR_METHOD_NOT_SUPPORTED = "error.methodNotSupported"; + public static final String ERR_INTERNAL_SERVER_ERROR = "error.internalServerError"; + + private ErrorConstants() { + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java new file mode 100644 index 0000000000..22fb066135 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java @@ -0,0 +1,52 @@ +package com.baeldung.web.rest.errors; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * View Model for transferring error message with a list of field errors. + */ +public class ErrorVM implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String message; + private final String description; + + private List fieldErrors; + + public ErrorVM(String message) { + this(message, null); + } + + public ErrorVM(String message, String description) { + this.message = message; + this.description = description; + } + + public ErrorVM(String message, String description, List fieldErrors) { + this.message = message; + this.description = description; + this.fieldErrors = fieldErrors; + } + + public void add(String objectName, String field, String message) { + if (fieldErrors == null) { + fieldErrors = new ArrayList<>(); + } + fieldErrors.add(new FieldErrorVM(objectName, field, message)); + } + + public String getMessage() { + return message; + } + + public String getDescription() { + return description; + } + + public List getFieldErrors() { + return fieldErrors; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java new file mode 100644 index 0000000000..51925bfa61 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java @@ -0,0 +1,85 @@ +package com.baeldung.web.rest.errors; + +import java.util.List; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.ResponseEntity.BodyBuilder; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +/** + * Controller advice to translate the server side exceptions to client-friendly json structures. + */ +@ControllerAdvice +public class ExceptionTranslator { + + @ExceptionHandler(ConcurrencyFailureException.class) + @ResponseStatus(HttpStatus.CONFLICT) + @ResponseBody + public ErrorVM processConcurrencyError(ConcurrencyFailureException ex) { + return new ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ErrorVM processValidationError(MethodArgumentNotValidException ex) { + BindingResult result = ex.getBindingResult(); + List fieldErrors = result.getFieldErrors(); + + return processFieldErrors(fieldErrors); + } + + @ExceptionHandler(CustomParameterizedException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ParameterizedErrorVM processParameterizedValidationError(CustomParameterizedException ex) { + return ex.getErrorVM(); + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + @ResponseBody + public ErrorVM processAccessDeniedException(AccessDeniedException e) { + return new ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.getMessage()); + } + + private ErrorVM processFieldErrors(List fieldErrors) { + ErrorVM dto = new ErrorVM(ErrorConstants.ERR_VALIDATION); + + for (FieldError fieldError : fieldErrors) { + dto.add(fieldError.getObjectName(), fieldError.getField(), fieldError.getCode()); + } + + return dto; + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseBody + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public ErrorVM processMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) { + return new ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, exception.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity processRuntimeException(Exception ex) { + BodyBuilder builder; + ErrorVM errorVM; + ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); + if (responseStatus != null) { + builder = ResponseEntity.status(responseStatus.value()); + errorVM = new ErrorVM("error." + responseStatus.value().value(), responseStatus.reason()); + } else { + builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR); + errorVM = new ErrorVM(ErrorConstants.ERR_INTERNAL_SERVER_ERROR, "Internal server error"); + } + return builder.body(errorVM); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java new file mode 100644 index 0000000000..19ab640ec7 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java @@ -0,0 +1,33 @@ +package com.baeldung.web.rest.errors; + +import java.io.Serializable; + +public class FieldErrorVM implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String objectName; + + private final String field; + + private final String message; + + public FieldErrorVM(String dto, String field, String message) { + this.objectName = dto; + this.field = field; + this.message = message; + } + + public String getObjectName() { + return objectName; + } + + public String getField() { + return field; + } + + public String getMessage() { + return message; + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java new file mode 100644 index 0000000000..18500c51af --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java @@ -0,0 +1,27 @@ +package com.baeldung.web.rest.errors; + +import java.io.Serializable; + +/** + * View Model for sending a parameterized error message. + */ +public class ParameterizedErrorVM implements Serializable { + + private static final long serialVersionUID = 1L; + private final String message; + private final String[] params; + + public ParameterizedErrorVM(String message, String... params) { + this.message = message; + this.params = params; + } + + public String getMessage() { + return message; + } + + public String[] getParams() { + return params; + } + +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/package-info.java b/jhipster/src/main/java/com/baeldung/web/rest/package-info.java new file mode 100644 index 0000000000..0a74f6e90c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring MVC REST controllers. + */ +package com.baeldung.web.rest; diff --git a/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java b/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java new file mode 100644 index 0000000000..7c643e9323 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java @@ -0,0 +1,45 @@ +package com.baeldung.web.rest.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; + +/** + * Utility class for HTTP headers creation. + */ +public final class HeaderUtil { + + private static final Logger log = LoggerFactory.getLogger(HeaderUtil.class); + + private static final String APPLICATION_NAME = "baeldungApp"; + + private HeaderUtil() { + } + + public static HttpHeaders createAlert(String message, String param) { + HttpHeaders headers = new HttpHeaders(); + headers.add("X-baeldungApp-alert", message); + headers.add("X-baeldungApp-params", param); + return headers; + } + + public static HttpHeaders createEntityCreationAlert(String entityName, String param) { + return createAlert(APPLICATION_NAME + "." + entityName + ".created", param); + } + + public static HttpHeaders createEntityUpdateAlert(String entityName, String param) { + return createAlert(APPLICATION_NAME + "." + entityName + ".updated", param); + } + + public static HttpHeaders createEntityDeletionAlert(String entityName, String param) { + return createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param); + } + + public static HttpHeaders createFailureAlert(String entityName, String errorKey, String defaultMessage) { + log.error("Entity creation failed, {}", defaultMessage); + HttpHeaders headers = new HttpHeaders(); + headers.add("X-baeldungApp-error", "error." + errorKey); + headers.add("X-baeldungApp-params", entityName); + return headers; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java b/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java new file mode 100644 index 0000000000..affcb7842c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java @@ -0,0 +1,47 @@ +package com.baeldung.web.rest.util; + +import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URISyntaxException; + +/** + * Utility class for handling pagination. + * + *

+ * Pagination uses the same principles as the Github API, + * and follow RFC 5988 (Link header). + */ +public final class PaginationUtil { + + private PaginationUtil() { + } + + public static HttpHeaders generatePaginationHttpHeaders(Page page, String baseUrl) { + + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Total-Count", "" + Long.toString(page.getTotalElements())); + String link = ""; + if ((page.getNumber() + 1) < page.getTotalPages()) { + link = "<" + generateUri(baseUrl, page.getNumber() + 1, page.getSize()) + ">; rel=\"next\","; + } + // prev link + if ((page.getNumber()) > 0) { + link += "<" + generateUri(baseUrl, page.getNumber() - 1, page.getSize()) + ">; rel=\"prev\","; + } + // last and first link + int lastPage = 0; + if (page.getTotalPages() > 0) { + lastPage = page.getTotalPages() - 1; + } + link += "<" + generateUri(baseUrl, lastPage, page.getSize()) + ">; rel=\"last\","; + link += "<" + generateUri(baseUrl, 0, page.getSize()) + ">; rel=\"first\""; + headers.add(HttpHeaders.LINK, link); + return headers; + } + + private static String generateUri(String baseUrl, int page, int size) { + return UriComponentsBuilder.fromUriString(baseUrl).queryParam("page", page).queryParam("size", size).toUriString(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java new file mode 100644 index 0000000000..94465f309f --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java @@ -0,0 +1,27 @@ +package com.baeldung.web.rest.vm; + +/** + * View Model object for storing the user's key and password. + */ +public class KeyAndPasswordVM { + + private String key; + + private String newPassword; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java new file mode 100644 index 0000000000..56f77aec85 --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java @@ -0,0 +1,48 @@ +package com.baeldung.web.rest.vm; + +import ch.qos.logback.classic.Logger; +import com.fasterxml.jackson.annotation.JsonCreator; + +/** + * View Model object for storing a Logback logger. + */ +public class LoggerVM { + + private String name; + + private String level; + + public LoggerVM(Logger logger) { + this.name = logger.getName(); + this.level = logger.getEffectiveLevel().toString(); + } + + @JsonCreator + public LoggerVM() { + // Empty public constructor used by Jackson. + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + @Override + public String toString() { + return "LoggerVM{" + + "name='" + name + '\'' + + ", level='" + level + '\'' + + '}'; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java new file mode 100644 index 0000000000..a910d0d59c --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java @@ -0,0 +1,55 @@ +package com.baeldung.web.rest.vm; + +import com.baeldung.config.Constants; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * View Model object for storing a user's credentials. + */ +public class LoginVM { + + @Pattern(regexp = Constants.LOGIN_REGEX) + @NotNull + @Size(min = 1, max = 50) + private String username; + + @NotNull + @Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH) + private String password; + + private Boolean rememberMe; + + 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 Boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(Boolean rememberMe) { + this.rememberMe = rememberMe; + } + + @Override + public String toString() { + return "LoginVM{" + + "username='" + username + '\'' + + ", rememberMe=" + rememberMe + + '}'; + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java new file mode 100644 index 0000000000..0899f0d0aa --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java @@ -0,0 +1,45 @@ +package com.baeldung.web.rest.vm; + +import com.baeldung.service.dto.UserDTO; +import javax.validation.constraints.Size; + +import java.time.ZonedDateTime; +import java.util.Set; + +/** + * View Model extending the UserDTO, which is meant to be used in the user management UI. + */ +public class ManagedUserVM extends UserDTO { + + public static final int PASSWORD_MIN_LENGTH = 4; + + public static final int PASSWORD_MAX_LENGTH = 100; + + @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) + private String password; + + public ManagedUserVM() { + // Empty constructor needed for Jackson. + } + + public ManagedUserVM(Long id, String login, String password, String firstName, String lastName, + String email, boolean activated, String imageUrl, String langKey, + String createdBy, ZonedDateTime createdDate, String lastModifiedBy, ZonedDateTime lastModifiedDate, + Set authorities) { + + super(id, login, firstName, lastName, email, activated, imageUrl, langKey, + createdBy, createdDate, lastModifiedBy, lastModifiedDate, authorities); + + this.password = password; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "ManagedUserVM{" + + "} " + super.toString(); + } +} diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java new file mode 100644 index 0000000000..8d56157b6a --- /dev/null +++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java @@ -0,0 +1,4 @@ +/** + * View Models used by Spring MVC REST controllers. + */ +package com.baeldung.web.rest.vm; diff --git a/jhipster/src/main/resources/.h2.server.properties b/jhipster/src/main/resources/.h2.server.properties new file mode 100644 index 0000000000..f8b4429902 --- /dev/null +++ b/jhipster/src/main/resources/.h2.server.properties @@ -0,0 +1,5 @@ +#H2 Server Properties +0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/baeldung|baeldung +webAllowOthers=true +webPort=8082 +webSSL=false diff --git a/jhipster/src/main/resources/banner.txt b/jhipster/src/main/resources/banner.txt new file mode 100644 index 0000000000..c3d8cf725d --- /dev/null +++ b/jhipster/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + + ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚â•â•██╔â•â•╠██╔â•â•â•██╗ ██╔â•â•â•â•╠╚â•â•██╔â•â•╠██╔â•â•â•â•â•╠██╔â•â•â•██╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╠╚█████╗ ██║ ██████╗ ███████╔╠+ ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔â•â•â•██║ ██║ ██╔â•â•â•â•╠╚â•â•â•██╗ ██║ ██╔â•â•â•╠██╔â•â•██║ + ${AnsiColor.GREEN}╚██████╔â•${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╠██║ ████████╗ ██║ ╚██╗ + ${AnsiColor.GREEN} ╚â•â•â•â•â•â• ${AnsiColor.RED} ╚â•╠╚â•╠╚â•â•â•â•â•â•â•╠╚â•╠╚â•â•â•â•â•╠╚â•╠╚â•â•â•â•â•â•â•╠╚â•╠╚â•â• + +${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} :: +:: http://jhipster.github.io ::${AnsiColor.DEFAULT} diff --git a/jhipster/src/main/resources/config/application-dev.yml b/jhipster/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000000..d75c2549c8 --- /dev/null +++ b/jhipster/src/main/resources/config/application-dev.yml @@ -0,0 +1,130 @@ +# =================================================================== +# Spring Boot configuration for the "dev" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://jhipster.github.io/profiles/ +# More information on configuration properties: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +spring: + profiles: + active: dev + include: swagger + devtools: + restart: + enabled: true + livereload: + enabled: false # we use gulp + BrowserSync for livereload + jackson: + serialization.indent_output: true + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:h2:file:./target/h2db/db/baeldung;DB_CLOSE_DELAY=-1 + username: baeldung + password: + h2: + console: + enabled: false + jpa: + database-platform: io.github.jhipster.domain.util.FixedH2Dialect + database: H2 + show-sql: true + properties: + hibernate.id.new_generator_mappings: true + hibernate.cache.use_second_level_cache: true + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: true + hibernate.cache.region.factory_class: io.github.jhipster.config.jcache.NoDefaultJCacheRegionFactory + mail: + host: localhost + port: 25 + username: + password: + messages: + cache-seconds: 1 + thymeleaf: + cache: false + +liquibase: + contexts: dev + +# =================================================================== +# To enable SSL, generate a certificate using: +# keytool -genkey -alias baeldung -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 +# +# You can also use Let's Encrypt: +# https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm +# +# Then, modify the server.ssl properties so your "server" configuration looks like: +# +# server: +# port: 8443 +# ssl: +# key-store: keystore.p12 +# key-store-password: +# keyStoreType: PKCS12 +# keyAlias: baeldung +# =================================================================== +server: + port: 8080 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +jhipster: + http: + version: V_1_1 # To use HTTP/2 you will need SSL support (see above the "server.ssl" configuration) + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 100 # Number of objects in each cache entry + security: + authentication: + jwt: + secret: my-secret-token-to-change-in-production + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + mail: # specific JHipster mail property, for standard properties see MailProperties + from: baeldung@localhost + base-url: http://127.0.0.1:8080 + metrics: # DropWizard Metrics configuration, used by MetricsConfiguration + jmx.enabled: true + graphite: # Use the "graphite" Maven profile to have the Graphite dependencies + enabled: false + host: localhost + port: 2003 + prefix: baeldung + prometheus: # Use the "prometheus" Maven profile to have the Prometheus dependencies + enabled: false + endpoint: /prometheusMetrics + logs: # Reports Dropwizard metrics in the logs + enabled: false + reportFrequency: 60 # in seconds + logging: + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + queue-size: 512 + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://jhipster.github.io/common-application-properties/ +# =================================================================== + +application: diff --git a/jhipster/src/main/resources/config/application-prod.yml b/jhipster/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000000..a46fbd1c73 --- /dev/null +++ b/jhipster/src/main/resources/config/application-prod.yml @@ -0,0 +1,132 @@ +# =================================================================== +# Spring Boot configuration for the "prod" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://jhipster.github.io/profiles/ +# More information on configuration properties: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +spring: + devtools: + restart: + enabled: false + livereload: + enabled: false + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://localhost:3306/baeldung?useUnicode=true&characterEncoding=utf8&useSSL=false + username: root + password: + hikari: + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + database: MYSQL + show-sql: false + properties: + hibernate.id.new_generator_mappings: true + hibernate.cache.use_second_level_cache: true + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + hibernate.cache.region.factory_class: io.github.jhipster.config.jcache.NoDefaultJCacheRegionFactory + mail: + host: localhost + port: 25 + username: + password: + thymeleaf: + cache: true + +liquibase: + contexts: prod + +# =================================================================== +# To enable SSL, generate a certificate using: +# keytool -genkey -alias baeldung -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 +# +# You can also use Let's Encrypt: +# https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm +# +# Then, modify the server.ssl properties so your "server" configuration looks like: +# +# server: +# port: 443 +# ssl: +# key-store: keystore.p12 +# key-store-password: +# keyStoreType: PKCS12 +# keyAlias: baeldung +# =================================================================== +server: + port: 8080 + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css, application/javascript, application/json + min-response-size: 1024 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +jhipster: + http: + version: V_1_1 # To use HTTP/2 you will need SSL support (see above the "server.ssl" configuration) + cache: # Used by the CachingHttpHeadersFilter + timeToLiveInDays: 1461 + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 1000 # Number of objects in each cache entry + security: + authentication: + jwt: + secret: e1d4b69d3f953e3fa622121e882e6f459ca20ca4 + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + mail: # specific JHipster mail property, for standard properties see MailProperties + from: baeldung@localhost + base-url: http://my-server-url-to-change # Modify according to your server's URL + metrics: # DropWizard Metrics configuration, used by MetricsConfiguration + jmx.enabled: true + graphite: + enabled: false + host: localhost + port: 2003 + prefix: baeldung + prometheus: + enabled: false + endpoint: /prometheusMetrics + logs: # Reports Dropwizard metrics in the logs + enabled: false + reportFrequency: 60 # in seconds + logging: + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + queue-size: 512 + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://jhipster.github.io/common-application-properties/ +# =================================================================== + +application: diff --git a/jhipster/src/main/resources/config/application.yml b/jhipster/src/main/resources/config/application.yml new file mode 100644 index 0000000000..ed48baf853 --- /dev/null +++ b/jhipster/src/main/resources/config/application.yml @@ -0,0 +1,106 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration will be overriden by the Spring profile you use, +# for example application-dev.yml if you use the "dev" profile. +# +# More information on profiles: https://jhipster.github.io/profiles/ +# More information on configuration properties: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +management: + security: + roles: ADMIN + context-path: /management + health: + mail: + enabled: false # When using the MailService, configure an SMTP server and set this to true +spring: + application: + name: baeldung + profiles: + # The commented value for `active` can be replaced with valid Spring profiles to load. + # Otherwise, it will be filled in by maven when building the WAR file + # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` + active: #spring.profiles.active# + jackson: + serialization.write_dates_as_timestamps: false + jpa: + open-in-view: false + hibernate: + ddl-auto: none + naming: + physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + messages: + basename: i18n/messages + mvc: + favicon: + enabled: false + thymeleaf: + mode: XHTML + +security: + basic: + enabled: false + +server: + session: + cookie: + http-only: true + +info: + project: + version: #project.version# + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +jhipster: + async: + core-pool-size: 2 + max-pool-size: 50 + queue-capacity: 10000 + # By default CORS is disabled. Uncomment to enable. + #cors: + #allowed-origins: "*" + #allowed-methods: GET, PUT, POST, DELETE, OPTIONS + #allowed-headers: "*" + #exposed-headers: + #allow-credentials: true + #max-age: 1800 + mail: + from: baeldung@localhost + swagger: + default-include-pattern: /api/.* + title: baeldung API + description: baeldung API documentation + version: 0.0.1 + terms-of-service-url: + contact-name: + contact-url: + contact-email: + license: + license-url: + ribbon: + display-on-active-profiles: dev + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://jhipster.github.io/common-application-properties/ +# =================================================================== + +application: diff --git a/jhipster/src/main/resources/config/liquibase/authorities.csv b/jhipster/src/main/resources/config/liquibase/authorities.csv new file mode 100644 index 0000000000..af5c6dfa18 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/authorities.csv @@ -0,0 +1,3 @@ +name +ROLE_ADMIN +ROLE_USER diff --git a/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml new file mode 100644 index 0000000000..98ac548808 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml new file mode 100644 index 0000000000..ce4773262c --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml new file mode 100644 index 0000000000..2040980371 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml new file mode 100644 index 0000000000..d0b26503e1 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml new file mode 100644 index 0000000000..b7670e747c --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/master.xml b/jhipster/src/main/resources/config/liquibase/master.xml new file mode 100644 index 0000000000..32eb479989 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/master.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/jhipster/src/main/resources/config/liquibase/users.csv b/jhipster/src/main/resources/config/liquibase/users.csv new file mode 100644 index 0000000000..b25922b699 --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/users.csv @@ -0,0 +1,5 @@ +id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by +1;system;$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG;System;System;system@localhost;;true;en;system;system +2;anonymoususer;$2a$10$j8S5d7Sr7.8VTOYNviDPOeWX8KcYILUVJBsYV83Y5NtECayypx9lO;Anonymous;User;anonymous@localhost;;true;en;system;system +3;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system +4;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system diff --git a/jhipster/src/main/resources/config/liquibase/users_authorities.csv b/jhipster/src/main/resources/config/liquibase/users_authorities.csv new file mode 100644 index 0000000000..06c5feeeea --- /dev/null +++ b/jhipster/src/main/resources/config/liquibase/users_authorities.csv @@ -0,0 +1,6 @@ +user_id;authority_name +1;ROLE_ADMIN +1;ROLE_USER +3;ROLE_ADMIN +3;ROLE_USER +4;ROLE_USER diff --git a/jhipster/src/main/resources/i18n/messages.properties b/jhipster/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000000..1c28002acd --- /dev/null +++ b/jhipster/src/main/resources/i18n/messages.properties @@ -0,0 +1,22 @@ +# Error page +error.title=Your request cannot be processed +error.subtitle=Sorry, an error has occurred. +error.status=Status: +error.message=Message: + +# Activation e-mail +email.activation.title=baeldung account activation +email.activation.greeting=Dear {0} +email.activation.text1=Your baeldung account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=baeldung Team. + +# Creation email +email.creation.text1=Your baeldung account has been created, please click on the URL below to access it: + +# Reset e-mail +email.reset.title=baeldung password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your baeldung account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, + diff --git a/jhipster/src/main/resources/i18n/messages_en.properties b/jhipster/src/main/resources/i18n/messages_en.properties new file mode 100644 index 0000000000..1c28002acd --- /dev/null +++ b/jhipster/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,22 @@ +# Error page +error.title=Your request cannot be processed +error.subtitle=Sorry, an error has occurred. +error.status=Status: +error.message=Message: + +# Activation e-mail +email.activation.title=baeldung account activation +email.activation.greeting=Dear {0} +email.activation.text1=Your baeldung account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=baeldung Team. + +# Creation email +email.creation.text1=Your baeldung account has been created, please click on the URL below to access it: + +# Reset e-mail +email.reset.title=baeldung password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your baeldung account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, + diff --git a/jhipster/src/main/resources/logback-spring.xml b/jhipster/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000..3c62a70c31 --- /dev/null +++ b/jhipster/src/main/resources/logback-spring.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + diff --git a/jhipster/src/main/resources/mails/activationEmail.html b/jhipster/src/main/resources/mails/activationEmail.html new file mode 100644 index 0000000000..9fb22a7796 --- /dev/null +++ b/jhipster/src/main/resources/mails/activationEmail.html @@ -0,0 +1,24 @@ + + + + JHipster activation + + + +

+ Dear +

+

+ Your JHipster account has been created, please click on the URL below to activate it: +

+

+ Activation Link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/jhipster/src/main/resources/mails/creationEmail.html b/jhipster/src/main/resources/mails/creationEmail.html new file mode 100644 index 0000000000..a59d91da0c --- /dev/null +++ b/jhipster/src/main/resources/mails/creationEmail.html @@ -0,0 +1,24 @@ + + + + JHipster creation + + + +

+ Dear +

+

+ Your JHipster account has been created, please click on the URL below to access it: +

+

+ login +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/jhipster/src/main/resources/mails/passwordResetEmail.html b/jhipster/src/main/resources/mails/passwordResetEmail.html new file mode 100644 index 0000000000..30d5f62c60 --- /dev/null +++ b/jhipster/src/main/resources/mails/passwordResetEmail.html @@ -0,0 +1,24 @@ + + + + JHipster password reset + + + +

+ Dear +

+

+ For your JHipster account a password reset was requested, please click on the URL below to reset it: +

+

+ Reset Link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/jhipster/src/main/resources/templates/error.html b/jhipster/src/main/resources/templates/error.html new file mode 100644 index 0000000000..774b080d6c --- /dev/null +++ b/jhipster/src/main/resources/templates/error.html @@ -0,0 +1,162 @@ + + + + + Your request cannot be processed + + + +
+

Your request cannot be processed :(

+ +

Sorry, an error has occurred.

+ + Status:  ()
+ + Message: 
+
+ + + +
+ + diff --git a/jhipster/src/main/webapp/404.html b/jhipster/src/main/webapp/404.html new file mode 100644 index 0000000000..8d7925a892 --- /dev/null +++ b/jhipster/src/main/webapp/404.html @@ -0,0 +1,60 @@ + + + + + Page Not Found + + + + +

Page Not Found

+

Sorry, but the page you were trying to view does not exist.

+ + + diff --git a/jhipster/src/main/webapp/app/account/account.module.ts b/jhipster/src/main/webapp/app/account/account.module.ts new file mode 100644 index 0000000000..09b19a7555 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/account.module.ts @@ -0,0 +1,45 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { BaeldungSharedModule } from '../shared'; + +import { + Register, + Activate, + Password, + PasswordResetInit, + PasswordResetFinish, + PasswordStrengthBarComponent, + RegisterComponent, + ActivateComponent, + PasswordComponent, + PasswordResetInitComponent, + PasswordResetFinishComponent, + SettingsComponent, + accountState +} from './'; + +@NgModule({ + imports: [ + BaeldungSharedModule, + RouterModule.forRoot(accountState, { useHash: true }) + ], + declarations: [ + ActivateComponent, + RegisterComponent, + PasswordComponent, + PasswordStrengthBarComponent, + PasswordResetInitComponent, + PasswordResetFinishComponent, + SettingsComponent + ], + providers: [ + Register, + Activate, + Password, + PasswordResetInit, + PasswordResetFinish + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungAccountModule {} diff --git a/jhipster/src/main/webapp/app/account/account.route.ts b/jhipster/src/main/webapp/app/account/account.route.ts new file mode 100644 index 0000000000..4715216cf3 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/account.route.ts @@ -0,0 +1,26 @@ +import { Routes, CanActivate } from '@angular/router'; + +import { UserRouteAccessService } from '../shared'; + +import { + activateRoute, + passwordRoute, + passwordResetFinishRoute, + passwordResetInitRoute, + registerRoute, + settingsRoute +} from './'; + +let ACCOUNT_ROUTES = [ + activateRoute, + passwordRoute, + passwordResetFinishRoute, + passwordResetInitRoute, + registerRoute, + settingsRoute +]; + +export const accountState: Routes = [{ + path: '', + children: ACCOUNT_ROUTES +}]; diff --git a/jhipster/src/main/webapp/app/account/activate/activate.component.html b/jhipster/src/main/webapp/app/account/activate/activate.component.html new file mode 100644 index 0000000000..25e3f23417 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/activate/activate.component.html @@ -0,0 +1,19 @@ +
+
+
+

Activation

+ +
+ + Your user has been activated. Please + sign in. + +
+ +
+ Your user could not be activated. Please use the registration form to sign up. +
+ +
+
+
diff --git a/jhipster/src/main/webapp/app/account/activate/activate.component.ts b/jhipster/src/main/webapp/app/account/activate/activate.component.ts new file mode 100644 index 0000000000..dbaaa7d676 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/activate/activate.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute } from '@angular/router'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Activate } from './activate.service'; +import { LoginModalService } from '../../shared'; + +@Component({ + selector: 'jhi-activate', + templateUrl: './activate.component.html' +}) +export class ActivateComponent implements OnInit { + error: string; + success: string; + modalRef: NgbModalRef; + + constructor( + private jhiLanguageService: JhiLanguageService, + private activate: Activate, + private loginModalService: LoginModalService, + private route: ActivatedRoute + ) { + this.jhiLanguageService.setLocations(['activate']); + } + + ngOnInit () { + this.route.queryParams.subscribe(params => { + this.activate.get(params['key']).subscribe(() => { + this.error = null; + this.success = 'OK'; + }, () => { + this.success = null; + this.error = 'ERROR'; + }); + }); + } + + login() { + this.modalRef = this.loginModalService.open(); + } +} diff --git a/jhipster/src/main/webapp/app/account/activate/activate.route.ts b/jhipster/src/main/webapp/app/account/activate/activate.route.ts new file mode 100644 index 0000000000..8f1bb32b58 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/activate/activate.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { ActivateComponent } from './activate.component'; + +export const activateRoute: Route = { + path: 'activate', + component: ActivateComponent, + data: { + authorities: [], + pageTitle: 'activate.title' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/account/activate/activate.service.ts b/jhipster/src/main/webapp/app/account/activate/activate.service.ts new file mode 100644 index 0000000000..f877a4d50e --- /dev/null +++ b/jhipster/src/main/webapp/app/account/activate/activate.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, URLSearchParams } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class Activate { + + constructor (private http: Http) {} + + get(key: string): Observable { + let params: URLSearchParams = new URLSearchParams(); + params.set('key', key); + + return this.http.get('api/activate', { + search: params + }).map((res: Response) => res); + } +} diff --git a/jhipster/src/main/webapp/app/account/index.ts b/jhipster/src/main/webapp/app/account/index.ts new file mode 100644 index 0000000000..aeada0551c --- /dev/null +++ b/jhipster/src/main/webapp/app/account/index.ts @@ -0,0 +1,19 @@ +export * from './activate/activate.component'; +export * from './activate/activate.service'; +export * from './activate/activate.route'; +export * from './password/password.component'; +export * from './password/password-strength-bar.component'; +export * from './password/password.service'; +export * from './password/password.route'; +export * from './password-reset/finish/password-reset-finish.component'; +export * from './password-reset/finish/password-reset-finish.service'; +export * from './password-reset/finish/password-reset-finish.route'; +export * from './password-reset/init/password-reset-init.component'; +export * from './password-reset/init/password-reset-init.service'; +export * from './password-reset/init/password-reset-init.route'; +export * from './register/register.component'; +export * from './register/register.service'; +export * from './register/register.route'; +export * from './settings/settings.component'; +export * from './settings/settings.route'; +export * from './account.route'; diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html new file mode 100644 index 0000000000..a1dfde055c --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html @@ -0,0 +1,77 @@ +
+
+
+

Reset password

+ +
+ The password reset key is missing. +
+ +
+

Choose a new password

+
+ +
+

Your password couldn't be reset. Remember a password request is only valid for 24 hours.

+
+ +

+ Your password has been reset. Please + sign in. +

+ +
+ The password and its confirmation do not match! +
+ +
+
+
+ + +
+ + Your password is required. + + + Your password is required to be at least 4 characters. + + + Your password cannot be longer than 50 characters. + +
+ +
+ +
+ + +
+ + Your password confirmation is required. + + + Your password confirmation is required to be at least 4 characters. + + + Your password confirmation cannot be longer than 50 characters. + +
+
+ +
+
+ +
+
+
diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts new file mode 100644 index 0000000000..f1889920bd --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute } from '@angular/router'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { PasswordResetFinish } from './password-reset-finish.service'; +import { LoginModalService } from '../../../shared'; + +@Component({ + selector: 'jhi-password-reset-finish', + templateUrl: './password-reset-finish.component.html' +}) +export class PasswordResetFinishComponent implements OnInit, AfterViewInit { + confirmPassword: string; + doNotMatch: string; + error: string; + keyMissing: boolean; + resetAccount: any; + success: string; + modalRef: NgbModalRef; + key: string; + + constructor( + private jhiLanguageService: JhiLanguageService, + private passwordResetFinish: PasswordResetFinish, + private loginModalService: LoginModalService, + private route: ActivatedRoute, + private elementRef: ElementRef, private renderer: Renderer + ) { + this.jhiLanguageService.setLocations(['reset']); + } + + ngOnInit() { + this.route.queryParams.subscribe(params => { + this.key = params['key']; + }); + this.resetAccount = {}; + this.keyMissing = !this.key; + } + + ngAfterViewInit() { + if (this.elementRef.nativeElement.querySelector('#password') != null) { + this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#password'), 'focus', []); + } + } + + finishReset() { + this.doNotMatch = null; + this.error = null; + if (this.resetAccount.password !== this.confirmPassword) { + this.doNotMatch = 'ERROR'; + } else { + this.passwordResetFinish.save({key: this.key, newPassword: this.resetAccount.password}).subscribe(() => { + this.success = 'OK'; + }, () => { + this.success = null; + this.error = 'ERROR'; + }); + } + } + + login() { + this.modalRef = this.loginModalService.open(); + } +} diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts new file mode 100644 index 0000000000..7b02653ed5 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../../shared'; +import { PasswordResetFinishComponent } from './password-reset-finish.component'; + +export const passwordResetFinishRoute: Route = { + path: 'reset/finish', + component: PasswordResetFinishComponent, + data: { + authorities: [], + pageTitle: 'global.menu.account.password' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts new file mode 100644 index 0000000000..abd81374b0 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class PasswordResetFinish { + + constructor (private http: Http) {} + + save(keyAndPassword: any): Observable { + return this.http.post('api/account/reset_password/finish', keyAndPassword); + } +} diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html new file mode 100644 index 0000000000..2503433275 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html @@ -0,0 +1,47 @@ +
+
+
+

Reset your password

+ +
+ E-Mail address isn't registered! Please check and try again. +
+ +
+

Enter the e-mail address you used to register.

+
+ +
+

Check your e-mails for details on how to reset your password.

+
+ +
+
+ + +
+ + Your e-mail is required. + + + Your e-mail is invalid. + + + Your e-mail is required to be at least 5 characters. + + + Your e-mail cannot be longer than 100 characters. + +
+
+ +
+ +
+
+
diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts new file mode 100644 index 0000000000..eba521dc05 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { PasswordResetInit } from './password-reset-init.service'; + +@Component({ + selector: 'jhi-password-reset-init', + templateUrl: './password-reset-init.component.html' +}) +export class PasswordResetInitComponent implements OnInit, AfterViewInit { + error: string; + errorEmailNotExists: string; + resetAccount: any; + success: string; + + constructor( + private jhiLanguageService: JhiLanguageService, + private passwordResetInit: PasswordResetInit, + private elementRef: ElementRef, + private renderer: Renderer + ) { + this.jhiLanguageService.setLocations(['reset']); + } + + ngOnInit() { + this.resetAccount = {}; + } + + ngAfterViewInit() { + this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#email'), 'focus', []); + } + + requestReset () { + + this.error = null; + this.errorEmailNotExists = null; + + this.passwordResetInit.save(this.resetAccount.email).subscribe(() => { + this.success = 'OK'; + }, (response) => { + this.success = null; + if (response.status === 400 && response.data === 'e-mail address not registered') { + this.errorEmailNotExists = 'ERROR'; + } else { + this.error = 'ERROR'; + } + }); + } +} diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts new file mode 100644 index 0000000000..37f7e8102d --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../../shared'; +import { PasswordResetInitComponent } from './password-reset-init.component'; + +export const passwordResetInitRoute: Route = { + path: 'reset/request', + component: PasswordResetInitComponent, + data: { + authorities: [], + pageTitle: 'global.menu.account.password' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts new file mode 100644 index 0000000000..fa466cd6cc --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class PasswordResetInit { + + constructor (private http: Http) {} + + save(mail: string): Observable { + return this.http.post('api/account/reset_password/init', mail); + } +} diff --git a/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts b/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts new file mode 100644 index 0000000000..2a61d133d6 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts @@ -0,0 +1,89 @@ +import { Component, ElementRef, Input, Renderer } from '@angular/core'; + +@Component({ + selector: 'jhi-password-strength-bar', + template: ` +
+ Password strength: +
    +
  • +
  • +
  • +
  • +
  • +
+
`, + styleUrls: [ + 'password-strength-bar.scss' + ] +}) +export class PasswordStrengthBarComponent { + + colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0']; + + constructor(private renderer: Renderer, private elementRef: ElementRef) { } + + measureStrength(p: string): number { + + let force = 0; + let regex = /[$-/:-?{-~!"^_`\[\]]/g; // " + + let lowerLetters = /[a-z]+/.test(p); + let upperLetters = /[A-Z]+/.test(p); + let numbers = /[0-9]+/.test(p); + let symbols = regex.test(p); + + let flags = [lowerLetters, upperLetters, numbers, symbols]; + let passedMatches = flags.filter( (isMatchedFlag: boolean) => { + return isMatchedFlag === true; + }).length; + + force += 2 * p.length + ((p.length >= 10) ? 1 : 0); + force += passedMatches * 10; + + // penality (short password) + force = (p.length <= 6) ? Math.min(force, 10) : force; + + // penality (poor variety of characters) + force = (passedMatches === 1) ? Math.min(force, 10) : force; + force = (passedMatches === 2) ? Math.min(force, 20) : force; + force = (passedMatches === 3) ? Math.min(force, 40) : force; + + return force; + }; + + getColor(s: number): any { + let idx = 0; + if (s <= 10) { + idx = 0; + } else if (s <= 20) { + idx = 1; + } else if (s <= 30) { + idx = 2; + } else if (s <= 40) { + idx = 3; + } else { + idx = 4; + } + return {idx: idx + 1, col: this.colors[idx]}; + }; + + @Input() + set passwordToCheck(password: string) { + if (password) { + let c = this.getColor(this.measureStrength(password)); + let element = this.elementRef.nativeElement; + if ( element.className ) { + this.renderer.setElementClass(element, element.className , false); + } + let lis = element.getElementsByTagName('li'); + for (let i = 0; i < lis.length; i++) { + if (i < c.idx) { + this.renderer.setElementStyle(lis[i], 'backgroundColor', c.col); + } else { + this.renderer.setElementStyle(lis[i], 'backgroundColor', '#DDD'); + } + } + } + } +} diff --git a/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss b/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss new file mode 100644 index 0000000000..8f8effcb60 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss @@ -0,0 +1,23 @@ +/* ========================================================================== +start Password strength bar style +========================================================================== */ +ul#strength { + display:inline; + list-style:none; + margin:0; + margin-left:15px; + padding:0; + vertical-align:2px; +} + +.point { + background:#DDD; + border-radius:2px; + display:inline-block; + height:5px; + margin-right:1px; + width:20px; + &:last { + margin:0 !important; + } +} diff --git a/jhipster/src/main/webapp/app/account/password/password.component.html b/jhipster/src/main/webapp/app/account/password/password.component.html new file mode 100644 index 0000000000..2534bccdfc --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password.component.html @@ -0,0 +1,65 @@ +
+
+
+

Password for [{{account.login}}]

+ +
+ Password changed! +
+
+ An error has occurred! The password could not be changed. +
+ +
+ The password and its confirmation do not match! +
+ +
+ +
+ + +
+ + Your password is required. + + + Your password is required to be at least 4 characters. + + + Your password cannot be longer than 50 characters. + +
+ +
+
+ + +
+ + Your confirmation password is required. + + + Your confirmation password is required to be at least 4 characters. + + + Your confirmation password cannot be longer than 50 characters. + +
+
+ + +
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/password/password.component.ts b/jhipster/src/main/webapp/app/account/password/password.component.ts new file mode 100644 index 0000000000..f34eae423c --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Principal } from '../../shared'; +import { Password } from './password.service'; + +@Component({ + selector: 'jhi-password', + templateUrl: './password.component.html' +}) +export class PasswordComponent implements OnInit { + doNotMatch: string; + error: string; + success: string; + account: any; + password: string; + confirmPassword: string; + + constructor( + private jhiLanguageService: JhiLanguageService, + private passwordService: Password, + private principal: Principal + ) { + this.jhiLanguageService.setLocations(['password']); + } + + ngOnInit () { + this.principal.identity().then((account) => { + this.account = account; + }); + } + + changePassword () { + if (this.password !== this.confirmPassword) { + this.error = null; + this.success = null; + this.doNotMatch = 'ERROR'; + } else { + this.doNotMatch = null; + this.passwordService.save(this.password).subscribe(() => { + this.error = null; + this.success = 'OK'; + }, () => { + this.success = null; + this.error = 'ERROR'; + }); + } + } +} diff --git a/jhipster/src/main/webapp/app/account/password/password.route.ts b/jhipster/src/main/webapp/app/account/password/password.route.ts new file mode 100644 index 0000000000..d5a7118458 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { PasswordComponent } from './password.component'; + +export const passwordRoute: Route = { + path: 'password', + component: PasswordComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'global.menu.account.password' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/account/password/password.service.ts b/jhipster/src/main/webapp/app/account/password/password.service.ts new file mode 100644 index 0000000000..0c220d8816 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/password/password.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class Password { + + constructor (private http: Http) {} + + save(newPassword: string): Observable { + return this.http.post('api/account/change_password', newPassword); + } +} diff --git a/jhipster/src/main/webapp/app/account/register/register.component.html b/jhipster/src/main/webapp/app/account/register/register.component.html new file mode 100644 index 0000000000..14a0c851e2 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/register/register.component.html @@ -0,0 +1,122 @@ +
+
+
+

Registration

+ +
+ Registration saved! Please check your email for confirmation. +
+ +
+ Registration failed! Please try again later. +
+ +
+ Login name already registered! Please choose another one. +
+ +
+ E-mail is already in use! Please choose another one. +
+ +
+ The password and its confirmation do not match! +
+
+
+
+
+ + +
+ + Your username is required. + + + Your username is required to be at least 1 character. + + + Your username cannot be longer than 50 characters. + + + Your username can only contain lower-case letters and digits. + +
+
+
+ + +
+ + Your e-mail is required. + + + Your e-mail is invalid. + + + Your e-mail is required to be at least 5 characters. + + + Your e-mail cannot be longer than 100 characters. + +
+
+
+ + +
+ + Your password is required. + + + Your password is required to be at least 4 characters. + + + Your password cannot be longer than 50 characters. + +
+ +
+
+ + +
+ + Your confirmation password is required. + + + Your confirmation password is required to be at least 4 characters. + + + Your confirmation password cannot be longer than 50 characters. + +
+
+ + +
+

+
+ If you want to + sign in, you can try the default accounts:
- Administrator (login="admin" and password="admin")
- User (login="user" and password="user").
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/register/register.component.ts b/jhipster/src/main/webapp/app/account/register/register.component.ts new file mode 100644 index 0000000000..dc5c4ef2f4 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/register/register.component.ts @@ -0,0 +1,73 @@ +import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Register } from './register.service'; +import { LoginModalService } from '../../shared'; + +@Component({ + selector: 'jhi-register', + templateUrl: './register.component.html' +}) +export class RegisterComponent implements OnInit, AfterViewInit { + + confirmPassword: string; + doNotMatch: string; + error: string; + errorEmailExists: string; + errorUserExists: string; + registerAccount: any; + success: boolean; + modalRef: NgbModalRef; + + constructor( + private languageService: JhiLanguageService, + private loginModalService: LoginModalService, + private registerService: Register, + private elementRef: ElementRef, + private renderer: Renderer + ) { + this.languageService.setLocations(['register']); + } + + ngOnInit() { + this.success = false; + this.registerAccount = {}; + } + + ngAfterViewInit() { + this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#login'), 'focus', []); + } + + register() { + if (this.registerAccount.password !== this.confirmPassword) { + this.doNotMatch = 'ERROR'; + } else { + this.doNotMatch = null; + this.error = null; + this.errorUserExists = null; + this.errorEmailExists = null; + this.languageService.getCurrent().then(key => { + this.registerAccount.langKey = key; + this.registerService.save(this.registerAccount).subscribe(() => { + this.success = true; + }, (response) => this.processError(response)); + }); + } + } + + openLogin() { + this.modalRef = this.loginModalService.open(); + } + + private processError(response) { + this.success = null; + if (response.status === 400 && response._body === 'login already in use') { + this.errorUserExists = 'ERROR'; + } else if (response.status === 400 && response._body === 'e-mail address already in use') { + this.errorEmailExists = 'ERROR'; + } else { + this.error = 'ERROR'; + } + } +} diff --git a/jhipster/src/main/webapp/app/account/register/register.route.ts b/jhipster/src/main/webapp/app/account/register/register.route.ts new file mode 100644 index 0000000000..3dc47f1e58 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/register/register.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { RegisterComponent } from './register.component'; + +export const registerRoute: Route = { + path: 'register', + component: RegisterComponent, + data: { + authorities: [], + pageTitle: 'register.title' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/account/register/register.service.ts b/jhipster/src/main/webapp/app/account/register/register.service.ts new file mode 100644 index 0000000000..87bbe9d37b --- /dev/null +++ b/jhipster/src/main/webapp/app/account/register/register.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class Register { + + constructor (private http: Http) {} + + save(account: any): Observable { + return this.http.post('api/register', account); + } +} diff --git a/jhipster/src/main/webapp/app/account/settings/settings.component.html b/jhipster/src/main/webapp/app/account/settings/settings.component.html new file mode 100644 index 0000000000..82fca9e804 --- /dev/null +++ b/jhipster/src/main/webapp/app/account/settings/settings.component.html @@ -0,0 +1,86 @@ +
+
+
+

User settings for [{{settingsAccount.login}}]

+ +
+ Settings saved! +
+ + + +
+ +
+ + +
+ + Your first name is required. + + + Your first name is required to be at least 1 character. + + + Your first name cannot be longer than 50 characters. + +
+
+
+ + +
+ + Your last name is required. + + + Your last name is required to be at least 1 character. + + + Your last name cannot be longer than 50 characters. + +
+
+
+ + +
+ + Your e-mail is required. + + + Your e-mail is invalid. + + + Your e-mail is required to be at least 5 characters. + + + Your e-mail cannot be longer than 100 characters. + +
+
+
+ + +
+ +
+
+
+ +
diff --git a/jhipster/src/main/webapp/app/account/settings/settings.component.ts b/jhipster/src/main/webapp/app/account/settings/settings.component.ts new file mode 100644 index 0000000000..37f4c70e3b --- /dev/null +++ b/jhipster/src/main/webapp/app/account/settings/settings.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Principal, AccountService, JhiLanguageHelper } from '../../shared'; + +@Component({ + selector: 'jhi-settings', + templateUrl: './settings.component.html' +}) +export class SettingsComponent implements OnInit { + error: string; + success: string; + settingsAccount: any; + languages: any[]; + + constructor( + private account: AccountService, + private principal: Principal, + private languageService: JhiLanguageService, + private languageHelper: JhiLanguageHelper + ) { + this.languageService.setLocations(['settings']); + } + + ngOnInit () { + this.principal.identity().then((account) => { + this.settingsAccount = this.copyAccount(account); + }); + this.languageHelper.getAll().then((languages) => { + this.languages = languages; + }); + } + + save () { + this.account.save(this.settingsAccount).subscribe(() => { + this.error = null; + this.success = 'OK'; + this.principal.identity(true).then((account) => { + this.settingsAccount = this.copyAccount(account); + }); + this.languageService.getCurrent().then((current) => { + if (this.settingsAccount.langKey !== current) { + this.languageService.changeLanguage(this.settingsAccount.langKey); + } + }); + }, () => { + this.success = null; + this.error = 'ERROR'; + }); + } + + copyAccount (account) { + return { + activated: account.activated, + email: account.email, + firstName: account.firstName, + langKey: account.langKey, + lastName: account.lastName, + login: account.login, + imageUrl: account.imageUrl + }; + } +} diff --git a/jhipster/src/main/webapp/app/account/settings/settings.route.ts b/jhipster/src/main/webapp/app/account/settings/settings.route.ts new file mode 100644 index 0000000000..9c9a852c7b --- /dev/null +++ b/jhipster/src/main/webapp/app/account/settings/settings.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { SettingsComponent } from './settings.component'; + +export const settingsRoute: Route = { + path: 'settings', + component: SettingsComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'global.menu.account.settings' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/admin/admin.module.ts b/jhipster/src/main/webapp/app/admin/admin.module.ts new file mode 100644 index 0000000000..4ac749bc78 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/admin.module.ts @@ -0,0 +1,73 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { ParseLinks } from 'ng-jhipster'; + +import { BaeldungSharedModule } from '../shared'; + +import { + adminState, + AuditsComponent, + UserMgmtComponent, + UserDialogComponent, + UserDeleteDialogComponent, + UserMgmtDetailComponent, + UserMgmtDialogComponent, + UserMgmtDeleteDialogComponent, + LogsComponent, + JhiMetricsMonitoringModalComponent, + JhiMetricsMonitoringComponent, + JhiHealthModalComponent, + JhiHealthCheckComponent, + JhiConfigurationComponent, + JhiDocsComponent, + AuditsService, + JhiConfigurationService, + JhiHealthService, + JhiMetricsService, + LogsService, + UserResolvePagingParams, + UserResolve, + UserModalService +} from './'; + + +@NgModule({ + imports: [ + BaeldungSharedModule, + RouterModule.forRoot(adminState, { useHash: true }) + ], + declarations: [ + AuditsComponent, + UserMgmtComponent, + UserDialogComponent, + UserDeleteDialogComponent, + UserMgmtDetailComponent, + UserMgmtDialogComponent, + UserMgmtDeleteDialogComponent, + LogsComponent, + JhiConfigurationComponent, + JhiHealthCheckComponent, + JhiHealthModalComponent, + JhiDocsComponent, + JhiMetricsMonitoringComponent, + JhiMetricsMonitoringModalComponent + ], + entryComponents: [ + UserMgmtDialogComponent, + UserMgmtDeleteDialogComponent, + JhiHealthModalComponent, + JhiMetricsMonitoringModalComponent, + ], + providers: [ + AuditsService, + JhiConfigurationService, + JhiHealthService, + JhiMetricsService, + LogsService, + UserResolvePagingParams, + UserResolve, + UserModalService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungAdminModule {} diff --git a/jhipster/src/main/webapp/app/admin/admin.route.ts b/jhipster/src/main/webapp/app/admin/admin.route.ts new file mode 100644 index 0000000000..8e64d2d5c0 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/admin.route.ts @@ -0,0 +1,36 @@ +import { Routes, CanActivate } from '@angular/router'; + +import { + auditsRoute, + configurationRoute, + docsRoute, + healthRoute, + logsRoute, + metricsRoute, + userMgmtRoute, + userDialogRoute +} from './'; + +import { UserRouteAccessService } from '../shared'; + +let ADMIN_ROUTES = [ + auditsRoute, + configurationRoute, + docsRoute, + healthRoute, + logsRoute, + ...userMgmtRoute, + metricsRoute +]; + + +export const adminState: Routes = [{ + path: '', + data: { + authorities: ['ROLE_ADMIN'] + }, + canActivate: [UserRouteAccessService], + children: ADMIN_ROUTES +}, + ...userDialogRoute +]; diff --git a/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts b/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts new file mode 100644 index 0000000000..55a5e9c819 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts @@ -0,0 +1,6 @@ +export class AuditData { + constructor( + public remoteAddress: string, + public sessionId: string + ) { } +} diff --git a/jhipster/src/main/webapp/app/admin/audits/audit.model.ts b/jhipster/src/main/webapp/app/admin/audits/audit.model.ts new file mode 100644 index 0000000000..9ca753795c --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audit.model.ts @@ -0,0 +1,10 @@ +import { AuditData } from './audit-data.model'; + +export class Audit { + constructor( + public data: AuditData, + public principal: string, + public timestamp: string, + public type: string + ) { } +} diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.component.html b/jhipster/src/main/webapp/app/admin/audits/audits.component.html new file mode 100644 index 0000000000..9fcbaf7ecd --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audits.component.html @@ -0,0 +1,45 @@ +
+

Audits

+ +
+
+

Filter by date

+

+ from + + to + +

+
+
+ +
+ + + + + + + + + + + + + + + +
DateUserStateExtra data
{{audit.timestamp| date:'medium'}}{{audit.principal}}{{audit.type}} + {{audit.data.message}} + Remote Address {{audit.data.remoteAddress}} +
+
+
+
+ +
+
+ +
+
+
diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.component.ts b/jhipster/src/main/webapp/app/admin/audits/audits.component.ts new file mode 100644 index 0000000000..84e6b40fe8 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audits.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { ParseLinks, JhiLanguageService} from 'ng-jhipster'; + +import { Audit } from './audit.model'; +import { AuditsService } from './audits.service'; +import { ITEMS_PER_PAGE } from '../../shared'; +import { PaginationConfig } from '../../blocks/config/uib-pagination.config'; + +@Component({ + selector: 'jhi-audit', + templateUrl: './audits.component.html' +}) +export class AuditsComponent implements OnInit { + audits: Audit[]; + fromDate: string; + itemsPerPage: any; + links: any; + page: number; + orderProp: string; + reverse: boolean; + toDate: string; + totalItems: number; + + constructor( + private jhiLanguageService: JhiLanguageService, + private auditsService: AuditsService, + private parseLinks: ParseLinks, + private paginationConfig: PaginationConfig, + private datePipe: DatePipe + ) { + this.jhiLanguageService.setLocations(['audits']); + this.itemsPerPage = ITEMS_PER_PAGE; + this.page = 1; + this.reverse = false; + this.orderProp = 'timestamp'; + } + + getAudits() { + return this.sortAudits(this.audits); + } + + loadPage(page: number) { + this.page = page; + this.onChangeDate(); + } + + ngOnInit() { + this.today(); + this.previousMonth(); + this.onChangeDate(); + } + + onChangeDate() { + this.auditsService.query({page: this.page - 1, size: this.itemsPerPage, + fromDate: this.fromDate, toDate: this.toDate}).subscribe(res => { + + this.audits = res.json(); + this.links = this.parseLinks.parse(res.headers.get('link')); + this.totalItems = + res.headers.get('X-Total-Count'); + }); + } + + previousMonth() { + let dateFormat = 'yyyy-MM-dd'; + let fromDate: Date = new Date(); + + if (fromDate.getMonth() === 0) { + fromDate = new Date(fromDate.getFullYear() - 1, 11, fromDate.getDate()); + } else { + fromDate = new Date(fromDate.getFullYear(), fromDate.getMonth() - 1, fromDate.getDate()); + } + + this.fromDate = this.datePipe.transform(fromDate, dateFormat); + } + + today() { + let dateFormat = 'yyyy-MM-dd'; + // Today + 1 day - needed if the current day must be included + let today: Date = new Date(); + + let date = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1); + this.toDate = this.datePipe.transform(date, dateFormat); + } + + private sortAudits(audits: Audit[]) { + audits = audits.slice(0).sort((a, b) => { + if (a[this.orderProp] < b[this.orderProp]) { + return -1; + } else if ([b[this.orderProp] < a[this.orderProp]]) { + return 1; + } else { + return 0; + } + }); + + return this.reverse ? audits.reverse() : audits; + } +} diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.route.ts b/jhipster/src/main/webapp/app/admin/audits/audits.route.ts new file mode 100644 index 0000000000..f0e0fa7be9 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audits.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { AuditsComponent } from './audits.component'; + +export const auditsRoute: Route = { + path: 'audits', + component: AuditsComponent, + data: { + pageTitle: 'audits.title' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.service.ts b/jhipster/src/main/webapp/app/admin/audits/audits.service.ts new file mode 100644 index 0000000000..b373b8915c --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/audits/audits.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, URLSearchParams } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class AuditsService { + constructor(private http: Http) { } + + query(req: any): Observable { + let params: URLSearchParams = new URLSearchParams(); + params.set('fromDate', req.fromDate); + params.set('toDate', req.toDate); + params.set('page', req.page); + params.set('size', req.size); + params.set('sort', req.sort); + + let options = { + search: params + }; + + return this.http.get('management/audits', options); + } +} diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html new file mode 100644 index 0000000000..1d64b41c29 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html @@ -0,0 +1,46 @@ +
+

Configuration

+ + Filter (by prefix) + + + + + + + + + + + + + + +
PrefixProperties
{{entry.prefix}} +
+
{{key}}
+
+ {{entry.properties[key]}} +
+
+
+
+ + + + + + + + + + + + + + +
PropertyValue
{{item.key}} + {{item.val}} +
+
+
diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts new file mode 100644 index 0000000000..88fbc32250 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { JhiConfigurationService } from './configuration.service'; + +@Component({ + selector: 'jhi-configuration', + templateUrl: './configuration.component.html' +}) +export class JhiConfigurationComponent implements OnInit { + allConfiguration: any = null; + configuration: any = null; + configKeys: any[]; + filter: string; + orderProp: string; + reverse: boolean; + + constructor( + private jhiLanguageService: JhiLanguageService, + private configurationService: JhiConfigurationService + ) { + this.jhiLanguageService.setLocations(['configuration']); + this.configKeys = []; + this.filter = ''; + this.orderProp = 'prefix'; + this.reverse = false; + } + + keys(dict): Array { + return (dict === undefined) ? [] : Object.keys(dict); + } + + ngOnInit() { + this.configurationService.get().subscribe((configuration) => { + this.configuration = configuration; + + for (let config of configuration) { + if (config.properties !== undefined) { + this.configKeys.push(Object.keys(config.properties)); + } + } + }); + + this.configurationService.getEnv().subscribe((configuration) => { + this.allConfiguration = configuration; + }); + } +} diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts new file mode 100644 index 0000000000..06866237b0 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { JhiConfigurationComponent } from './configuration.component'; + +export const configurationRoute: Route = { + path: 'jhi-configuration', + component: JhiConfigurationComponent, + data: { + pageTitle: 'configuration.title' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts new file mode 100644 index 0000000000..e239ed3097 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class JhiConfigurationService { + + constructor(private http: Http) { + } + + get(): Observable { + return this.http.get('management/configprops').map((res: Response) => { + let properties: any[] = []; + + const propertiesObject = res.json(); + + for (let key in propertiesObject) { + if (propertiesObject.hasOwnProperty(key)) { + properties.push(propertiesObject[key]); + } + } + + return properties.sort((propertyA, propertyB) => { + return (propertyA.prefix === propertyB.prefix) ? 0 : + (propertyA.prefix < propertyB.prefix) ? -1 : 1; + }); + }); + } + + getEnv(): Observable { + return this.http.get('management/env').map((res: Response) => { + let properties: any = {}; + + const propertiesObject = res.json(); + + for (let key in propertiesObject) { + if (propertiesObject.hasOwnProperty(key)) { + let valsObject = propertiesObject[key]; + let vals: any[] = []; + + for (let valKey in valsObject) { + if (valsObject.hasOwnProperty(valKey)) { + vals.push({key: valKey, val: valsObject[valKey]}); + } + } + properties[key] = vals; + } + } + + return properties; + }); + } +} diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.component.html b/jhipster/src/main/webapp/app/admin/docs/docs.component.html new file mode 100644 index 0000000000..30efbbb93e --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/docs/docs.component.html @@ -0,0 +1,2 @@ + diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.component.ts b/jhipster/src/main/webapp/app/admin/docs/docs.component.ts new file mode 100644 index 0000000000..4bd5bf2329 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/docs/docs.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +@Component({ + selector: 'jhi-docs', + templateUrl: './docs.component.html' +}) +export class JhiDocsComponent { + constructor ( + private jhiLanguageService: JhiLanguageService + ) { + this.jhiLanguageService.setLocations(['global']); + } +} diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.route.ts b/jhipster/src/main/webapp/app/admin/docs/docs.route.ts new file mode 100644 index 0000000000..6e99618f1d --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/docs/docs.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { JhiDocsComponent } from './docs.component'; + +export const docsRoute: Route = { + path: 'docs', + component: JhiDocsComponent, + data: { + pageTitle: 'global.menu.admin.apidocs' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/health/health-modal.component.html b/jhipster/src/main/webapp/app/admin/health/health-modal.component.html new file mode 100644 index 0000000000..1be6271f3b --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health-modal.component.html @@ -0,0 +1,36 @@ + + + diff --git a/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts b/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts new file mode 100644 index 0000000000..1486f20dbf --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { JhiHealthService } from './health.service'; + +@Component({ + selector: 'jhi-health-modal', + templateUrl: './health-modal.component.html' +}) +export class JhiHealthModalComponent { + + currentHealth: any; + + constructor(private healthService: JhiHealthService, public activeModal: NgbActiveModal) {} + + baseName(name) { + return this.healthService.getBaseName(name); + } + + subSystemName(name) { + return this.healthService.getSubSystemName(name); + } + + readableValue(value: number) { + if (this.currentHealth.name !== 'diskSpace') { + return value.toString(); + } + + // Should display storage space in an human readable unit + let val = value / 1073741824; + if (val > 1) { // Value + return val.toFixed(2) + ' GB'; + } else { + return (value / 1048576).toFixed(2) + ' MB'; + } + } +} diff --git a/jhipster/src/main/webapp/app/admin/health/health.component.html b/jhipster/src/main/webapp/app/admin/health/health.component.html new file mode 100644 index 0000000000..6fea72af96 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health.component.html @@ -0,0 +1,34 @@ +
+

+ Health Checks + +

+
+ + + + + + + + + + + + + + + +
Service NameStatusDetails
{{'health.indicator.' + baseName(health.name) | translate}} {{subSystemName(health.name)}} + + {{health.status}} + + + + + +
+
+
diff --git a/jhipster/src/main/webapp/app/admin/health/health.component.ts b/jhipster/src/main/webapp/app/admin/health/health.component.ts new file mode 100644 index 0000000000..ab1be4271f --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health.component.ts @@ -0,0 +1,69 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { JhiHealthService } from './health.service'; +import { JhiHealthModalComponent } from './health-modal.component'; + +@Component({ + selector: 'jhi-health', + templateUrl: './health.component.html', +}) +export class JhiHealthCheckComponent implements OnInit { + healthData: any; + updatingHealth: boolean; + + constructor( + private jhiLanguageService: JhiLanguageService, + private modalService: NgbModal, + private healthService: JhiHealthService + ) { + this.jhiLanguageService.setLocations(['health']); + + } + + ngOnInit() { + this.refresh(); + } + + baseName(name: string) { + return this.healthService.getBaseName(name); + } + + getBadgeClass(statusState) { + if (statusState === 'UP') { + return 'badge-success'; + } else { + return 'badge-danger'; + } + } + + refresh() { + this.updatingHealth = true; + + this.healthService.checkHealth().subscribe(health => { + this.healthData = this.healthService.transformHealthData(health); + this.updatingHealth = false; + }, error => { + if (error.status === 503) { + this.healthData = this.healthService.transformHealthData(error.json()); + this.updatingHealth = false; + } + }); + } + + showHealth(health: any) { + const modalRef = this.modalService.open(JhiHealthModalComponent); + modalRef.componentInstance.currentHealth = health; + modalRef.result.then((result) => { + // Left blank intentionally, nothing to do here + }, (reason) => { + // Left blank intentionally, nothing to do here + }); + } + + subSystemName(name: string) { + return this.healthService.getSubSystemName(name); + } + +} diff --git a/jhipster/src/main/webapp/app/admin/health/health.route.ts b/jhipster/src/main/webapp/app/admin/health/health.route.ts new file mode 100644 index 0000000000..b47d07a0c0 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { JhiHealthCheckComponent } from './health.component'; + +export const healthRoute: Route = { + path: 'jhi-health', + component: JhiHealthCheckComponent, + data: { + pageTitle: 'health.title' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/health/health.service.ts b/jhipster/src/main/webapp/app/admin/health/health.service.ts new file mode 100644 index 0000000000..1bf00dab08 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/health/health.service.ts @@ -0,0 +1,140 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class JhiHealthService { + + separator: string; + + constructor (private http: Http) { + this.separator = '.'; + } + + checkHealth(): Observable { + return this.http.get('management/health').map((res: Response) => res.json()); + } + + transformHealthData(data): any { + let response = []; + this.flattenHealthData(response, null, data); + return response; + } + + getBaseName(name): string { + if (name) { + let split = name.split('.'); + return split[0]; + } + } + + getSubSystemName(name): string { + if (name) { + let split = name.split('.'); + split.splice(0, 1); + let remainder = split.join('.'); + return remainder ? ' - ' + remainder : ''; + } + } + + /* private methods */ + private addHealthObject(result, isLeaf, healthObject, name): any { + + let status: any; + let error: any; + let healthData: any = { + 'name': name, + 'error': error, + 'status': status + }; + + let details = {}; + let hasDetails = false; + + for (let key in healthObject) { + if (healthObject.hasOwnProperty(key)) { + let value = healthObject[key]; + if (key === 'status' || key === 'error') { + healthData[key] = value; + } else { + if (!this.isHealthObject(value)) { + details[key] = value; + hasDetails = true; + } + } + } + } + + // Add the details + if (hasDetails) { + healthData.details = details; + } + + // Only add nodes if they provide additional information + if (isLeaf || hasDetails || healthData.error) { + result.push(healthData); + } + return healthData; + } + + private flattenHealthData (result, path, data): any { + for (let key in data) { + if (data.hasOwnProperty(key)) { + let value = data[key]; + if (this.isHealthObject(value)) { + if (this.hasSubSystem(value)) { + this.addHealthObject(result, false, value, this.getModuleName(path, key)); + this.flattenHealthData(result, this.getModuleName(path, key), value); + } else { + this.addHealthObject(result, true, value, this.getModuleName(path, key)); + } + } + } + } + + return result; + } + + private getModuleName (path, name): string { + let result; + if (path && name) { + result = path + this.separator + name; + } else if (path) { + result = path; + } else if (name) { + result = name; + } else { + result = ''; + } + return result; + } + + private hasSubSystem (healthObject): boolean { + let result = false; + + for (let key in healthObject) { + if (healthObject.hasOwnProperty(key)) { + let value = healthObject[key]; + if (value && value.status) { + result = true; + } + } + } + + return result; + } + + private isHealthObject (healthObject): boolean { + let result = false; + + for (let key in healthObject) { + if (healthObject.hasOwnProperty(key)) { + if (key === 'status') { + result = true; + } + } + } + + return result; + } +} diff --git a/jhipster/src/main/webapp/app/admin/index.ts b/jhipster/src/main/webapp/app/admin/index.ts new file mode 100644 index 0000000000..2273c8af12 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/index.ts @@ -0,0 +1,29 @@ +export * from './audits/audits.component'; +export * from './audits/audits.service'; +export * from './audits/audits.route'; +export * from './audits/audit.model'; +export * from './audits/audit-data.model'; +export * from './configuration/configuration.component'; +export * from './configuration/configuration.service'; +export * from './configuration/configuration.route'; +export * from './docs/docs.component'; +export * from './docs/docs.route'; +export * from './health/health.component'; +export * from './health/health-modal.component'; +export * from './health/health.service'; +export * from './health/health.route'; +export * from './logs/logs.component'; +export * from './logs/logs.service'; +export * from './logs/logs.route'; +export * from './logs/log.model'; +export * from './metrics/metrics.component'; +export * from './metrics/metrics-modal.component'; +export * from './metrics/metrics.service'; +export * from './metrics/metrics.route'; +export * from './user-management/user-management-dialog.component'; +export * from './user-management/user-management-delete-dialog.component'; +export * from './user-management/user-management-detail.component'; +export * from './user-management/user-management.component'; +export * from './user-management/user-management.route'; +export * from './user-management/user-modal.service'; +export * from './admin.route'; diff --git a/jhipster/src/main/webapp/app/admin/logs/log.model.ts b/jhipster/src/main/webapp/app/admin/logs/log.model.ts new file mode 100644 index 0000000000..79cbd34808 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/logs/log.model.ts @@ -0,0 +1,6 @@ +export class Log { + constructor( + public name: string, + public level: string + ) { } +} diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.component.html b/jhipster/src/main/webapp/app/admin/logs/logs.component.html new file mode 100644 index 0000000000..242ef5563a --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/logs/logs.component.html @@ -0,0 +1,27 @@ +
+

Logs

+ +

There are {{ loggers.length }} loggers.

+ + Filter + + + + + + + + + + + + + +
NameLevel
{{logger.name | slice:0:140}} + + + + + +
+
diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.component.ts b/jhipster/src/main/webapp/app/admin/logs/logs.component.ts new file mode 100644 index 0000000000..7655749018 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/logs/logs.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Log } from './log.model'; +import { LogsService } from './logs.service'; + +@Component({ + selector: 'jhi-logs', + templateUrl: './logs.component.html', +}) +export class LogsComponent implements OnInit { + + loggers: Log[]; + filter: string; + orderProp: string; + reverse: boolean; + + constructor ( + private jhiLanguageService: JhiLanguageService, + private logsService: LogsService + ) { + this.filter = ''; + this.orderProp = 'name'; + this.reverse = false; + this.jhiLanguageService.setLocations(['logs']); + } + + ngOnInit() { + this.logsService.findAll().subscribe(loggers => this.loggers = loggers); + } + + changeLevel (name: string, level: string) { + let log = new Log(name, level); + this.logsService.changeLevel(log).subscribe(() => { + this.logsService.findAll().subscribe(loggers => this.loggers = loggers); + }); + } +} diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.route.ts b/jhipster/src/main/webapp/app/admin/logs/logs.route.ts new file mode 100644 index 0000000000..1077f5da32 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/logs/logs.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { LogsComponent } from './logs.component'; + +export const logsRoute: Route = { + path: 'logs', + component: LogsComponent, + data: { + pageTitle: 'logs.title' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.service.ts b/jhipster/src/main/webapp/app/admin/logs/logs.service.ts new file mode 100644 index 0000000000..c937c10a42 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/logs/logs.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import { Log } from './log.model'; + +@Injectable() +export class LogsService { + constructor(private http: Http) { } + + changeLevel(log: Log): Observable { + return this.http.put('management/logs', log); + } + + findAll(): Observable { + return this.http.get('management/logs').map((res: Response) => res.json()); + } +} diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html new file mode 100644 index 0000000000..b8f0391acf --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html @@ -0,0 +1,56 @@ + + + + diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts new file mode 100644 index 0000000000..4a3034dd94 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'jhi-metrics-modal', + templateUrl: './metrics-modal.component.html' +}) +export class JhiMetricsMonitoringModalComponent implements OnInit { + + threadDumpFilter: any; + threadDump: any; + threadDumpAll = 0; + threadDumpBlocked = 0; + threadDumpRunnable = 0; + threadDumpTimedWaiting = 0; + threadDumpWaiting = 0; + + constructor(public activeModal: NgbActiveModal) {} + + ngOnInit() { + this.threadDump.forEach((value) => { + if (value.threadState === 'RUNNABLE') { + this.threadDumpRunnable += 1; + } else if (value.threadState === 'WAITING') { + this.threadDumpWaiting += 1; + } else if (value.threadState === 'TIMED_WAITING') { + this.threadDumpTimedWaiting += 1; + } else if (value.threadState === 'BLOCKED') { + this.threadDumpBlocked += 1; + } + }); + + this.threadDumpAll = this.threadDumpRunnable + this.threadDumpWaiting + + this.threadDumpTimedWaiting + this.threadDumpBlocked; + } + + getBadgeClass (threadState) { + if (threadState === 'RUNNABLE') { + return 'badge-success'; + } else if (threadState === 'WAITING') { + return 'badge-info'; + } else if (threadState === 'TIMED_WAITING') { + return 'badge-warning'; + } else if (threadState === 'BLOCKED') { + return 'badge-danger'; + } + } +} diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html new file mode 100644 index 0000000000..800ca21ef9 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html @@ -0,0 +1,253 @@ +
+

+ Application Metrics + +

+ +

JVM Metrics

+
+
+ Memory +

Total Memory ({{metrics.gauges['jvm.memory.total.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.total.max'].value / 1000000 | number:'1.0-0'}}M)

+ + {{metrics.gauges['jvm.memory.total.used'].value * 100 / metrics.gauges['jvm.memory.total.max'].value | number:'1.0-0'}}% + +

Heap Memory ({{metrics.gauges['jvm.memory.heap.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.heap.max'].value / 1000000 | number:'1.0-0'}}M)

+ + {{metrics.gauges['jvm.memory.heap.used'].value * 100 / metrics.gauges['jvm.memory.heap.max'].value | number:'1.0-0'}}% + +

Non-Heap Memory ({{metrics.gauges['jvm.memory.non-heap.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.non-heap.committed'].value / 1000000 | number:'1.0-0'}}M)

+ + {{metrics.gauges['jvm.memory.non-heap.used'].value * 100 / metrics.gauges['jvm.memory.non-heap.committed'].value | number:'1.0-0'}}% + +
+
+ Threads (Total: {{metrics.gauges['jvm.threads.count'].value}}) +

Runnable {{metrics.gauges['jvm.threads.runnable.count'].value}}

+ + {{metrics.gauges['jvm.threads.runnable.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}% + +

Timed Waiting ({{metrics.gauges['jvm.threads.timed_waiting.count'].value}})

+ + {{metrics.gauges['jvm.threads.timed_waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}% + +

Waiting ({{metrics.gauges['jvm.threads.waiting.count'].value}})

+ + {{metrics.gauges['jvm.threads.waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}% + +

Blocked ({{metrics.gauges['jvm.threads.blocked.count'].value}})

+ + {{metrics.gauges['jvm.threads.blocked.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}% + +
+
+ Garbage collections +
+
Mark Sweep count
+
{{metrics.gauges['jvm.garbage.PS-MarkSweep.count'].value}}
+
+
+
Mark Sweep time
+
{{metrics.gauges['jvm.garbage.PS-MarkSweep.time'].value}}ms
+
+
+
Scavenge count
+
{{metrics.gauges['jvm.garbage.PS-Scavenge.count'].value}}
+
+
+
Scavenge time
+
{{metrics.gauges['jvm.garbage.PS-Scavenge.time'].value}}ms
+
+
+
+
Updating...
+ +

HTTP requests (events per second)

+

+ Active requests {{metrics.counters['com.codahale.metrics.servlet.InstrumentedFilter.activeRequests'].count | number:'1.0-0'}} - Total requests {{metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count | number:'1.0-0'}} +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeCountMeanAverage (1 min)Average (5 min)Average (15 min)
OK + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].count}} + + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].mean_rate | number:'1.0-2'}} + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m1_rate | number:'1.0-2'}} + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m5_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m15_rate | number:'1.0-2'}} +
Not Found + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].count}} + + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].mean_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m1_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m5_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m15_rate | number:'1.0-2'}} +
Server error + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].count}} + + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].mean_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m1_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m5_rate | number:'1.0-2'}} + + {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m15_rate | number:'1.0-2'}} +
+
+ +

Services statistics (time in millisecond)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Service nameCountMeanMinp50p75p95p99Max
{{entry.key}}{{entry.value.count}}{{entry.value.mean * 1000 | number:'1.0-0'}}{{entry.value.min * 1000 | number:'1.0-0'}}{{entry.value.p50 * 1000 | number:'1.0-0'}}{{entry.value.p75 * 1000 | number:'1.0-0'}}{{entry.value.p95 * 1000 | number:'1.0-0'}}{{entry.value.p99 * 1000 | number:'1.0-0'}}{{entry.value.max * 1000 | number:'1.0-0'}}
+
+ +

Cache statistics

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cache nameCache HitsCache MissesCache GetsCache PutsCache RemovalsCache EvictionsCache Hit %Cache Miss %Average get time (µs)Average put time (µs)Average remove time (µs)
{{entry.key}}{{metrics.gauges[entry.key + '.cache-hits'].value}}{{metrics.gauges[entry.key + '.cache-misses'].value}}{{metrics.gauges[entry.key + '.cache-gets'].value}}{{metrics.gauges[entry.key + '.cache-puts'].value}}{{metrics.gauges[entry.key + '.cache-removals'].value}}{{metrics.gauges[entry.key + '.cache-evictions'].value}}{{metrics.gauges[entry.key + '.cache-hit-percentage'].value}}{{metrics.gauges[entry.key + '.cache-miss-percentage'].value }}{{metrics.gauges[entry.key + '.average-get-time'].value | number : '1.2-2' }}{{metrics.gauges[entry.key + '.average-put-time'].value | number : '1.2-2'}}{{metrics.gauges[entry.key + '.average-remove-time'].value | number : '1.2-2' }}
+
+ +

DataSource statistics (time in millisecond)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Usage ({{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value}} / {{metrics.gauges['HikariPool-1.pool.TotalConnections'].value}})CountMeanMinp50p75p95p99Max
+
+ + {{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value * 100 / metrics.gauges['HikariPool-1.pool.TotalConnections'].value | number:'1.0-0'}}% + +
+
{{metrics.histograms['HikariPool-1.pool.Usage'].count}}{{metrics.histograms['HikariPool-1.pool.Usage'].mean | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].min | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].p50 | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].p75 | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].p95 | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].p99 | number:'1.0-2'}}{{metrics.histograms['HikariPool-1.pool.Usage'].max | number:'1.0-2'}}
+
+
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts new file mode 100644 index 0000000000..21c39a3deb --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts @@ -0,0 +1,74 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { JhiMetricsMonitoringModalComponent } from './metrics-modal.component'; +import { JhiMetricsService } from './metrics.service'; + +@Component({ + selector: 'jhi-metrics', + templateUrl: './metrics.component.html', +}) +export class JhiMetricsMonitoringComponent implements OnInit { + metrics: any = {}; + cachesStats: any = {}; + servicesStats: any = {}; + updatingMetrics = true; + JCACHE_KEY: string ; + + constructor( + private jhiLanguageService: JhiLanguageService, + private modalService: NgbModal, + private metricsService: JhiMetricsService + ) { + this.JCACHE_KEY = 'jcache.statistics'; + this.jhiLanguageService.setLocations(['metrics']); + } + + ngOnInit() { + this.refresh(); + } + + refresh () { + this.updatingMetrics = true; + this.metricsService.getMetrics().subscribe((metrics) => { + this.metrics = metrics; + this.updatingMetrics = false; + this.servicesStats = {}; + this.cachesStats = {}; + Object.keys(metrics.timers).forEach((key) => { + let value = metrics.timers[key]; + if (key.indexOf('web.rest') !== -1 || key.indexOf('service') !== -1) { + this.servicesStats[key] = value; + } + }); + Object.keys(metrics.gauges).forEach((key) => { + if (key.indexOf('jcache.statistics') !== -1) { + let value = metrics.gauges[key].value; + // remove gets or puts + let index = key.lastIndexOf('.'); + let newKey = key.substr(0, index); + + // Keep the name of the domain + this.cachesStats[newKey] = { + 'name': this.JCACHE_KEY.length, + 'value': value + }; + } + }); + }); + } + + refreshThreadDumpData () { + this.metricsService.threadDump().subscribe((data) => { + const modalRef = this.modalService.open(JhiMetricsMonitoringModalComponent, { size: 'lg'}); + modalRef.componentInstance.threadDump = data; + modalRef.result.then((result) => { + // Left blank intentionally, nothing to do here + }, (reason) => { + // Left blank intentionally, nothing to do here + }); + }); + } + +} diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts new file mode 100644 index 0000000000..cc13284dc8 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts @@ -0,0 +1,12 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { JhiMetricsMonitoringComponent } from './metrics.component'; + +export const metricsRoute: Route = { + path: 'jhi-metrics', + component: JhiMetricsMonitoringComponent, + data: { + pageTitle: 'metrics.title' + } +}; diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts new file mode 100644 index 0000000000..2b31947339 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class JhiMetricsService { + + constructor (private http: Http) {} + + getMetrics(): Observable { + return this.http.get('management/metrics').map((res: Response) => res.json()); + } + + threadDump(): Observable { + return this.http.get('management/dump').map((res: Response) => res.json()); + } +} diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html new file mode 100644 index 0000000000..452f1612c8 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html @@ -0,0 +1,19 @@ +
+ + + +
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts new file mode 100644 index 0000000000..2c5c2a6a01 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, JhiLanguageService } from 'ng-jhipster'; + +import { User, UserService } from '../../shared'; +import { UserModalService } from './user-modal.service'; + +@Component({ + selector: 'jhi-user-mgmt-delete-dialog', + templateUrl: './user-management-delete-dialog.component.html' +}) +export class UserMgmtDeleteDialogComponent { + + user: User; + + constructor( + private jhiLanguageService: JhiLanguageService, + private userService: UserService, + public activeModal: NgbActiveModal, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['user-management']); + } + + clear () { + this.activeModal.dismiss('cancel'); + } + + confirmDelete (login) { + this.userService.delete(login).subscribe(response => { + this.eventManager.broadcast({ name: 'userListModification', + content: 'Deleted a user'}); + this.activeModal.dismiss(true); + }); + } + +} + +@Component({ + selector: 'jhi-user-delete-dialog', + template: '' +}) +export class UserDeleteDialogComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private userModalService: UserModalService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + this.modalRef = this.userModalService.open(UserMgmtDeleteDialogComponent, params['login']); + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html new file mode 100644 index 0000000000..d5deb8fa24 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html @@ -0,0 +1,44 @@ +
+

+ User [{{user.login}}] +

+
+
Login
+
+ {{user.login}} + Deactivated + Activated +
+
First Name
+
{{user.firstName}}
+
Last Name
+
{{user.lastName}}
+
Email
+
{{user.email}}
+
Lang Key
+
{{user.langKey}}
+
Created By
+
{{user.createdBy}}
+
Created Date
+
{{user.createdDate | date:'dd/MM/yy HH:mm' }}
+
Last Modified By
+
{{user.lastModifiedBy}}
+
Last Modified Date
+
{{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}
+
Profiles
+
+
    +
  • + {{authority}} +
  • +
+
+
+ +
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts new file mode 100644 index 0000000000..564b1daf3d --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { User, UserService } from '../../shared'; + +@Component({ + selector: 'jhi-user-mgmt-detail', + templateUrl: './user-management-detail.component.html' +}) +export class UserMgmtDetailComponent implements OnInit, OnDestroy { + + user: User; + private subscription: any; + + constructor( + private jhiLanguageService: JhiLanguageService, + private userService: UserService, + private route: ActivatedRoute + ) { + this.jhiLanguageService.setLocations(['user-management']); + } + + ngOnInit() { + this.subscription = this.route.params.subscribe(params => { + this.load(params['login']); + }); + } + + load (login) { + this.userService.find(login).subscribe(user => { + this.user = user; + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + +} diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html new file mode 100644 index 0000000000..6359527df7 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html @@ -0,0 +1,112 @@ +
+ + + + +
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts new file mode 100644 index 0000000000..ebbf073df4 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts @@ -0,0 +1,89 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, JhiLanguageService } from 'ng-jhipster'; + +import { UserModalService } from './user-modal.service'; +import { JhiLanguageHelper, User, UserService } from '../../shared'; + +@Component({ + selector: 'jhi-user-mgmt-dialog', + templateUrl: './user-management-dialog.component.html' +}) +export class UserMgmtDialogComponent implements OnInit { + + user: User; + languages: any[]; + authorities: any[]; + isSaving: Boolean; + + constructor ( + public activeModal: NgbActiveModal, + private languageHelper: JhiLanguageHelper, + private jhiLanguageService: JhiLanguageService, + private userService: UserService, + private eventManager: EventManager + ) {} + + ngOnInit() { + this.isSaving = false; + this.authorities = ['ROLE_USER', 'ROLE_ADMIN']; + this.languageHelper.getAll().then((languages) => { + this.languages = languages; + }); + this.jhiLanguageService.setLocations(['user-management']); + } + + clear() { + this.activeModal.dismiss('cancel'); + } + + save() { + this.isSaving = true; + if (this.user.id !== null) { + this.userService.update(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError()); + } else { + this.userService.create(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError()); + } + } + + private onSaveSuccess(result) { + this.eventManager.broadcast({ name: 'userListModification', content: 'OK' }); + this.isSaving = false; + this.activeModal.dismiss(result); + } + + private onSaveError() { + this.isSaving = false; + } +} + +@Component({ + selector: 'jhi-user-dialog', + template: '' +}) +export class UserDialogComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private userModalService: UserModalService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + if ( params['login'] ) { + this.modalRef = this.userModalService.open(UserMgmtDialogComponent, params['login']); + } else { + this.modalRef = this.userModalService.open(UserMgmtDialogComponent); + } + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html new file mode 100644 index 0000000000..73fc18469a --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html @@ -0,0 +1,81 @@ +
+

+ Users + +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDLogin Email Lang Key ProfilesCreated Date Last Modified By Last Modified Date
{{user.id}}{{user.login}}{{user.email}} + Deactivated + Activated + {{user.langKey}} +
+ {{ authority }} +
+
{{user.createdDate | date:'dd/MM/yy HH:mm'}}{{user.lastModifiedBy}}{{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}} +
+ + + +
+
+
+
+
+ +
+
+ +
+
+
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts new file mode 100644 index 0000000000..213bb24923 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts @@ -0,0 +1,131 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Response } from '@angular/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { EventManager, PaginationUtil, ParseLinks, AlertService, JhiLanguageService } from 'ng-jhipster'; + +import { ITEMS_PER_PAGE, Principal, User, UserService } from '../../shared'; +import { PaginationConfig } from '../../blocks/config/uib-pagination.config'; + +@Component({ + selector: 'jhi-user-mgmt', + templateUrl: './user-management.component.html' +}) +export class UserMgmtComponent implements OnInit, OnDestroy { + + currentAccount: any; + users: User[]; + error: any; + success: any; + routeData: any; + links: any; + totalItems: any; + queryCount: any; + itemsPerPage: any; + page: any; + predicate: any; + previousPage: any; + reverse: any; + + constructor( + private jhiLanguageService: JhiLanguageService, + private userService: UserService, + private parseLinks: ParseLinks, + private alertService: AlertService, + private principal: Principal, + private eventManager: EventManager, private paginationUtil: PaginationUtil, + private paginationConfig: PaginationConfig, + private activatedRoute: ActivatedRoute, + private router: Router + ) { + this.itemsPerPage = ITEMS_PER_PAGE; + this.routeData = this.activatedRoute.data.subscribe(data => { + this.page = data['pagingParams'].page; + this.previousPage = data['pagingParams'].page; + this.reverse = data['pagingParams'].ascending; + this.predicate = data['pagingParams'].predicate; + }); + this.jhiLanguageService.setLocations(['user-management']); + } + + ngOnInit() { + this.principal.identity().then((account) => { + this.currentAccount = account; + this.loadAll(); + this.registerChangeInUsers(); + }); + } + + ngOnDestroy() { + this.routeData.unsubscribe(); + } + + registerChangeInUsers() { + this.eventManager.subscribe('userListModification', (response) => this.loadAll()); + } + + setActive (user, isActivated) { + user.activated = isActivated; + + this.userService.update(user).subscribe( + response => { + if (response.status === 200) { + this.error = null; + this.success = 'OK'; + this.loadAll(); + } else { + this.success = null; + this.error = 'ERROR'; + } + }); + } + + loadAll () { + this.userService.query({ + page: this.page - 1, + size: this.itemsPerPage, + sort: this.sort()}).subscribe( + (res: Response) => this.onSuccess(res.json(), res.headers), + (res: Response) => this.onError(res.json()) + ); + } + + trackIdentity (index, item: User) { + return item.id; + } + + sort () { + let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')]; + if (this.predicate !== 'id') { + result.push('id'); + } + return result; + } + + loadPage (page: number) { + if (page !== this.previousPage) { + this.previousPage = page; + this.transition(); + } + } + + transition () { + this.router.navigate(['/user-management'], { queryParams: + { + page: this.page, + sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc') + } + }); + this.loadAll(); + } + + private onSuccess(data, headers) { + this.links = this.parseLinks.parse(headers.get('link')); + this.totalItems = headers.get('X-Total-Count'); + this.queryCount = this.totalItems; + this.users = data; + } + + private onError(error) { + this.alertService.error(error.error, error.message, null); + } +} diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts new file mode 100644 index 0000000000..6aca0cdeb1 --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { PaginationUtil } from 'ng-jhipster'; + +import { UserMgmtComponent } from './user-management.component'; +import { UserMgmtDetailComponent } from './user-management-detail.component'; +import { UserDialogComponent } from './user-management-dialog.component'; +import { UserDeleteDialogComponent } from './user-management-delete-dialog.component'; + +import { Principal } from '../../shared'; + + +@Injectable() +export class UserResolve implements CanActivate { + + constructor(private principal: Principal) { } + + canActivate() { + return this.principal.identity().then(account => this.principal.hasAnyAuthority(['ROLE_ADMIN'])); + } +} + +@Injectable() +export class UserResolvePagingParams implements Resolve { + + constructor(private paginationUtil: PaginationUtil) {} + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + let page = route.queryParams['page'] ? route.queryParams['page'] : '1'; + let sort = route.queryParams['sort'] ? route.queryParams['sort'] : 'id,asc'; + return { + page: this.paginationUtil.parsePage(page), + predicate: this.paginationUtil.parsePredicate(sort), + ascending: this.paginationUtil.parseAscending(sort) + }; + } +} + +export const userMgmtRoute: Routes = [ + { + path: 'user-management', + component: UserMgmtComponent, + resolve: { + 'pagingParams': UserResolvePagingParams + }, + data: { + pageTitle: 'userManagement.home.title' + } + }, + { + path: 'user-management/:login', + component: UserMgmtDetailComponent, + data: { + pageTitle: 'userManagement.home.title' + } + } +]; + +export const userDialogRoute: Routes = [ + { + path: 'user-management-new', + component: UserDialogComponent, + outlet: 'popup' + }, + { + path: 'user-management/:login/edit', + component: UserDialogComponent, + outlet: 'popup' + }, + { + path: 'user-management/:login/delete', + component: UserDeleteDialogComponent, + outlet: 'popup' + } +]; diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts b/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts new file mode 100644 index 0000000000..006bc2a53c --- /dev/null +++ b/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts @@ -0,0 +1,42 @@ +import { Injectable, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + +import { UserMgmtDialogComponent } from './user-management-dialog.component'; +import { User, UserService } from '../../shared'; + +@Injectable() +export class UserModalService { + private isOpen = false; + constructor ( + private modalService: NgbModal, + private router: Router, + private userService: UserService + ) {} + + open (component: Component, login?: string): NgbModalRef { + if (this.isOpen) { + return; + } + this.isOpen = true; + + if (login) { + this.userService.find(login).subscribe(user => this.userModalRef(component, user)); + } else { + return this.userModalRef(component, new User()); + } + } + + userModalRef(component: Component, user: User): NgbModalRef { + let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'}); + modalRef.componentInstance.user = user; + modalRef.result.then(result => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }, (reason) => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }); + return modalRef; + } +} diff --git a/jhipster/src/main/webapp/app/app.constants.ts b/jhipster/src/main/webapp/app/app.constants.ts new file mode 100644 index 0000000000..8f53b6f2e8 --- /dev/null +++ b/jhipster/src/main/webapp/app/app.constants.ts @@ -0,0 +1,7 @@ +// DO NOT EDIT THIS FILE, EDIT THE WEBPACK COMMON CONFIG INSTEAD, WHICH WILL MODIFY THIS FILE +let _VERSION = '0.0.0'; // This value will be overwritten by webpack +let _DEBUG_INFO_ENABLED = true; // This value will be overwritten by webpack +/* @toreplace VERSION */ +/* @toreplace DEBUG_INFO_ENABLED */ +export const VERSION = _VERSION; +export const DEBUG_INFO_ENABLED = _DEBUG_INFO_ENABLED; diff --git a/jhipster/src/main/webapp/app/app.main.ts b/jhipster/src/main/webapp/app/app.main.ts new file mode 100644 index 0000000000..301e5ffb81 --- /dev/null +++ b/jhipster/src/main/webapp/app/app.main.ts @@ -0,0 +1,11 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { ProdConfig } from './blocks/config/prod.config'; +import { BaeldungAppModule } from './app.module'; + +ProdConfig(); + +if (module['hot']) { + module['hot'].accept(); +} + +platformBrowserDynamic().bootstrapModule(BaeldungAppModule); diff --git a/jhipster/src/main/webapp/app/app.module.ts b/jhipster/src/main/webapp/app/app.module.ts new file mode 100644 index 0000000000..dc82c6cdce --- /dev/null +++ b/jhipster/src/main/webapp/app/app.module.ts @@ -0,0 +1,58 @@ +import './vendor.ts'; + +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { BrowserModule } from '@angular/platform-browser'; +import { Ng2Webstorage } from 'ng2-webstorage'; + +import { BaeldungSharedModule, UserRouteAccessService } from './shared'; +import { BaeldungHomeModule } from './home/home.module'; +import { BaeldungAdminModule } from './admin/admin.module'; +import { BaeldungAccountModule } from './account/account.module'; +import { BaeldungEntityModule } from './entities/entity.module'; + +import { LayoutRoutingModule } from './layouts'; +import { customHttpProvider } from './blocks/interceptor/http.provider'; +import { PaginationConfig } from './blocks/config/uib-pagination.config'; + +import { + JhiMainComponent, + NavbarComponent, + FooterComponent, + ProfileService, + PageRibbonComponent, + ActiveMenuDirective, + ErrorComponent +} from './layouts'; + + +@NgModule({ + imports: [ + BrowserModule, + LayoutRoutingModule, + Ng2Webstorage.forRoot({ prefix: 'jhi', separator: '-'}), + BaeldungSharedModule, + BaeldungHomeModule, + BaeldungAdminModule, + BaeldungAccountModule, + BaeldungEntityModule + ], + declarations: [ + JhiMainComponent, + NavbarComponent, + ErrorComponent, + PageRibbonComponent, + ActiveMenuDirective, + FooterComponent + ], + providers: [ + ProfileService, + { provide: Window, useValue: window }, + { provide: Document, useValue: document }, + customHttpProvider(), + PaginationConfig, + UserRouteAccessService + ], + bootstrap: [ JhiMainComponent ] +}) +export class BaeldungAppModule {} diff --git a/jhipster/src/main/webapp/app/app.route.ts b/jhipster/src/main/webapp/app/app.route.ts new file mode 100644 index 0000000000..bfddd9bb7e --- /dev/null +++ b/jhipster/src/main/webapp/app/app.route.ts @@ -0,0 +1,9 @@ +import { Route } from '@angular/router'; + +import { NavbarComponent } from './layouts'; + +export const navbarRoute: Route = { + path: '', + component: NavbarComponent, + outlet: 'navbar' + }; diff --git a/jhipster/src/main/webapp/app/blocks/config/prod.config.ts b/jhipster/src/main/webapp/app/blocks/config/prod.config.ts new file mode 100644 index 0000000000..cc0050de27 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/config/prod.config.ts @@ -0,0 +1,9 @@ +import { enableProdMode } from '@angular/core'; +import { DEBUG_INFO_ENABLED } from '../../app.constants'; + +export function ProdConfig() { + // disable debug data on prod profile to improve performance + if (!DEBUG_INFO_ENABLED) { + enableProdMode(); + } +} diff --git a/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts b/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts new file mode 100644 index 0000000000..245e1f6bf1 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts @@ -0,0 +1,13 @@ +import { ITEMS_PER_PAGE } from '../../shared'; +import { Injectable } from '@angular/core'; +import { NgbPaginationConfig} from '@ng-bootstrap/ng-bootstrap'; + +@Injectable() +export class PaginationConfig { + constructor(private config: NgbPaginationConfig) { + config.boundaryLinks = true; + config.maxSize = 5; + config.pageSize = ITEMS_PER_PAGE; + config.size = 'sm'; + } +} diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts new file mode 100644 index 0000000000..49f517148c --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts @@ -0,0 +1,34 @@ +import { HttpInterceptor } from 'ng-jhipster'; +import { RequestOptionsArgs, Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { Injector } from '@angular/core'; +import { AuthService } from '../../shared/auth/auth.service'; +import { Principal } from '../../shared/auth/principal.service'; +import { AuthServerProvider } from '../../shared/auth/auth-jwt.service'; + +export class AuthExpiredInterceptor extends HttpInterceptor { + + constructor(private injector: Injector) { + super(); + } + + requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs { + return options; + } + + responseIntercept(observable: Observable): Observable { + let self = this; + + return > observable.catch((error, source) => { + if (error.status === 401) { + let principal: Principal = self.injector.get(Principal); + + if (principal.isAuthenticated()) { + let auth: AuthService = self.injector.get(AuthService); + auth.authorize(true); + } + } + return Observable.throw(error); + }); + } +} diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts new file mode 100644 index 0000000000..cf90284116 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts @@ -0,0 +1,27 @@ +import { Observable } from 'rxjs/Observable'; +import { RequestOptionsArgs, Response } from '@angular/http'; +import { LocalStorageService, SessionStorageService } from 'ng2-webstorage'; +import { HttpInterceptor } from 'ng-jhipster'; + +export class AuthInterceptor extends HttpInterceptor { + + constructor( + private localStorage: LocalStorageService, + private sessionStorage: SessionStorageService + ) { + super(); + } + + requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs { + let token = this.localStorage.retrieve('authenticationToken') || this.sessionStorage.retrieve('authenticationToken'); + if (!!token) { + options.headers.append('Authorization', 'Bearer ' + token); + } + return options; + } + + responseIntercept(observable: Observable): Observable { + return observable; // by pass + } + +} diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts new file mode 100644 index 0000000000..6e22557d05 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts @@ -0,0 +1,24 @@ +import { HttpInterceptor, EventManager } from 'ng-jhipster'; +import { RequestOptionsArgs, Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +export class ErrorHandlerInterceptor extends HttpInterceptor { + + constructor(private eventManager: EventManager) { + super(); + } + + requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs { + return options; + } + + responseIntercept(observable: Observable): Observable { + return > observable.catch(error => { + if (!(error.status === 401 && (error.text() === '' || + (error.json().path && error.json().path.indexOf('/api/account') === 0 )))) { + this.eventManager.broadcast( {name: 'baeldungApp.httpError', content: error}); + } + return Observable.throw(error); + }); + } +} diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts b/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts new file mode 100644 index 0000000000..a9689662a3 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts @@ -0,0 +1,45 @@ +import { Injector } from '@angular/core'; +import { Http, XHRBackend, RequestOptions } from '@angular/http'; +import { EventManager, InterceptableHttp } from 'ng-jhipster'; + +import { AuthInterceptor } from './auth.interceptor'; +import { LocalStorageService, SessionStorageService } from 'ng2-webstorage'; +import { AuthExpiredInterceptor } from './auth-expired.interceptor'; +import { ErrorHandlerInterceptor } from './errorhandler.interceptor'; +import { NotificationInterceptor } from './notification.interceptor'; + +export function interceptableFactory( + backend: XHRBackend, + defaultOptions: RequestOptions, + localStorage: LocalStorageService, + sessionStorage: SessionStorageService, + injector: Injector, + eventManager: EventManager +) { + return new InterceptableHttp( + backend, + defaultOptions, + [ + new AuthInterceptor(localStorage, sessionStorage), + new AuthExpiredInterceptor(injector), + // Other interceptors can be added here + new ErrorHandlerInterceptor(eventManager), + new NotificationInterceptor() + ] + ); +}; + +export function customHttpProvider() { + return { + provide: Http, + useFactory: interceptableFactory, + deps: [ + XHRBackend, + RequestOptions, + LocalStorageService, + SessionStorageService, + Injector, + EventManager + ] + }; +}; diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts new file mode 100644 index 0000000000..ebee7688c5 --- /dev/null +++ b/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts @@ -0,0 +1,33 @@ +import { HttpInterceptor } from 'ng-jhipster'; +import { RequestOptionsArgs, Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +export class NotificationInterceptor extends HttpInterceptor { + + constructor() { + super(); + } + + requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs { + return options; + } + + responseIntercept(observable: Observable): Observable { + return > observable.catch((error) => { + let arr = Array.from(error.headers._headers); + let headers = []; + let i; + for (i = 0; i < arr.length; i++) { + if (arr[i][0].endsWith('app-alert') || arr[i][0].endsWith('app-params')) { + headers.push(arr[i][0]); + } + } + headers.sort(); + let alertKey = headers.length >= 1 ? error.headers.get(headers[0]) : null; + if (typeof alertKey === 'string') { + // AlertService.success(alertKey, { param: response.headers(headers[1])}); + } + return Observable.throw(error); + }); + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html new file mode 100644 index 0000000000..63540ec6bd --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html @@ -0,0 +1,19 @@ +
+ + + +
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts new file mode 100644 index 0000000000..29ef818ddf --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, JhiLanguageService } from 'ng-jhipster'; + +import { Comment } from './comment.model'; +import { CommentPopupService } from './comment-popup.service'; +import { CommentService } from './comment.service'; + +@Component({ + selector: 'jhi-comment-delete-dialog', + templateUrl: './comment-delete-dialog.component.html' +}) +export class CommentDeleteDialogComponent { + + comment: Comment; + + constructor( + private jhiLanguageService: JhiLanguageService, + private commentService: CommentService, + public activeModal: NgbActiveModal, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['comment']); + } + + clear () { + this.activeModal.dismiss('cancel'); + } + + confirmDelete (id: number) { + this.commentService.delete(id).subscribe(response => { + this.eventManager.broadcast({ + name: 'commentListModification', + content: 'Deleted an comment' + }); + this.activeModal.dismiss(true); + }); + } +} + +@Component({ + selector: 'jhi-comment-delete-popup', + template: '' +}) +export class CommentDeletePopupComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private commentPopupService: CommentPopupService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + this.modalRef = this.commentPopupService + .open(CommentDeleteDialogComponent, params['id']); + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html new file mode 100644 index 0000000000..add5b9e208 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html @@ -0,0 +1,35 @@ + +
+

Comment {{comment.id}}

+
+ +
+
Text
+
+ {{comment.text}} +
+
Creation Date
+
+ {{comment.creationDate | date:'mediumDate'}} +
+
Post
+
+ +
+
+ + + + +
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts new file mode 100644 index 0000000000..c2bf7fce0f --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { JhiLanguageService } from 'ng-jhipster'; +import { Comment } from './comment.model'; +import { CommentService } from './comment.service'; + +@Component({ + selector: 'jhi-comment-detail', + templateUrl: './comment-detail.component.html' +}) +export class CommentDetailComponent implements OnInit, OnDestroy { + + comment: Comment; + private subscription: any; + + constructor( + private jhiLanguageService: JhiLanguageService, + private commentService: CommentService, + private route: ActivatedRoute + ) { + this.jhiLanguageService.setLocations(['comment']); + } + + ngOnInit() { + this.subscription = this.route.params.subscribe(params => { + this.load(params['id']); + }); + } + + load (id) { + this.commentService.find(id).subscribe(comment => { + this.comment = comment; + }); + } + previousState() { + window.history.back(); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html new file mode 100644 index 0000000000..7e13c33177 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html @@ -0,0 +1,76 @@ + + +
+ + + + +
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts new file mode 100644 index 0000000000..1a30305c96 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Response } from '@angular/http'; + +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, AlertService, JhiLanguageService } from 'ng-jhipster'; + +import { Comment } from './comment.model'; +import { CommentPopupService } from './comment-popup.service'; +import { CommentService } from './comment.service'; +import { Post, PostService } from '../post'; +@Component({ + selector: 'jhi-comment-dialog', + templateUrl: './comment-dialog.component.html' +}) +export class CommentDialogComponent implements OnInit { + + comment: Comment; + authorities: any[]; + isSaving: boolean; + + posts: Post[]; + constructor( + public activeModal: NgbActiveModal, + private jhiLanguageService: JhiLanguageService, + private alertService: AlertService, + private commentService: CommentService, + private postService: PostService, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['comment']); + } + + ngOnInit() { + this.isSaving = false; + this.authorities = ['ROLE_USER', 'ROLE_ADMIN']; + this.postService.query().subscribe( + (res: Response) => { this.posts = res.json(); }, (res: Response) => this.onError(res.json())); + } + clear () { + this.activeModal.dismiss('cancel'); + } + + save () { + this.isSaving = true; + if (this.comment.id !== undefined) { + this.commentService.update(this.comment) + .subscribe((res: Comment) => + this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json())); + } else { + this.commentService.create(this.comment) + .subscribe((res: Comment) => + this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json())); + } + } + + private onSaveSuccess (result: Comment) { + this.eventManager.broadcast({ name: 'commentListModification', content: 'OK'}); + this.isSaving = false; + this.activeModal.dismiss(result); + } + + private onSaveError (error) { + this.isSaving = false; + this.onError(error); + } + + private onError (error) { + this.alertService.error(error.message, null, null); + } + + trackPostById(index: number, item: Post) { + return item.id; + } +} + +@Component({ + selector: 'jhi-comment-popup', + template: '' +}) +export class CommentPopupComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private commentPopupService: CommentPopupService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + if ( params['id'] ) { + this.modalRef = this.commentPopupService + .open(CommentDialogComponent, params['id']); + } else { + this.modalRef = this.commentPopupService + .open(CommentDialogComponent); + } + + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts b/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts new file mode 100644 index 0000000000..a1c0ce11bf --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts @@ -0,0 +1,50 @@ +import { Injectable, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { Comment } from './comment.model'; +import { CommentService } from './comment.service'; +@Injectable() +export class CommentPopupService { + private isOpen = false; + constructor ( + private modalService: NgbModal, + private router: Router, + private commentService: CommentService + + ) {} + + open (component: Component, id?: number | any): NgbModalRef { + if (this.isOpen) { + return; + } + this.isOpen = true; + + if (id) { + this.commentService.find(id).subscribe(comment => { + if (comment.creationDate) { + comment.creationDate = { + year: comment.creationDate.getFullYear(), + month: comment.creationDate.getMonth() + 1, + day: comment.creationDate.getDate() + }; + } + this.commentModalRef(component, comment); + }); + } else { + return this.commentModalRef(component, new Comment()); + } + } + + commentModalRef(component: Component, comment: Comment): NgbModalRef { + let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'}); + modalRef.componentInstance.comment = comment; + modalRef.result.then(result => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }, (reason) => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }); + return modalRef; + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.component.html b/jhipster/src/main/webapp/app/entities/comment/comment.component.html new file mode 100644 index 0000000000..1efd1ce379 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.component.html @@ -0,0 +1,64 @@ +
+

+ Comments + +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + +
ID Text Creation Date Post
{{comment.id}}{{comment.text}}{{comment.creationDate | date:'mediumDate'}} + + +
+ + + +
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment.component.ts new file mode 100644 index 0000000000..4e009d4774 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.component.ts @@ -0,0 +1,110 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Response } from '@angular/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription } from 'rxjs/Rx'; +import { EventManager, ParseLinks, PaginationUtil, JhiLanguageService, AlertService } from 'ng-jhipster'; + +import { Comment } from './comment.model'; +import { CommentService } from './comment.service'; +import { ITEMS_PER_PAGE, Principal } from '../../shared'; +import { PaginationConfig } from '../../blocks/config/uib-pagination.config'; + +@Component({ + selector: 'jhi-comment', + templateUrl: './comment.component.html' +}) +export class CommentComponent implements OnInit, OnDestroy { + + comments: Comment[]; + currentAccount: any; + eventSubscriber: Subscription; + itemsPerPage: number; + links: any; + page: any; + predicate: any; + queryCount: any; + reverse: any; + totalItems: number; + + constructor( + private jhiLanguageService: JhiLanguageService, + private commentService: CommentService, + private alertService: AlertService, + private eventManager: EventManager, + private parseLinks: ParseLinks, + private principal: Principal + ) { + this.comments = []; + this.itemsPerPage = ITEMS_PER_PAGE; + this.page = 0; + this.links = { + last: 0 + }; + this.predicate = 'id'; + this.reverse = true; + this.jhiLanguageService.setLocations(['comment']); + } + + loadAll () { + this.commentService.query({ + page: this.page, + size: this.itemsPerPage, + sort: this.sort() + }).subscribe( + (res: Response) => this.onSuccess(res.json(), res.headers), + (res: Response) => this.onError(res.json()) + ); + } + + reset () { + this.page = 0; + this.comments = []; + this.loadAll(); + } + + loadPage(page) { + this.page = page; + this.loadAll(); + } + ngOnInit() { + this.loadAll(); + this.principal.identity().then((account) => { + this.currentAccount = account; + }); + this.registerChangeInComments(); + } + + ngOnDestroy() { + this.eventManager.destroy(this.eventSubscriber); + } + + trackId (index: number, item: Comment) { + return item.id; + } + + + + registerChangeInComments() { + this.eventSubscriber = this.eventManager.subscribe('commentListModification', (response) => this.reset()); + } + + sort () { + let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')]; + if (this.predicate !== 'id') { + result.push('id'); + } + return result; + } + + private onSuccess(data, headers) { + this.links = this.parseLinks.parse(headers.get('link')); + this.totalItems = headers.get('X-Total-Count'); + for (let i = 0; i < data.length; i++) { + this.comments.push(data[i]); + } + } + + private onError (error) { + this.alertService.error(error.message, null, null); + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.model.ts b/jhipster/src/main/webapp/app/entities/comment/comment.model.ts new file mode 100644 index 0000000000..66df7d0b34 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.model.ts @@ -0,0 +1,10 @@ +import { Post } from '../post'; +export class Comment { + constructor( + public id?: number, + public text?: string, + public creationDate?: any, + public post?: Post, + ) { + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.module.ts b/jhipster/src/main/webapp/app/entities/comment/comment.module.ts new file mode 100644 index 0000000000..1f3167274e --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.module.ts @@ -0,0 +1,50 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { BaeldungSharedModule } from '../../shared'; + +import { + CommentService, + CommentPopupService, + CommentComponent, + CommentDetailComponent, + CommentDialogComponent, + CommentPopupComponent, + CommentDeletePopupComponent, + CommentDeleteDialogComponent, + commentRoute, + commentPopupRoute, +} from './'; + +let ENTITY_STATES = [ + ...commentRoute, + ...commentPopupRoute, +]; + +@NgModule({ + imports: [ + BaeldungSharedModule, + RouterModule.forRoot(ENTITY_STATES, { useHash: true }) + ], + declarations: [ + CommentComponent, + CommentDetailComponent, + CommentDialogComponent, + CommentDeleteDialogComponent, + CommentPopupComponent, + CommentDeletePopupComponent, + ], + entryComponents: [ + CommentComponent, + CommentDialogComponent, + CommentPopupComponent, + CommentDeleteDialogComponent, + CommentDeletePopupComponent, + ], + providers: [ + CommentService, + CommentPopupService, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungCommentModule {} diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.route.ts b/jhipster/src/main/webapp/app/entities/comment/comment.route.ts new file mode 100644 index 0000000000..bb0e5e4141 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.route.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { PaginationUtil } from 'ng-jhipster'; + +import { CommentComponent } from './comment.component'; +import { CommentDetailComponent } from './comment-detail.component'; +import { CommentPopupComponent } from './comment-dialog.component'; +import { CommentDeletePopupComponent } from './comment-delete-dialog.component'; + +import { Principal } from '../../shared'; + + +export const commentRoute: Routes = [ + { + path: 'comment', + component: CommentComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.comment.home.title' + } + }, { + path: 'comment/:id', + component: CommentDetailComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.comment.home.title' + } + } +]; + +export const commentPopupRoute: Routes = [ + { + path: 'comment-new', + component: CommentPopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.comment.home.title' + }, + outlet: 'popup' + }, + { + path: 'comment/:id/edit', + component: CommentPopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.comment.home.title' + }, + outlet: 'popup' + }, + { + path: 'comment/:id/delete', + component: CommentDeletePopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.comment.home.title' + }, + outlet: 'popup' + } +]; diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.service.ts b/jhipster/src/main/webapp/app/entities/comment/comment.service.ts new file mode 100644 index 0000000000..b6121b3b10 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/comment.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, URLSearchParams, BaseRequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import { Comment } from './comment.model'; +import { DateUtils } from 'ng-jhipster'; +@Injectable() +export class CommentService { + + private resourceUrl = 'api/comments'; + + constructor(private http: Http, private dateUtils: DateUtils) { } + + create(comment: Comment): Observable { + let copy: Comment = Object.assign({}, comment); + copy.creationDate = this.dateUtils + .convertLocalDateToServer(comment.creationDate); + return this.http.post(this.resourceUrl, copy).map((res: Response) => { + return res.json(); + }); + } + + update(comment: Comment): Observable { + let copy: Comment = Object.assign({}, comment); + copy.creationDate = this.dateUtils + .convertLocalDateToServer(comment.creationDate); + return this.http.put(this.resourceUrl, copy).map((res: Response) => { + return res.json(); + }); + } + + find(id: number): Observable { + return this.http.get(`${this.resourceUrl}/${id}`).map((res: Response) => { + let jsonResponse = res.json(); + jsonResponse.creationDate = this.dateUtils + .convertLocalDateFromServer(jsonResponse.creationDate); + return jsonResponse; + }); + } + + query(req?: any): Observable { + let options = this.createRequestOption(req); + return this.http.get(this.resourceUrl, options) + .map((res: any) => this.convertResponse(res)) + ; + } + + delete(id: number): Observable { + return this.http.delete(`${this.resourceUrl}/${id}`); + } + + + private convertResponse(res: any): any { + let jsonResponse = res.json(); + for (let i = 0; i < jsonResponse.length; i++) { + jsonResponse[i].creationDate = this.dateUtils + .convertLocalDateFromServer(jsonResponse[i].creationDate); + } + res._body = jsonResponse; + return res; + } + + private createRequestOption(req?: any): BaseRequestOptions { + let options: BaseRequestOptions = new BaseRequestOptions(); + if (req) { + let params: URLSearchParams = new URLSearchParams(); + params.set('page', req.page); + params.set('size', req.size); + if (req.sort) { + params.paramsMap.set('sort', req.sort); + } + params.set('query', req.query); + + options.search = params; + } + return options; + } +} diff --git a/jhipster/src/main/webapp/app/entities/comment/index.ts b/jhipster/src/main/webapp/app/entities/comment/index.ts new file mode 100644 index 0000000000..5e54fb6099 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/comment/index.ts @@ -0,0 +1,8 @@ +export * from './comment.model'; +export * from './comment-popup.service'; +export * from './comment.service'; +export * from './comment-dialog.component'; +export * from './comment-delete-dialog.component'; +export * from './comment-detail.component'; +export * from './comment.component'; +export * from './comment.route'; diff --git a/jhipster/src/main/webapp/app/entities/entity.module.ts b/jhipster/src/main/webapp/app/entities/entity.module.ts new file mode 100644 index 0000000000..ba044e1902 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/entity.module.ts @@ -0,0 +1,18 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; + +import { BaeldungPostModule } from './post/post.module'; +import { BaeldungCommentModule } from './comment/comment.module'; +/* jhipster-needle-add-entity-module-import - JHipster will add entity modules imports here */ + +@NgModule({ + imports: [ + BaeldungPostModule, + BaeldungCommentModule, + /* jhipster-needle-add-entity-module - JHipster will add entity modules here */ + ], + declarations: [], + entryComponents: [], + providers: [], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungEntityModule {} diff --git a/jhipster/src/main/webapp/app/entities/post/index.ts b/jhipster/src/main/webapp/app/entities/post/index.ts new file mode 100644 index 0000000000..9375e11bd8 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/index.ts @@ -0,0 +1,8 @@ +export * from './post.model'; +export * from './post-popup.service'; +export * from './post.service'; +export * from './post-dialog.component'; +export * from './post-delete-dialog.component'; +export * from './post-detail.component'; +export * from './post.component'; +export * from './post.route'; diff --git a/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html new file mode 100644 index 0000000000..901fc1968e --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html @@ -0,0 +1,19 @@ +
+ + + +
diff --git a/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts new file mode 100644 index 0000000000..5e1413fd95 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, JhiLanguageService } from 'ng-jhipster'; + +import { Post } from './post.model'; +import { PostPopupService } from './post-popup.service'; +import { PostService } from './post.service'; + +@Component({ + selector: 'jhi-post-delete-dialog', + templateUrl: './post-delete-dialog.component.html' +}) +export class PostDeleteDialogComponent { + + post: Post; + + constructor( + private jhiLanguageService: JhiLanguageService, + private postService: PostService, + public activeModal: NgbActiveModal, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['post']); + } + + clear () { + this.activeModal.dismiss('cancel'); + } + + confirmDelete (id: number) { + this.postService.delete(id).subscribe(response => { + this.eventManager.broadcast({ + name: 'postListModification', + content: 'Deleted an post' + }); + this.activeModal.dismiss(true); + }); + } +} + +@Component({ + selector: 'jhi-post-delete-popup', + template: '' +}) +export class PostDeletePopupComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private postPopupService: PostPopupService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + this.modalRef = this.postPopupService + .open(PostDeleteDialogComponent, params['id']); + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/entities/post/post-detail.component.html b/jhipster/src/main/webapp/app/entities/post/post-detail.component.html new file mode 100644 index 0000000000..8edcbee375 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-detail.component.html @@ -0,0 +1,37 @@ + +
+

Post {{post.id}}

+
+ +
+
Title
+
+ {{post.title}} +
+
Content
+
+ {{post.content}} +
+
Creation Date
+
+ {{post.creationDate | date:'mediumDate'}} +
+
Creator
+
+ {{post.creator?.login}} +
+
+ + + + +
diff --git a/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts b/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts new file mode 100644 index 0000000000..679592f3b8 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { JhiLanguageService } from 'ng-jhipster'; +import { Post } from './post.model'; +import { PostService } from './post.service'; + +@Component({ + selector: 'jhi-post-detail', + templateUrl: './post-detail.component.html' +}) +export class PostDetailComponent implements OnInit, OnDestroy { + + post: Post; + private subscription: any; + + constructor( + private jhiLanguageService: JhiLanguageService, + private postService: PostService, + private route: ActivatedRoute + ) { + this.jhiLanguageService.setLocations(['post']); + } + + ngOnInit() { + this.subscription = this.route.params.subscribe(params => { + this.load(params['id']); + }); + } + + load (id) { + this.postService.find(id).subscribe(post => { + this.post = post; + }); + } + previousState() { + window.history.back(); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + +} diff --git a/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html new file mode 100644 index 0000000000..2216dcd451 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html @@ -0,0 +1,96 @@ + + +
+ + + + +
diff --git a/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts new file mode 100644 index 0000000000..803b76fc78 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Response } from '@angular/http'; + +import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, AlertService, JhiLanguageService } from 'ng-jhipster'; + +import { Post } from './post.model'; +import { PostPopupService } from './post-popup.service'; +import { PostService } from './post.service'; +import { User, UserService } from '../../shared'; +@Component({ + selector: 'jhi-post-dialog', + templateUrl: './post-dialog.component.html' +}) +export class PostDialogComponent implements OnInit { + + post: Post; + authorities: any[]; + isSaving: boolean; + + users: User[]; + constructor( + public activeModal: NgbActiveModal, + private jhiLanguageService: JhiLanguageService, + private alertService: AlertService, + private postService: PostService, + private userService: UserService, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['post']); + } + + ngOnInit() { + this.isSaving = false; + this.authorities = ['ROLE_USER', 'ROLE_ADMIN']; + this.userService.query().subscribe( + (res: Response) => { this.users = res.json(); }, (res: Response) => this.onError(res.json())); + } + clear () { + this.activeModal.dismiss('cancel'); + } + + save () { + this.isSaving = true; + if (this.post.id !== undefined) { + this.postService.update(this.post) + .subscribe((res: Post) => + this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json())); + } else { + this.postService.create(this.post) + .subscribe((res: Post) => + this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json())); + } + } + + private onSaveSuccess (result: Post) { + this.eventManager.broadcast({ name: 'postListModification', content: 'OK'}); + this.isSaving = false; + this.activeModal.dismiss(result); + } + + private onSaveError (error) { + this.isSaving = false; + this.onError(error); + } + + private onError (error) { + this.alertService.error(error.message, null, null); + } + + trackUserById(index: number, item: User) { + return item.id; + } +} + +@Component({ + selector: 'jhi-post-popup', + template: '' +}) +export class PostPopupComponent implements OnInit, OnDestroy { + + modalRef: NgbModalRef; + routeSub: any; + + constructor ( + private route: ActivatedRoute, + private postPopupService: PostPopupService + ) {} + + ngOnInit() { + this.routeSub = this.route.params.subscribe(params => { + if ( params['id'] ) { + this.modalRef = this.postPopupService + .open(PostDialogComponent, params['id']); + } else { + this.modalRef = this.postPopupService + .open(PostDialogComponent); + } + + }); + } + + ngOnDestroy() { + this.routeSub.unsubscribe(); + } +} diff --git a/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts b/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts new file mode 100644 index 0000000000..42ea731e07 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts @@ -0,0 +1,50 @@ +import { Injectable, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { Post } from './post.model'; +import { PostService } from './post.service'; +@Injectable() +export class PostPopupService { + private isOpen = false; + constructor ( + private modalService: NgbModal, + private router: Router, + private postService: PostService + + ) {} + + open (component: Component, id?: number | any): NgbModalRef { + if (this.isOpen) { + return; + } + this.isOpen = true; + + if (id) { + this.postService.find(id).subscribe(post => { + if (post.creationDate) { + post.creationDate = { + year: post.creationDate.getFullYear(), + month: post.creationDate.getMonth() + 1, + day: post.creationDate.getDate() + }; + } + this.postModalRef(component, post); + }); + } else { + return this.postModalRef(component, new Post()); + } + } + + postModalRef(component: Component, post: Post): NgbModalRef { + let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'}); + modalRef.componentInstance.post = post; + modalRef.result.then(result => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }, (reason) => { + this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true }); + this.isOpen = false; + }); + return modalRef; + } +} diff --git a/jhipster/src/main/webapp/app/entities/post/post.component.html b/jhipster/src/main/webapp/app/entities/post/post.component.html new file mode 100644 index 0000000000..cf17c4e6dc --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.component.html @@ -0,0 +1,64 @@ +
+

+ Posts + +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
ID Title Content Creation Date Creator
{{post.id}}{{post.title}}{{post.content}}{{post.creationDate | date:'mediumDate'}} + {{post.creator?.login}} + +
+ + + +
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/post/post.component.ts b/jhipster/src/main/webapp/app/entities/post/post.component.ts new file mode 100644 index 0000000000..e8401a48c7 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.component.ts @@ -0,0 +1,110 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Response } from '@angular/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription } from 'rxjs/Rx'; +import { EventManager, ParseLinks, PaginationUtil, JhiLanguageService, AlertService } from 'ng-jhipster'; + +import { Post } from './post.model'; +import { PostService } from './post.service'; +import { ITEMS_PER_PAGE, Principal } from '../../shared'; +import { PaginationConfig } from '../../blocks/config/uib-pagination.config'; + +@Component({ + selector: 'jhi-post', + templateUrl: './post.component.html' +}) +export class PostComponent implements OnInit, OnDestroy { + + posts: Post[]; + currentAccount: any; + eventSubscriber: Subscription; + itemsPerPage: number; + links: any; + page: any; + predicate: any; + queryCount: any; + reverse: any; + totalItems: number; + + constructor( + private jhiLanguageService: JhiLanguageService, + private postService: PostService, + private alertService: AlertService, + private eventManager: EventManager, + private parseLinks: ParseLinks, + private principal: Principal + ) { + this.posts = []; + this.itemsPerPage = ITEMS_PER_PAGE; + this.page = 0; + this.links = { + last: 0 + }; + this.predicate = 'id'; + this.reverse = true; + this.jhiLanguageService.setLocations(['post']); + } + + loadAll () { + this.postService.query({ + page: this.page, + size: this.itemsPerPage, + sort: this.sort() + }).subscribe( + (res: Response) => this.onSuccess(res.json(), res.headers), + (res: Response) => this.onError(res.json()) + ); + } + + reset () { + this.page = 0; + this.posts = []; + this.loadAll(); + } + + loadPage(page) { + this.page = page; + this.loadAll(); + } + ngOnInit() { + this.loadAll(); + this.principal.identity().then((account) => { + this.currentAccount = account; + }); + this.registerChangeInPosts(); + } + + ngOnDestroy() { + this.eventManager.destroy(this.eventSubscriber); + } + + trackId (index: number, item: Post) { + return item.id; + } + + + + registerChangeInPosts() { + this.eventSubscriber = this.eventManager.subscribe('postListModification', (response) => this.reset()); + } + + sort () { + let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')]; + if (this.predicate !== 'id') { + result.push('id'); + } + return result; + } + + private onSuccess(data, headers) { + this.links = this.parseLinks.parse(headers.get('link')); + this.totalItems = headers.get('X-Total-Count'); + for (let i = 0; i < data.length; i++) { + this.posts.push(data[i]); + } + } + + private onError (error) { + this.alertService.error(error.message, null, null); + } +} diff --git a/jhipster/src/main/webapp/app/entities/post/post.model.ts b/jhipster/src/main/webapp/app/entities/post/post.model.ts new file mode 100644 index 0000000000..454f7e75f8 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.model.ts @@ -0,0 +1,11 @@ +import { User } from '../../shared'; +export class Post { + constructor( + public id?: number, + public title?: string, + public content?: string, + public creationDate?: any, + public creator?: User, + ) { + } +} diff --git a/jhipster/src/main/webapp/app/entities/post/post.module.ts b/jhipster/src/main/webapp/app/entities/post/post.module.ts new file mode 100644 index 0000000000..53ba982c02 --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.module.ts @@ -0,0 +1,52 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { BaeldungSharedModule } from '../../shared'; +import { BaeldungAdminModule } from '../../admin/admin.module'; + +import { + PostService, + PostPopupService, + PostComponent, + PostDetailComponent, + PostDialogComponent, + PostPopupComponent, + PostDeletePopupComponent, + PostDeleteDialogComponent, + postRoute, + postPopupRoute, +} from './'; + +let ENTITY_STATES = [ + ...postRoute, + ...postPopupRoute, +]; + +@NgModule({ + imports: [ + BaeldungSharedModule, + BaeldungAdminModule, + RouterModule.forRoot(ENTITY_STATES, { useHash: true }) + ], + declarations: [ + PostComponent, + PostDetailComponent, + PostDialogComponent, + PostDeleteDialogComponent, + PostPopupComponent, + PostDeletePopupComponent, + ], + entryComponents: [ + PostComponent, + PostDialogComponent, + PostPopupComponent, + PostDeleteDialogComponent, + PostDeletePopupComponent, + ], + providers: [ + PostService, + PostPopupService, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungPostModule {} diff --git a/jhipster/src/main/webapp/app/entities/post/post.route.ts b/jhipster/src/main/webapp/app/entities/post/post.route.ts new file mode 100644 index 0000000000..27b23bdc0d --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.route.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { PaginationUtil } from 'ng-jhipster'; + +import { PostComponent } from './post.component'; +import { PostDetailComponent } from './post-detail.component'; +import { PostPopupComponent } from './post-dialog.component'; +import { PostDeletePopupComponent } from './post-delete-dialog.component'; + +import { Principal } from '../../shared'; + + +export const postRoute: Routes = [ + { + path: 'post', + component: PostComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.post.home.title' + } + }, { + path: 'post/:id', + component: PostDetailComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.post.home.title' + } + } +]; + +export const postPopupRoute: Routes = [ + { + path: 'post-new', + component: PostPopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.post.home.title' + }, + outlet: 'popup' + }, + { + path: 'post/:id/edit', + component: PostPopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.post.home.title' + }, + outlet: 'popup' + }, + { + path: 'post/:id/delete', + component: PostDeletePopupComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'baeldungApp.post.home.title' + }, + outlet: 'popup' + } +]; diff --git a/jhipster/src/main/webapp/app/entities/post/post.service.ts b/jhipster/src/main/webapp/app/entities/post/post.service.ts new file mode 100644 index 0000000000..7f7ee969fe --- /dev/null +++ b/jhipster/src/main/webapp/app/entities/post/post.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, URLSearchParams, BaseRequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import { Post } from './post.model'; +import { DateUtils } from 'ng-jhipster'; +@Injectable() +export class PostService { + + private resourceUrl = 'api/posts'; + + constructor(private http: Http, private dateUtils: DateUtils) { } + + create(post: Post): Observable { + let copy: Post = Object.assign({}, post); + copy.creationDate = this.dateUtils + .convertLocalDateToServer(post.creationDate); + return this.http.post(this.resourceUrl, copy).map((res: Response) => { + return res.json(); + }); + } + + update(post: Post): Observable { + let copy: Post = Object.assign({}, post); + copy.creationDate = this.dateUtils + .convertLocalDateToServer(post.creationDate); + return this.http.put(this.resourceUrl, copy).map((res: Response) => { + return res.json(); + }); + } + + find(id: number): Observable { + return this.http.get(`${this.resourceUrl}/${id}`).map((res: Response) => { + let jsonResponse = res.json(); + jsonResponse.creationDate = this.dateUtils + .convertLocalDateFromServer(jsonResponse.creationDate); + return jsonResponse; + }); + } + + query(req?: any): Observable { + let options = this.createRequestOption(req); + return this.http.get(this.resourceUrl, options) + .map((res: any) => this.convertResponse(res)) + ; + } + + delete(id: number): Observable { + return this.http.delete(`${this.resourceUrl}/${id}`); + } + + + private convertResponse(res: any): any { + let jsonResponse = res.json(); + for (let i = 0; i < jsonResponse.length; i++) { + jsonResponse[i].creationDate = this.dateUtils + .convertLocalDateFromServer(jsonResponse[i].creationDate); + } + res._body = jsonResponse; + return res; + } + + private createRequestOption(req?: any): BaseRequestOptions { + let options: BaseRequestOptions = new BaseRequestOptions(); + if (req) { + let params: URLSearchParams = new URLSearchParams(); + params.set('page', req.page); + params.set('size', req.size); + if (req.sort) { + params.paramsMap.set('sort', req.sort); + } + params.set('query', req.query); + + options.search = params; + } + return options; + } +} diff --git a/jhipster/src/main/webapp/app/home/home.component.html b/jhipster/src/main/webapp/app/home/home.component.html new file mode 100644 index 0000000000..4c0197228c --- /dev/null +++ b/jhipster/src/main/webapp/app/home/home.component.html @@ -0,0 +1,42 @@ +
+
+ +
+
+

Welcome, Java Hipster!

+

This is your homepage

+ +
+
+ You are logged in as user "{{account.login}}". +
+ +
+ If you want to + sign in, you can try the default accounts:
- Administrator (login="admin" and password="admin")
- User (login="user" and password="user").
+
+ +
+ You don't have an account yet? + Register a new account +
+
+ +

+ If you have any question on JHipster: +

+ + + +

+ If you like JHipster, don't forget to give us a star on Github! +

+
+
diff --git a/jhipster/src/main/webapp/app/home/home.component.ts b/jhipster/src/main/webapp/app/home/home.component.ts new file mode 100644 index 0000000000..b16838377e --- /dev/null +++ b/jhipster/src/main/webapp/app/home/home.component.ts @@ -0,0 +1,50 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { EventManager, JhiLanguageService } from 'ng-jhipster'; + +import { Account, LoginModalService, Principal } from '../shared'; + +@Component({ + selector: 'jhi-home', + templateUrl: './home.component.html', + styleUrls: [ + 'home.scss' + ] + +}) +export class HomeComponent implements OnInit { + account: Account; + modalRef: NgbModalRef; + + constructor( + private jhiLanguageService: JhiLanguageService, + private principal: Principal, + private loginModalService: LoginModalService, + private eventManager: EventManager + ) { + this.jhiLanguageService.setLocations(['home']); + } + + ngOnInit() { + this.principal.identity().then((account) => { + this.account = account; + }); + this.registerAuthenticationSuccess(); + } + + registerAuthenticationSuccess() { + this.eventManager.subscribe('authenticationSuccess', (message) => { + this.principal.identity().then((account) => { + this.account = account; + }); + }); + } + + isAuthenticated() { + return this.principal.isAuthenticated(); + } + + login() { + this.modalRef = this.loginModalService.open(); + } +} diff --git a/jhipster/src/main/webapp/app/home/home.module.ts b/jhipster/src/main/webapp/app/home/home.module.ts new file mode 100644 index 0000000000..172c605249 --- /dev/null +++ b/jhipster/src/main/webapp/app/home/home.module.ts @@ -0,0 +1,23 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { BaeldungSharedModule } from '../shared'; + +import { HOME_ROUTE, HomeComponent } from './'; + + +@NgModule({ + imports: [ + BaeldungSharedModule, + RouterModule.forRoot([ HOME_ROUTE ], { useHash: true }) + ], + declarations: [ + HomeComponent, + ], + entryComponents: [ + ], + providers: [ + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BaeldungHomeModule {} diff --git a/jhipster/src/main/webapp/app/home/home.route.ts b/jhipster/src/main/webapp/app/home/home.route.ts new file mode 100644 index 0000000000..cba14bf0c6 --- /dev/null +++ b/jhipster/src/main/webapp/app/home/home.route.ts @@ -0,0 +1,14 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from '../shared'; +import { HomeComponent } from './'; + +export const HOME_ROUTE: Route = { + path: '', + component: HomeComponent, + data: { + authorities: [], + pageTitle: 'home.title' + }, + canActivate: [UserRouteAccessService] +}; diff --git a/jhipster/src/main/webapp/app/home/home.scss b/jhipster/src/main/webapp/app/home/home.scss new file mode 100644 index 0000000000..578787b1c9 --- /dev/null +++ b/jhipster/src/main/webapp/app/home/home.scss @@ -0,0 +1,26 @@ + +/* ========================================================================== +Main page styles +========================================================================== */ + +.hipster { + display: inline-block; + width: 347px; + height: 497px; + background: url("../../content/images/hipster.png") no-repeat center top; + background-size: contain; +} + +/* wait autoprefixer update to allow simple generation of high pixel density media query */ +@media +only screen and (-webkit-min-device-pixel-ratio: 2), +only screen and ( min--moz-device-pixel-ratio: 2), +only screen and ( -o-min-device-pixel-ratio: 2/1), +only screen and ( min-device-pixel-ratio: 2), +only screen and ( min-resolution: 192dpi), +only screen and ( min-resolution: 2dppx) { + .hipster { + background: url("../../content/images/hipster2x.png") no-repeat center top; + background-size: contain; + } +} diff --git a/jhipster/src/main/webapp/app/home/index.ts b/jhipster/src/main/webapp/app/home/index.ts new file mode 100644 index 0000000000..d76285b277 --- /dev/null +++ b/jhipster/src/main/webapp/app/home/index.ts @@ -0,0 +1,3 @@ +export * from './home.component'; +export * from './home.route'; +export * from './home.module'; diff --git a/jhipster/src/main/webapp/app/layouts/error/error.component.html b/jhipster/src/main/webapp/app/layouts/error/error.component.html new file mode 100644 index 0000000000..92fe2f1512 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/error/error.component.html @@ -0,0 +1,17 @@ +
+
+
+ +
+
+

Error Page!

+ +
+
{{errorMessage}} +
+
+
You are not authorized to access the page. +
+
+
+
diff --git a/jhipster/src/main/webapp/app/layouts/error/error.component.ts b/jhipster/src/main/webapp/app/layouts/error/error.component.ts new file mode 100644 index 0000000000..983809f541 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/error/error.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +@Component({ + selector: 'jhi-error', + templateUrl: './error.component.html' +}) +export class ErrorComponent implements OnInit { + errorMessage: string; + error403: boolean; + + constructor( + private jhiLanguageService: JhiLanguageService + ) { + this.jhiLanguageService.setLocations(['error']); + } + + ngOnInit() { + } +} diff --git a/jhipster/src/main/webapp/app/layouts/error/error.route.ts b/jhipster/src/main/webapp/app/layouts/error/error.route.ts new file mode 100644 index 0000000000..5f6085076c --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/error/error.route.ts @@ -0,0 +1,25 @@ +import { Routes } from '@angular/router'; + +import { UserRouteAccessService } from '../../shared'; +import { ErrorComponent } from './error.component'; + +export const errorRoute: Routes = [ + { + path: 'error', + component: ErrorComponent, + data: { + authorities: [], + pageTitle: 'error.title' + }, + canActivate: [UserRouteAccessService] + }, + { + path: 'accessdenied', + component: ErrorComponent, + data: { + authorities: [], + pageTitle: 'error.title' + }, + canActivate: [UserRouteAccessService] + } +]; diff --git a/jhipster/src/main/webapp/app/layouts/footer/footer.component.html b/jhipster/src/main/webapp/app/layouts/footer/footer.component.html new file mode 100644 index 0000000000..4e4fda05bf --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/footer/footer.component.html @@ -0,0 +1,4 @@ + + diff --git a/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts b/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts new file mode 100644 index 0000000000..37da8bca75 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'jhi-footer', + templateUrl: './footer.component.html' +}) +export class FooterComponent {} diff --git a/jhipster/src/main/webapp/app/layouts/index.ts b/jhipster/src/main/webapp/app/layouts/index.ts new file mode 100644 index 0000000000..f25305a0ac --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/index.ts @@ -0,0 +1,10 @@ +export * from './error/error.component'; +export * from './error/error.route'; +export * from './main/main.component'; +export * from './footer/footer.component'; +export * from './navbar/navbar.component'; +export * from './navbar/active-menu.directive'; +export * from './profiles/page-ribbon.component'; +export * from './profiles/profile.service'; +export * from './profiles/profile-info.model'; +export * from './layout-routing.module'; diff --git a/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts b/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts new file mode 100644 index 0000000000..8edbdff26c --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes, Resolve } from '@angular/router'; + +import { navbarRoute } from '../app.route'; +import { errorRoute } from './'; + +let LAYOUT_ROUTES = [ + navbarRoute, + ...errorRoute +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(LAYOUT_ROUTES, { useHash: true }) + ], + exports: [ + RouterModule + ] +}) +export class LayoutRoutingModule {} diff --git a/jhipster/src/main/webapp/app/layouts/main/main.component.html b/jhipster/src/main/webapp/app/layouts/main/main.component.html new file mode 100644 index 0000000000..5bcd12ab0b --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/main/main.component.html @@ -0,0 +1,11 @@ + +
+ +
+
+
+ + +
+ +
diff --git a/jhipster/src/main/webapp/app/layouts/main/main.component.ts b/jhipster/src/main/webapp/app/layouts/main/main.component.ts new file mode 100644 index 0000000000..c5982f60ca --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/main/main.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRouteSnapshot, NavigationEnd, RoutesRecognized } from '@angular/router'; + +import { JhiLanguageHelper, StateStorageService } from '../../shared'; + +@Component({ + selector: 'jhi-main', + templateUrl: './main.component.html' +}) +export class JhiMainComponent implements OnInit { + + constructor( + private jhiLanguageHelper: JhiLanguageHelper, + private router: Router, + private $storageService: StateStorageService, + ) {} + + private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) { + let title: string = (routeSnapshot.data && routeSnapshot.data['pageTitle']) ? routeSnapshot.data['pageTitle'] : 'baeldungApp'; + if (routeSnapshot.firstChild) { + title = this.getPageTitle(routeSnapshot.firstChild) || title; + } + return title; + } + + ngOnInit() { + this.router.events.subscribe((event) => { + if (event instanceof NavigationEnd) { + this.jhiLanguageHelper.updateTitle(this.getPageTitle(this.router.routerState.snapshot.root)); + } + if (event instanceof RoutesRecognized) { + let params = {}; + let destinationData = {}; + let destinationName = ''; + let destinationEvent = event.state.root.firstChild.children[0]; + if (destinationEvent !== undefined) { + params = destinationEvent.params; + destinationData = destinationEvent.data; + destinationName = destinationEvent.url[0].path; + } + let from = {name: this.router.url.slice(1)}; + let destination = {name: destinationName, data: destinationData}; + this.$storageService.storeDestinationState(destination, params, from); + } + }); + } +} diff --git a/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts b/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts new file mode 100644 index 0000000000..87275c9d57 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts @@ -0,0 +1,26 @@ +import { Directive, OnInit, ElementRef, Renderer, Input} from '@angular/core'; +import { TranslateService, LangChangeEvent } from 'ng2-translate'; + +@Directive({ + selector: '[jhiActiveMenu]' +}) +export class ActiveMenuDirective implements OnInit { + @Input() jhiActiveMenu: string; + + constructor(private el: ElementRef, private renderer: Renderer, private translateService: TranslateService) {} + + ngOnInit() { + this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { + this.updateActiveFlag(event.lang); + }); + this.updateActiveFlag(this.translateService.currentLang); + } + + updateActiveFlag(selectedLanguage) { + if (this.jhiActiveMenu === selectedLanguage) { + this.renderer.setElementClass(this.el.nativeElement, 'active', true); + } else { + this.renderer.setElementClass(this.el.nativeElement, 'active', false); + } + } +} diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html new file mode 100644 index 0000000000..07b7abb25c --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html @@ -0,0 +1,168 @@ + diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts new file mode 100644 index 0000000000..8f58bfebd9 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts @@ -0,0 +1,81 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { ProfileService } from '../profiles/profile.service'; // FIXME barrel doesnt work here +import { JhiLanguageHelper, Principal, LoginModalService, LoginService } from '../../shared'; + +import { VERSION, DEBUG_INFO_ENABLED } from '../../app.constants'; + +@Component({ + selector: 'jhi-navbar', + templateUrl: './navbar.component.html', + styleUrls: [ + 'navbar.scss' + ] +}) +export class NavbarComponent implements OnInit { + + inProduction: boolean; + isNavbarCollapsed: boolean; + languages: any[]; + swaggerEnabled: boolean; + modalRef: NgbModalRef; + version: string; + + constructor( + private loginService: LoginService, + private languageHelper: JhiLanguageHelper, + private languageService: JhiLanguageService, + private principal: Principal, + private loginModalService: LoginModalService, + private profileService: ProfileService, + private router: Router + ) { + this.version = DEBUG_INFO_ENABLED ? 'v' + VERSION : ''; + this.isNavbarCollapsed = true; + this.languageService.addLocation('home'); + } + + ngOnInit() { + this.languageHelper.getAll().then((languages) => { + this.languages = languages; + }); + + this.profileService.getProfileInfo().subscribe(profileInfo => { + this.inProduction = profileInfo.inProduction; + this.swaggerEnabled = profileInfo.swaggerEnabled; + }); + } + + changeLanguage(languageKey: string) { + this.languageService.changeLanguage(languageKey); + } + + collapseNavbar() { + this.isNavbarCollapsed = true; + } + + isAuthenticated() { + return this.principal.isAuthenticated(); + } + + login() { + this.modalRef = this.loginModalService.open(); + } + + logout() { + this.collapseNavbar(); + this.loginService.logout(); + this.router.navigate(['']); + } + + toggleNavbar() { + this.isNavbarCollapsed = !this.isNavbarCollapsed; + } + + getImageUrl() { + return this.isAuthenticated() ? this.principal.getImageUrl() : null; + } +} diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss b/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss new file mode 100644 index 0000000000..d48d609543 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss @@ -0,0 +1,70 @@ + +/* ========================================================================== +Navbar +========================================================================== */ +.navbar-version { + font-size: 10px; + color: #ccc +} + +.jh-navbar { + background-color: #353d47; + padding: .2em 1em; + .profile-image { + margin: -10px 0px; + height: 40px; + width: 40px; + border-radius: 50%; + } + .dropdown-item.active, .dropdown-item.active:focus, .dropdown-item.active:hover { + background-color: #353d47; + } + .dropdown-toggle::after { + margin-left: 0.15em; + } + ul.navbar-nav { + padding: 0.5em; + .nav-item { + margin-left: 1.5rem; + } + } + a.nav-link { + font-weight: 400; + } + .jh-navbar-toggler { + color: #ccc; + font-size: 1.5em; + padding: 10px; + &:hover { + color: #fff; + } + } +} + +@media screen and (max-width: 992px) { + .jh-logo-container { + width: 100%; + } +} + +.navbar-title { + display: inline-block; + vertical-align: middle; +} + +/* ========================================================================== +Logo styles +========================================================================== */ +.navbar-brand { + &.logo { + padding: 5px 15px; + .logo-img { + height: 45px; + width: 70px; + display: inline-block; + vertical-align: middle; + background: url("../../../content/images/logo-jhipster.png") no-repeat center center; + background-size: contain; + } + } +} diff --git a/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts new file mode 100644 index 0000000000..f7ba492f66 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ProfileService } from './profile.service'; +import { ProfileInfo } from './profile-info.model'; + +@Component({ + selector: 'jhi-page-ribbon', + template: ``, + styleUrls: [ + 'page-ribbon.scss' + ] +}) +export class PageRibbonComponent implements OnInit { + + profileInfo: ProfileInfo; + ribbonEnv: string; + + constructor(private profileService: ProfileService) {} + + ngOnInit() { + this.profileService.getProfileInfo().subscribe(profileInfo => { + this.profileInfo = profileInfo; + this.ribbonEnv = profileInfo.ribbonEnv; + }); + } +} diff --git a/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss new file mode 100644 index 0000000000..5efd11c03e --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss @@ -0,0 +1,32 @@ + +/* ========================================================================== +Developement Ribbon +========================================================================== */ +.ribbon { + background-color: rgba(170, 0, 0, 0.5); + left: -3.5em; + moz-transform: rotate(-45deg); + ms-transform: rotate(-45deg); + o-transform: rotate(-45deg); + webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + overflow: hidden; + position: absolute; + top: 40px; + white-space: nowrap; + width: 15em; + z-index: 9999; + pointer-events: none; + opacity: 0.75; + a { + color: #fff; + display: block; + font-weight: 400; + margin: 1px 0; + padding: 10px 50px; + text-align: center; + text-decoration: none; + text-shadow: 0 0 5px #444; + pointer-events: none; + } +} diff --git a/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts b/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts new file mode 100644 index 0000000000..f1adc52c7b --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts @@ -0,0 +1,6 @@ +export class ProfileInfo { + activeProfiles: string[]; + ribbonEnv: string; + inProduction: boolean; + swaggerEnabled: boolean; +} diff --git a/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts b/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts new file mode 100644 index 0000000000..347b84d8e5 --- /dev/null +++ b/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import { ProfileInfo } from './profile-info.model'; + +@Injectable() +export class ProfileService { + + private profileInfoUrl = 'api/profile-info'; + + constructor(private http: Http) { } + + getProfileInfo(): Observable { + return this.http.get(this.profileInfoUrl) + .map((res: Response) => { + let data = res.json(); + let pi = new ProfileInfo(); + pi.activeProfiles = data.activeProfiles; + pi.ribbonEnv = data.ribbonEnv; + pi.inProduction = data.activeProfiles.indexOf('prod') !== -1; + pi.swaggerEnabled = data.activeProfiles.indexOf('swagger') !== -1; + return pi; + }); + } +} diff --git a/jhipster/src/main/webapp/app/polyfills.ts b/jhipster/src/main/webapp/app/polyfills.ts new file mode 100644 index 0000000000..0771ba0b72 --- /dev/null +++ b/jhipster/src/main/webapp/app/polyfills.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +import 'reflect-metadata/Reflect'; +import 'zone.js/dist/zone'; diff --git a/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts b/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts new file mode 100644 index 0000000000..e9e3e2b7a0 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts @@ -0,0 +1,101 @@ +import { Component, OnDestroy } from '@angular/core'; +import { TranslateService } from 'ng2-translate'; +import { EventManager, AlertService } from 'ng-jhipster'; +import { Subscription } from 'rxjs/Rx'; + +@Component({ + selector: 'jhi-alert-error', + template: ` + ` +}) +export class JhiAlertErrorComponent implements OnDestroy { + + alerts: any[]; + cleanHttpErrorListener: Subscription; + + constructor(private alertService: AlertService, private eventManager: EventManager, private translateService: TranslateService) { + this.alerts = []; + + this.cleanHttpErrorListener = eventManager.subscribe('baeldungApp.httpError', (response) => { + let i; + let httpResponse = response.content; + switch (httpResponse.status) { + // connection refused, server not reachable + case 0: + this.addErrorAlert('Server not reachable', 'error.server.not.reachable'); + break; + + case 400: + let arr = Array.from(httpResponse.headers._headers); + let headers = []; + for (i = 0; i < arr.length; i++) { + if (arr[i][0].endsWith('app-error') || arr[i][0].endsWith('app-params')) { + headers.push(arr[i][0]); + } + } + headers.sort(); + let errorHeader = httpResponse.headers.get(headers[0]); + let entityKey = httpResponse.headers.get(headers[1]); + if (errorHeader) { + let entityName = translateService.instant('global.menu.entities.' + entityKey); + this.addErrorAlert(errorHeader, errorHeader, {entityName: entityName}); + } else if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().fieldErrors) { + let fieldErrors = httpResponse.json().fieldErrors; + for (i = 0; i < fieldErrors.length; i++) { + let fieldError = fieldErrors[i]; + // convert 'something[14].other[4].id' to 'something[].other[].id' so translations can be written to it + let convertedField = fieldError.field.replace(/\[\d*\]/g, '[]'); + let fieldName = translateService.instant('baeldungApp.' + + fieldError.objectName + '.' + convertedField); + this.addErrorAlert( + 'Field ' + fieldName + ' cannot be empty', 'error.' + fieldError.message, {fieldName: fieldName}); + } + } else if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().message) { + this.addErrorAlert(httpResponse.json().message, httpResponse.json().message, httpResponse.json()); + } else { + this.addErrorAlert(httpResponse.text()); + } + break; + + case 404: + this.addErrorAlert('Not found', 'error.url.not.found'); + break; + + default: + if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().message) { + this.addErrorAlert(httpResponse.json().message); + } else { + this.addErrorAlert(JSON.stringify(httpResponse)); // Fixme find a way to parse httpResponse + } + } + }); + } + + ngOnDestroy() { + if (this.cleanHttpErrorListener !== undefined && this.cleanHttpErrorListener !== null) { + this.eventManager.destroy(this.cleanHttpErrorListener); + this.alerts = []; + } + } + + addErrorAlert (message, key?, data?) { + key = key && key !== null ? key : message; + this.alerts.push( + this.alertService.addAlert( + { + type: 'danger', + msg: key, + params: data, + timeout: 5000, + toast: this.alertService.isToast(), + scoped: true + }, + this.alerts + ) + ); + } +} diff --git a/jhipster/src/main/webapp/app/shared/alert/alert.component.ts b/jhipster/src/main/webapp/app/shared/alert/alert.component.ts new file mode 100644 index 0000000000..b8aa418ac5 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/alert/alert.component.ts @@ -0,0 +1,26 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { AlertService } from 'ng-jhipster'; + +@Component({ + selector: 'jhi-alert', + template: ` + ` +}) +export class JhiAlertComponent implements OnInit, OnDestroy { + alerts: any[]; + + constructor(private alertService: AlertService) { } + + ngOnInit() { + this.alerts = this.alertService.get(); + } + + ngOnDestroy() { + this.alerts = []; + } + +} diff --git a/jhipster/src/main/webapp/app/shared/auth/account.service.ts b/jhipster/src/main/webapp/app/shared/auth/account.service.ts new file mode 100644 index 0000000000..6d21943d49 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/account.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +@Injectable() +export class AccountService { + constructor(private http: Http) { } + + get(): Observable { + return this.http.get('api/account').map((res: Response) => res.json()); + } + + save(account: any): Observable { + return this.http.post('api/account', account); + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts b/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts new file mode 100644 index 0000000000..9be418d175 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, Headers, URLSearchParams } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; +import { LocalStorageService, SessionStorageService } from 'ng2-webstorage'; + +@Injectable() +export class AuthServerProvider { + constructor( + private http: Http, + private $localStorage: LocalStorageService, + private $sessionStorage: SessionStorageService + ) {} + + getToken () { + return this.$localStorage.retrieve('authenticationToken') || this.$sessionStorage.retrieve('authenticationToken'); + } + + login (credentials): Observable { + + let data = { + username: credentials.username, + password: credentials.password, + rememberMe: credentials.rememberMe + }; + return this.http.post('api/authenticate', data).map(authenticateSuccess.bind(this)); + + function authenticateSuccess (resp) { + let bearerToken = resp.headers.get('Authorization'); + if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') { + let jwt = bearerToken.slice(7, bearerToken.length); + this.storeAuthenticationToken(jwt, credentials.rememberMe); + return jwt; + } + } + } + + loginWithToken(jwt, rememberMe) { + if (jwt) { + this.storeAuthenticationToken(jwt, rememberMe); + return Promise.resolve(jwt); + } else { + return Promise.reject('auth-jwt-service Promise reject'); // Put appropriate error message here + } + } + + storeAuthenticationToken(jwt, rememberMe) { + if (rememberMe) { + this.$localStorage.store('authenticationToken', jwt); + } else { + this.$sessionStorage.store('authenticationToken', jwt); + } + } + + logout (): Observable { + return new Observable(observer => { + this.$localStorage.clear('authenticationToken'); + this.$sessionStorage.clear('authenticationToken'); + observer.complete(); + }); + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/auth.service.ts b/jhipster/src/main/webapp/app/shared/auth/auth.service.ts new file mode 100644 index 0000000000..9e21fb6737 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/auth.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +import { LoginModalService } from '../login/login-modal.service'; +import { Principal } from './principal.service'; +import { StateStorageService } from './state-storage.service'; + +@Injectable() +export class AuthService { + + constructor( + private principal: Principal, + private stateStorageService: StateStorageService, + private loginModalService: LoginModalService, + private router: Router + ) {} + + authorize (force) { + let authReturn = this.principal.identity(force).then(authThen.bind(this)); + + return authReturn; + + function authThen () { + let isAuthenticated = this.principal.isAuthenticated(); + let toStateInfo = this.stateStorageService.getDestinationState().destination; + + // an authenticated user can't access to login and register pages + if (isAuthenticated && (toStateInfo.name === 'register' || toStateInfo.name === 'social-auth')) { + this.router.navigate(['']); + return false; + } + + // recover and clear previousState after external login redirect (e.g. oauth2) + let fromStateInfo = this.stateStorageService.getDestinationState().from; + let previousState = this.stateStorageService.getPreviousState(); + if (isAuthenticated && !fromStateInfo.name && previousState) { + this.stateStorageService.resetPreviousState(); + this.router.navigate([previousState.name], { queryParams: previousState.params }); + return false; + } + + if (toStateInfo.data.authorities && toStateInfo.data.authorities.length > 0) { + return this.principal.hasAnyAuthority(toStateInfo.data.authorities).then(hasAnyAuthority => { + if (!hasAnyAuthority) { + if (isAuthenticated) { + // user is signed in but not authorized for desired state + this.router.navigate(['accessdenied']); + } else { + // user is not authenticated. Show the state they wanted before you + // send them to the login service, so you can return them when you're done + let toStateParamsInfo = this.stateStorageService.getDestinationState().params; + this.stateStorageService.storePreviousState(toStateInfo.name, toStateParamsInfo); + // now, send them to the signin state so they can log in + this.router.navigate(['accessdenied']).then(() => { + this.loginModalService.open(); + }); + } + } + return hasAnyAuthority; + }); + } + return true; + } + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts b/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts new file mode 100644 index 0000000000..6f1064112a --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { CookieService } from 'angular2-cookie/core'; + +@Injectable() +export class CSRFService { + + constructor(private cookieService: CookieService) {} + + getCSRF(name?: string) { + name = `${name ? name : 'XSRF-TOKEN'}`; + return this.cookieService.get(name); + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts b/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts new file mode 100644 index 0000000000..858c105fd5 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts @@ -0,0 +1,42 @@ +import { Directive, ElementRef, Input, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Principal } from './principal.service'; + +/** + * @whatItDoes Conditionally includes an HTML element if current user has any + * of the authorities passed as the `expression`. + * + * @howToUse + * ``` + * ... + * + * ... + * ``` + */ +@Directive({ + selector: '[jhiHasAnyAuthority]' +}) +export class HasAnyAuthorityDirective { + + private authorities: string[]; + + constructor(private principal: Principal, private templateRef: TemplateRef, private viewContainerRef: ViewContainerRef) { + } + + @Input() + set jhiHasAnyAuthority(value: string|string[]) { + this.authorities = typeof value === 'string' ? [ value ] : value; + this.updateView(); + // Get notified each time authentication state changes. + this.principal.getAuthenticationState().subscribe(identity => this.updateView()); + } + + private updateView(): void { + this.principal.hasAnyAuthority(this.authorities).then(result => { + if (result) { + this.viewContainerRef.createEmbeddedView(this.templateRef); + } else { + this.viewContainerRef.clear(); + } + }); + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/principal.service.ts b/jhipster/src/main/webapp/app/shared/auth/principal.service.ts new file mode 100644 index 0000000000..2c7f05dd56 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/principal.service.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { AccountService } from './account.service'; + +@Injectable() +export class Principal { + private userIdentity: any; + private authenticated = false; + private authenticationState = new Subject(); + + constructor( + private account: AccountService + ) {} + + authenticate (identity) { + this.userIdentity = identity; + this.authenticated = identity !== null; + this.authenticationState.next(this.userIdentity); + } + + hasAnyAuthority (authorities: string[]): Promise { + if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) { + return Promise.resolve(false); + } + + for (let i = 0; i < authorities.length; i++) { + if (this.userIdentity.authorities.indexOf(authorities[i]) !== -1) { + return Promise.resolve(true); + } + } + + return Promise.resolve(false); + } + + hasAuthority (authority: string): Promise { + if (!this.authenticated) { + return Promise.resolve(false); + } + + return this.identity().then(id => { + return Promise.resolve(id.authorities && id.authorities.indexOf(authority) !== -1); + }, () => { + return Promise.resolve(false); + }); + } + + identity (force?: boolean): Promise { + if (force === true) { + this.userIdentity = undefined; + } + + // check and see if we have retrieved the userIdentity data from the server. + // if we have, reuse it by immediately resolving + if (this.userIdentity) { + return Promise.resolve(this.userIdentity); + } + + // retrieve the userIdentity data from the server, update the identity object, and then resolve. + return this.account.get().toPromise().then(account => { + if (account) { + this.userIdentity = account; + this.authenticated = true; + } else { + this.userIdentity = null; + this.authenticated = false; + } + this.authenticationState.next(this.userIdentity); + return this.userIdentity; + }).catch(err => { + this.userIdentity = null; + this.authenticated = false; + this.authenticationState.next(this.userIdentity); + return null; + }); + } + + isAuthenticated (): boolean { + return this.authenticated; + } + + isIdentityResolved (): boolean { + return this.userIdentity !== undefined; + } + + getAuthenticationState(): Observable { + return this.authenticationState.asObservable(); + } + + getImageUrl(): String { + return this.isIdentityResolved () ? this.userIdentity.imageUrl : null; + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts b/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts new file mode 100644 index 0000000000..1fe364b06d --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { SessionStorageService } from 'ng2-webstorage'; + +@Injectable() +export class StateStorageService { + constructor( + private $sessionStorage: SessionStorageService + ) {} + + getPreviousState() { + return this.$sessionStorage.retrieve('previousState'); + } + + resetPreviousState() { + this.$sessionStorage.clear('previousState'); + } + + storePreviousState(previousStateName, previousStateParams) { + let previousState = { 'name': previousStateName, 'params': previousStateParams }; + this.$sessionStorage.store('previousState', previousState); + } + + getDestinationState() { + return this.$sessionStorage.retrieve('destinationState'); + } + + storeDestinationState(destinationState, destinationStateParams, fromState) { + let destinationInfo = { + 'destination': { + 'name': destinationState.name, + 'data': destinationState.data, + }, + 'params': destinationStateParams, + 'from': { + 'name': fromState.name, + } + }; + this.$sessionStorage.store('destinationState', destinationInfo); + } +} diff --git a/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts b/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts new file mode 100644 index 0000000000..95eb236672 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router'; + +import { AuthService } from '../'; + +@Injectable() +export class UserRouteAccessService implements CanActivate { + + constructor(private router: Router, private auth: AuthService) { + } + + canActivate(route: ActivatedRouteSnapshot): boolean | Promise { + return this.auth.authorize(false).then( canActivate => canActivate); + } +} diff --git a/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts b/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts new file mode 100644 index 0000000000..a148d4579b --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts @@ -0,0 +1 @@ +export const ITEMS_PER_PAGE = 20; diff --git a/jhipster/src/main/webapp/app/shared/index.ts b/jhipster/src/main/webapp/app/shared/index.ts new file mode 100644 index 0000000000..d2687cf884 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/index.ts @@ -0,0 +1,23 @@ +export * from './alert/alert.component'; +export * from './alert/alert-error.component'; +export * from './auth/csrf.service'; +export * from './auth/state-storage.service'; +export * from './auth/account.service'; +export * from './auth/auth-jwt.service'; +export * from './auth/auth.service'; +export * from './auth/principal.service'; +export * from './auth/has-any-authority.directive'; +export * from './language/language.constants'; +export * from './language/language.helper'; +export * from './language/language.pipe'; +export * from './login/login.component'; +export * from './login/login.service'; +export * from './login/login-modal.service'; +export * from './constants/pagination.constants'; +export * from './user/account.model'; +export * from './user/user.model'; +export * from './user/user.service'; +export * from './shared-libs.module'; +export * from './shared-common.module'; +export * from './shared.module'; +export * from './auth/user-route-access-service'; diff --git a/jhipster/src/main/webapp/app/shared/language/language.constants.ts b/jhipster/src/main/webapp/app/shared/language/language.constants.ts new file mode 100644 index 0000000000..2292ef4624 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/language/language.constants.ts @@ -0,0 +1,8 @@ +/* + Languages codes are ISO_639-1 codes, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + They are written in English to avoid character encoding issues (not a perfect solution) +*/ +export const LANGUAGES: string[] = [ + 'en' + // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array +]; diff --git a/jhipster/src/main/webapp/app/shared/language/language.helper.ts b/jhipster/src/main/webapp/app/shared/language/language.helper.ts new file mode 100644 index 0000000000..d8d609bf0c --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/language/language.helper.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { Router, ActivatedRouteSnapshot } from '@angular/router'; +import { TranslateService, TranslationChangeEvent, LangChangeEvent } from 'ng2-translate/ng2-translate'; + +import { LANGUAGES } from './language.constants'; + +@Injectable() +export class JhiLanguageHelper { + + constructor (private translateService: TranslateService, private titleService: Title, private router: Router) { + this.init(); + } + + getAll(): Promise { + return Promise.resolve(LANGUAGES); + } + + /** + * Update the window title using params in the following + * order: + * 1. titleKey parameter + * 2. $state.$current.data.pageTitle (current state page title) + * 3. 'global.title' + */ + updateTitle(titleKey?: string) { + if (!titleKey) { + titleKey = this.getPageTitle(this.router.routerState.snapshot.root); + } + + this.translateService.get(titleKey).subscribe(title => { + this.titleService.setTitle(title); + }); + } + + private init () { + this.translateService.onTranslationChange.subscribe((event: TranslationChangeEvent) => { + this.updateTitle(); + }); + + this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { + this.updateTitle(); + }); + } + + private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) { + let title: string = (routeSnapshot.data && routeSnapshot.data['pageTitle']) ? routeSnapshot.data['pageTitle'] : 'baeldungApp'; + if (routeSnapshot.firstChild) { + title = this.getPageTitle(routeSnapshot.firstChild) || title; + } + return title; + } +} diff --git a/jhipster/src/main/webapp/app/shared/language/language.pipe.ts b/jhipster/src/main/webapp/app/shared/language/language.pipe.ts new file mode 100644 index 0000000000..d271c8e2c2 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/language/language.pipe.ts @@ -0,0 +1,42 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'findLanguageFromKey'}) +export class FindLanguageFromKeyPipe implements PipeTransform { + private languages: any = { + 'ca': 'Català', + 'cs': 'ÄŒeský', + 'da': 'Dansk', + 'de': 'Deutsch', + 'el': 'Ελληνικά', + 'en': 'English', + 'es': 'Español', + 'et': 'Eesti', + 'fr': 'Français', + 'gl': 'Galego', + 'hu': 'Magyar', + 'hi': 'हिंदी', + 'hy': 'Õ€Õ¡ÕµÕ¥Ö€Õ¥Õ¶', + 'it': 'Italiano', + 'ja': '日本語', + 'ko': '한국어', + 'mr': 'मराठी', + 'nl': 'Nederlands', + 'pl': 'Polski', + 'pt-br': 'Português (Brasil)', + 'pt-pt': 'Português', + 'ro': 'Română', + 'ru': 'РуÑÑкий', + 'sk': 'Slovenský', + 'sr': 'Srpski', + 'sv': 'Svenska', + 'ta': 'தமிழà¯', + 'th': 'ไทย', + 'tr': 'Türkçe', + 'vi': 'Tiếng Việt', + 'zh-cn': '中文(简体)', + 'zh-tw': 'ç¹é«”中文' + }; + transform(lang: string): string { + return this.languages[lang]; + } +} diff --git a/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts b/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts new file mode 100644 index 0000000000..9e435978ea --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + +import { JhiLoginModalComponent } from './login.component'; + +@Injectable() +export class LoginModalService { + private isOpen = false; + constructor ( + private modalService: NgbModal, + ) {} + + open (): NgbModalRef { + if (this.isOpen) { + return; + } + this.isOpen = true; + let modalRef = this.modalService.open(JhiLoginModalComponent); + modalRef.result.then(result => { + this.isOpen = false; + }, (reason) => { + this.isOpen = false; + }); + return modalRef; + } +} diff --git a/jhipster/src/main/webapp/app/shared/login/login.component.html b/jhipster/src/main/webapp/app/shared/login/login.component.html new file mode 100644 index 0000000000..a6b6b19249 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/login/login.component.html @@ -0,0 +1,46 @@ + + diff --git a/jhipster/src/main/webapp/app/shared/login/login.component.ts b/jhipster/src/main/webapp/app/shared/login/login.component.ts new file mode 100644 index 0000000000..90acbb03ac --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/login/login.component.ts @@ -0,0 +1,90 @@ +import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Router } from '@angular/router'; +import { JhiLanguageService, EventManager } from 'ng-jhipster'; + +import { LoginService } from '../login/login.service'; +import { StateStorageService } from '../auth/state-storage.service'; + +@Component({ + selector: 'jhi-login-modal', + templateUrl: './login.component.html' +}) +export class JhiLoginModalComponent implements OnInit, AfterViewInit { + authenticationError: boolean; + password: string; + rememberMe: boolean; + username: string; + credentials: any; + + constructor( + private eventManager: EventManager, + private languageService: JhiLanguageService, + private loginService: LoginService, + private stateStorageService: StateStorageService, + private elementRef: ElementRef, + private renderer: Renderer, + private router: Router, + public activeModal: NgbActiveModal + ) { + this.credentials = {}; + } + + ngOnInit() { + this.languageService.addLocation('login'); + } + + ngAfterViewInit() { + this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#username'), 'focus', []); + } + + cancel () { + this.credentials = { + username: null, + password: null, + rememberMe: true + }; + this.authenticationError = false; + this.activeModal.dismiss('cancel'); + } + + login () { + this.loginService.login({ + username: this.username, + password: this.password, + rememberMe: this.rememberMe + }).then(() => { + this.authenticationError = false; + this.activeModal.dismiss('login success'); + if (this.router.url === '/register' || (/activate/.test(this.router.url)) || + this.router.url === '/finishReset' || this.router.url === '/requestReset') { + this.router.navigate(['']); + } + + this.eventManager.broadcast({ + name: 'authenticationSuccess', + content: 'Sending Authentication Success' + }); + + // // previousState was set in the authExpiredInterceptor before being redirected to login modal. + // // since login is succesful, go to stored previousState and clear previousState + let previousState = this.stateStorageService.getPreviousState(); + if (previousState) { + this.stateStorageService.resetPreviousState(); + this.router.navigate([previousState.name], { queryParams: previousState.params }); + } + }).catch(() => { + this.authenticationError = true; + }); + } + + register () { + this.activeModal.dismiss('to state register'); + this.router.navigate(['/register']); + } + + requestResetPassword () { + this.activeModal.dismiss('to state requestReset'); + this.router.navigate(['/reset', 'request']); + } +} diff --git a/jhipster/src/main/webapp/app/shared/login/login.service.ts b/jhipster/src/main/webapp/app/shared/login/login.service.ts new file mode 100644 index 0000000000..2235299225 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/login/login.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { JhiLanguageService } from 'ng-jhipster'; + +import { Principal } from '../auth/principal.service'; +import { AuthServerProvider } from '../auth/auth-jwt.service'; + +@Injectable() +export class LoginService { + + constructor ( + private languageService: JhiLanguageService, + private principal: Principal, + private authServerProvider: AuthServerProvider + ) {} + + login (credentials, callback?) { + let cb = callback || function() {}; + + return new Promise((resolve, reject) => { + this.authServerProvider.login(credentials).subscribe(data => { + this.principal.identity(true).then(account => { + // After the login the language will be changed to + // the language selected by the user during his registration + if (account !== null) { + this.languageService.changeLanguage(account.langKey); + } + resolve(data); + }); + return cb(); + }, err => { + this.logout(); + reject(err); + return cb(err); + }); + }); + } + loginWithToken(jwt, rememberMe) { + return this.authServerProvider.loginWithToken(jwt, rememberMe); + } + + logout () { + this.authServerProvider.logout().subscribe(); + this.principal.authenticate(null); + } +} diff --git a/jhipster/src/main/webapp/app/shared/shared-common.module.ts b/jhipster/src/main/webapp/app/shared/shared-common.module.ts new file mode 100644 index 0000000000..6f713e216b --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/shared-common.module.ts @@ -0,0 +1,47 @@ +import { NgModule, Sanitizer } from '@angular/core'; +import { Title } from '@angular/platform-browser'; + +import { TranslateService } from 'ng2-translate'; +import { AlertService } from 'ng-jhipster'; + +import { + BaeldungSharedLibsModule, + JhiLanguageHelper, + FindLanguageFromKeyPipe, + JhiAlertComponent, + JhiAlertErrorComponent +} from './'; + + +export function alertServiceProvider(sanitizer: Sanitizer, translateService: TranslateService) { + // set below to true to make alerts look like toast + let isToast = false; + return new AlertService(sanitizer, isToast, translateService); +} + +@NgModule({ + imports: [ + BaeldungSharedLibsModule + ], + declarations: [ + FindLanguageFromKeyPipe, + JhiAlertComponent, + JhiAlertErrorComponent + ], + providers: [ + JhiLanguageHelper, + { + provide: AlertService, + useFactory: alertServiceProvider, + deps: [Sanitizer, TranslateService] + }, + Title + ], + exports: [ + BaeldungSharedLibsModule, + FindLanguageFromKeyPipe, + JhiAlertComponent, + JhiAlertErrorComponent + ] +}) +export class BaeldungSharedCommonModule {} diff --git a/jhipster/src/main/webapp/app/shared/shared-libs.module.ts b/jhipster/src/main/webapp/app/shared/shared-libs.module.ts new file mode 100644 index 0000000000..0bf10eeaa8 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/shared-libs.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { CommonModule } from '@angular/common'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgJhipsterModule } from 'ng-jhipster'; +import { InfiniteScrollModule } from 'angular2-infinite-scroll'; + +@NgModule({ + imports: [ + NgbModule.forRoot(), + NgJhipsterModule.forRoot({ + i18nEnabled: true, + defaultI18nLang: 'en' + }), + InfiniteScrollModule + ], + exports: [ + FormsModule, + HttpModule, + CommonModule, + NgbModule, + NgJhipsterModule, + InfiniteScrollModule + ] +}) +export class BaeldungSharedLibsModule {} diff --git a/jhipster/src/main/webapp/app/shared/shared.module.ts b/jhipster/src/main/webapp/app/shared/shared.module.ts new file mode 100644 index 0000000000..f7af13852b --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/shared.module.ts @@ -0,0 +1,53 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { DatePipe } from '@angular/common'; + +import { CookieService } from 'angular2-cookie/services/cookies.service'; +import { + BaeldungSharedLibsModule, + BaeldungSharedCommonModule, + CSRFService, + AuthService, + AuthServerProvider, + AccountService, + UserService, + StateStorageService, + LoginService, + LoginModalService, + Principal, + HasAnyAuthorityDirective, + JhiLoginModalComponent +} from './'; + +@NgModule({ + imports: [ + BaeldungSharedLibsModule, + BaeldungSharedCommonModule + ], + declarations: [ + JhiLoginModalComponent, + HasAnyAuthorityDirective + ], + providers: [ + CookieService, + LoginService, + LoginModalService, + AccountService, + StateStorageService, + Principal, + CSRFService, + AuthServerProvider, + AuthService, + UserService, + DatePipe + ], + entryComponents: [JhiLoginModalComponent], + exports: [ + BaeldungSharedCommonModule, + JhiLoginModalComponent, + HasAnyAuthorityDirective, + DatePipe + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + +}) +export class BaeldungSharedModule {} diff --git a/jhipster/src/main/webapp/app/shared/user/account.model.ts b/jhipster/src/main/webapp/app/shared/user/account.model.ts new file mode 100644 index 0000000000..c8b9750760 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/user/account.model.ts @@ -0,0 +1,12 @@ +export class Account { + constructor( + public activated: boolean, + public authorities: string[], + public email: string, + public firstName: string, + public langKey: string, + public lastName: string, + public login: string, + public imageUrl: string + ) { } +} diff --git a/jhipster/src/main/webapp/app/shared/user/user.model.ts b/jhipster/src/main/webapp/app/shared/user/user.model.ts new file mode 100644 index 0000000000..374c3ae0ca --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/user/user.model.ts @@ -0,0 +1,44 @@ +export class User { + public id?: any; + public login?: string; + public firstName?: string; + public lastName?: string; + public email?: string; + public activated?: Boolean; + public langKey?: string; + public authorities?: any[]; + public createdBy?: string; + public createdDate?: Date; + public lastModifiedBy?: string; + public lastModifiedDate?: Date; + public password?: string; + constructor( + id?: any, + login?: string, + firstName?: string, + lastName?: string, + email?: string, + activated?: Boolean, + langKey?: string, + authorities?: any[], + createdBy?: string, + createdDate?: Date, + lastModifiedBy?: string, + lastModifiedDate?: Date, + password?: string + ) { + this.id = id ? id : null; + this.login = login ? login : null; + this.firstName = firstName ? firstName : null; + this.lastName = lastName ? lastName : null; + this.email = email ? email : null; + this.activated = activated ? activated : false; + this.langKey = langKey ? langKey : null; + this.authorities = authorities ? authorities : null; + this.createdBy = createdBy ? createdBy : null; + this.createdDate = createdDate ? createdDate : null; + this.lastModifiedBy = lastModifiedBy ? lastModifiedBy : null; + this.lastModifiedDate = lastModifiedDate ? lastModifiedDate : null; + this.password = password ? password : null; + } +} diff --git a/jhipster/src/main/webapp/app/shared/user/user.service.ts b/jhipster/src/main/webapp/app/shared/user/user.service.ts new file mode 100644 index 0000000000..b0e1121215 --- /dev/null +++ b/jhipster/src/main/webapp/app/shared/user/user.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, URLSearchParams } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import { User } from './user.model'; + +@Injectable() +export class UserService { + private resourceUrl = 'api/users'; + + constructor(private http: Http) { } + + create(user: User): Observable { + return this.http.post(this.resourceUrl, user); + } + + update(user: User): Observable { + return this.http.put(this.resourceUrl, user); + } + + find(login: string): Observable { + return this.http.get(`${this.resourceUrl}/${login}`).map((res: Response) => res.json()); + } + + query(req?: any): Observable { + let params: URLSearchParams = new URLSearchParams(); + if (req) { + params.set('page', req.page); + params.set('size', req.size); + if (req.sort) { + params.paramsMap.set('sort', req.sort); + } + } + + let options = { + search: params + }; + + return this.http.get(this.resourceUrl, options); + } + + delete(login: string): Observable { + return this.http.delete(`${this.resourceUrl}/${login}`); + } +} diff --git a/jhipster/src/main/webapp/app/vendor.ts b/jhipster/src/main/webapp/app/vendor.ts new file mode 100644 index 0000000000..f9ccfd5b00 --- /dev/null +++ b/jhipster/src/main/webapp/app/vendor.ts @@ -0,0 +1,3 @@ +/* after changing this file run 'npm install' or 'npm run webpack:build' */ +/* tslint:disable */ +import '../content/scss/vendor.scss'; diff --git a/jhipster/src/main/webapp/content/images/hipster.png b/jhipster/src/main/webapp/content/images/hipster.png new file mode 100644 index 0000000000000000000000000000000000000000..141778d482528a8c6636d56d6683f74b21a34394 GIT binary patch literal 9499 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 literal 0 HcmV?d00001 diff --git a/jhipster/src/main/webapp/content/images/logo-jhipster.png b/jhipster/src/main/webapp/content/images/logo-jhipster.png new file mode 100644 index 0000000000000000000000000000000000000000..d8eb48da05c157571eed9fd7303f8d9566f2ea12 GIT binary patch literal 4459 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 { + dd { + margin-bottom: 15px; + } +} + +@media screen and (min-width: 768px) { + .row.jh-entity-details > { + dt { + margin-bottom: 15px; + } + dd { + border-bottom: 1px solid #eee; + padding-left: 180px; + margin-left: 0; + } + } +} + +/* ========================================================================== +ui bootstrap tweaks +========================================================================== */ +.nav, .pagination, .carousel, .panel-title a { + cursor: pointer; +} + +.datetime-picker-dropdown > li.date-picker-menu div > table .btn-default, +.uib-datepicker-popup > li > div.uib-datepicker > table .btn-default { + border: 0; +} + +.datetime-picker-dropdown > li.date-picker-menu div > table:focus, +.uib-datepicker-popup > li > div.uib-datepicker > table:focus { + outline: none; +} + + +/* jhipster-needle-scss-add-main JHipster will add new css style */ diff --git a/jhipster/src/main/webapp/content/scss/vendor.scss b/jhipster/src/main/webapp/content/scss/vendor.scss new file mode 100644 index 0000000000..55990bbef5 --- /dev/null +++ b/jhipster/src/main/webapp/content/scss/vendor.scss @@ -0,0 +1,10 @@ +/* after changing this file run 'npm install' or 'npm run webpack:build' */ +$fa-font-path: '~font-awesome/fonts'; + +/*************************** +put Sass variables here: +eg $input-color: red; +****************************/ + +@import 'node_modules/bootstrap/scss/bootstrap'; +@import 'node_modules/font-awesome/scss/font-awesome'; diff --git a/jhipster/src/main/webapp/favicon.ico b/jhipster/src/main/webapp/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..44c7595f58f5a2c52f5e21451e5e6868d6caea7f GIT binary patch literal 5430 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 user has been activated. Please ", + "error": "Your user could not be activated. Please use the registration form to sign up." + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/audits.json b/jhipster/src/main/webapp/i18n/en/audits.json new file mode 100644 index 0000000000..ed5e16d4c5 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/audits.json @@ -0,0 +1,27 @@ +{ + "audits": { + "title": "Audits", + "filter": { + "title": "Filter per date", + "from": "from", + "to": "to", + "button": { + "weeks": "Weeks", + "today": "today", + "clear": "clear", + "close": "close" + } + }, + "table": { + "header": { + "principal": "User", + "date": "Date", + "status": "State", + "data": "Extra data" + }, + "data": { + "remoteAddress": "Remote Address:" + } + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/comment.json b/jhipster/src/main/webapp/i18n/en/comment.json new file mode 100644 index 0000000000..eed148855a --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/comment.json @@ -0,0 +1,23 @@ +{ + "baeldungApp": { + "comment" : { + "home": { + "title": "Comments", + "createLabel": "Create a new Comment", + "createOrEditLabel": "Create or edit a Comment" + }, + "created": "A new Comment is created with identifier {{ param }}", + "updated": "A Comment is updated with identifier {{ param }}", + "deleted": "A Comment is deleted with identifier {{ param }}", + "delete": { + "question": "Are you sure you want to delete Comment {{ id }}?" + }, + "detail": { + "title": "Comment" + }, + "text": "Text", + "creationDate": "Creation Date", + "post": "Post" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/configuration.json b/jhipster/src/main/webapp/i18n/en/configuration.json new file mode 100644 index 0000000000..81e208de5c --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/configuration.json @@ -0,0 +1,10 @@ +{ + "configuration": { + "title": "Configuration", + "filter": "Filter (by prefix)", + "table": { + "prefix": "Prefix", + "properties": "Properties" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/error.json b/jhipster/src/main/webapp/i18n/en/error.json new file mode 100644 index 0000000000..65246c3b03 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/error.json @@ -0,0 +1,6 @@ +{ + "error": { + "title": "Error page!", + "403": "You are not authorized to access the page." + } +} diff --git a/jhipster/src/main/webapp/i18n/en/gateway.json b/jhipster/src/main/webapp/i18n/en/gateway.json new file mode 100644 index 0000000000..8c8bbd7e98 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/gateway.json @@ -0,0 +1,15 @@ +{ + "gateway": { + "title": "Gateway", + "routes": { + "title": "Current routes", + "url": "URL", + "service": "service", + "servers": "Available servers", + "error": "Warning: no server available!" + }, + "refresh": { + "button": "Refresh" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/global.json b/jhipster/src/main/webapp/i18n/en/global.json new file mode 100644 index 0000000000..0c49362336 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/global.json @@ -0,0 +1,133 @@ +{ + "global": { + "title": "Baeldung", + "browsehappy": "You are using an outdated browser. Please upgrade your browser to improve your experience.", + "menu": { + "home": "Home", + "jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)", + "entities": { + "main": "Entities", + "post": "Post", + "comment": "Comment", + "jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)" + }, + "account": { + "main": "Account", + "settings": "Settings", + "password": "Password", + "sessions": "Sessions", + "login": "Sign in", + "logout": "Sign out", + "register": "Register" + }, + "admin": { + "main": "Administration", + "userManagement": "User management", + "tracker": "User tracker", + "metrics": "Metrics", + "health": "Health", + "configuration": "Configuration", + "logs": "Logs", + "audits": "Audits", + "apidocs": "API", + "database": "Database", + "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)" + }, + "language": "Language" + }, + "form": { + "username": "Username", + "username.placeholder": "Your username", + "newpassword": "New password", + "newpassword.placeholder": "New password", + "confirmpassword": "New password confirmation", + "confirmpassword.placeholder": "Confirm the new password", + "email": "E-mail", + "email.placeholder": "Your e-mail" + }, + "messages": { + "info": { + "authenticated": { + "prefix": "If you want to ", + "link": "sign in", + "suffix": ", you can try the default accounts:
- Administrator (login=\"admin\" and password=\"admin\")
- User (login=\"user\" and password=\"user\")." + }, + "register": { + "noaccount": "You don't have an account yet?", + "link": "Register a new account" + } + }, + "error": { + "dontmatch": "The password and its confirmation do not match!" + }, + "validate": { + "newpassword": { + "required": "Your password is required.", + "minlength": "Your password is required to be at least 4 characters.", + "maxlength": "Your password cannot be longer than 50 characters.", + "strength": "Password strength:" + }, + "confirmpassword": { + "required": "Your confirmation password is required.", + "minlength": "Your confirmation password is required to be at least 4 characters.", + "maxlength": "Your confirmation password cannot be longer than 50 characters." + }, + "email": { + "required": "Your e-mail is required.", + "invalid": "Your e-mail is invalid.", + "minlength": "Your e-mail is required to be at least 5 characters.", + "maxlength": "Your e-mail cannot be longer than 50 characters." + } + } + }, + "field": { + "id": "ID" + }, + "ribbon": { + "dev":"Development" + } + }, + "entity": { + "action": { + "addblob": "Add blob", + "addimage": "Add image", + "back": "Back", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "open": "Open", + "save": "Save", + "view": "View" + }, + "detail": { + "field": "Field", + "value": "Value" + }, + "delete": { + "title": "Confirm delete operation" + }, + "validation": { + "required": "This field is required.", + "minlength": "This field is required to be at least {{ min }} characters.", + "maxlength": "This field cannot be longer than {{ max }} characters.", + "min": "This field should be at least {{ min }}.", + "max": "This field cannot be more than {{ max }}.", + "minbytes": "This field should be at least {{ min }} bytes.", + "maxbytes": "This field cannot be more than {{ max }} bytes.", + "pattern": "This field should follow pattern {{ pattern }}.", + "number": "This field should be a number.", + "datetimelocal": "This field should be a date and time." + } + }, + "error": { + "internalServerError": "Internal server error", + "server.not.reachable": "Server not reachable", + "url.not.found": "Not found", + "NotNull": "Field {{ fieldName }} cannot be empty!", + "Size": "Field {{ fieldName }} does not meet min/max size requirements!", + "userexists": "Login name already used!", + "emailexists": "E-mail is already in use!", + "idexists": "A new {{ entityName }} cannot already have an ID" + }, + "footer": "This is your footer" +} diff --git a/jhipster/src/main/webapp/i18n/en/health.json b/jhipster/src/main/webapp/i18n/en/health.json new file mode 100644 index 0000000000..868ea3fda4 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/health.json @@ -0,0 +1,27 @@ +{ + "health": { + "title": "Health Checks", + "refresh.button": "Refresh", + "stacktrace": "Stacktrace", + "details": { + "details": "Details", + "properties": "Properties", + "name": "Name", + "value": "Value", + "error": "Error" + }, + "indicator": { + "diskSpace": "Disk space", + "mail": "Email", + "db": "Database" + }, + "table": { + "service": "Service name", + "status": "Status" + }, + "status": { + "UP": "UP", + "DOWN": "DOWN" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/home.json b/jhipster/src/main/webapp/i18n/en/home.json new file mode 100644 index 0000000000..402f18700a --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/home.json @@ -0,0 +1,19 @@ +{ + "home": { + "title": "Welcome, Java Hipster!", + "subtitle": "This is your homepage", + "logged": { + "message": "You are logged in as user \"{{username}}\"." + }, + "question": "If you have any question on JHipster:", + "link": { + "homepage": "JHipster homepage", + "stackoverflow": "JHipster on Stack Overflow", + "bugtracker": "JHipster bug tracker", + "chat": "JHipster public chat room", + "follow": "follow @java_hipster on Twitter" + }, + "like": "If you like JHipster, don't forget to give us a star on", + "github": "GitHub" + } +} diff --git a/jhipster/src/main/webapp/i18n/en/login.json b/jhipster/src/main/webapp/i18n/en/login.json new file mode 100644 index 0000000000..3a000852e6 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/login.json @@ -0,0 +1,19 @@ +{ + "login": { + "title": "Sign in", + "form": { + "password": "Password", + "password.placeholder": "Your password", + "rememberme": "Remember me", + "button": "Sign in" + }, + "messages": { + "error": { + "authentication": "Failed to sign in! Please check your credentials and try again." + } + }, + "password" : { + "forgot": "Did you forget your password?" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/logs.json b/jhipster/src/main/webapp/i18n/en/logs.json new file mode 100644 index 0000000000..a614b128da --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/logs.json @@ -0,0 +1,11 @@ +{ + "logs": { + "title": "Logs", + "nbloggers": "There are {{ total }} loggers.", + "filter": "Filter", + "table": { + "name": "Name", + "level": "Level" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/metrics.json b/jhipster/src/main/webapp/i18n/en/metrics.json new file mode 100644 index 0000000000..9d804947c5 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/metrics.json @@ -0,0 +1,101 @@ +{ + "metrics": { + "title": "Application Metrics", + "refresh.button": "Refresh", + "updating": "Updating...", + "jvm": { + "title": "JVM Metrics", + "memory": { + "title": "Memory", + "total": "Total Memory", + "heap": "Heap Memory", + "nonheap": "Non-Heap Memory" + }, + "threads": { + "title": "Threads", + "all": "All", + "runnable": "Runnable", + "timedwaiting": "Timed waiting", + "waiting": "Waiting", + "blocked": "Blocked", + "dump": { + "title": "Threads dump", + "id": "Id: ", + "blockedtime": "Blocked Time", + "blockedcount": "Blocked Count", + "waitedtime": "Waited Time", + "waitedcount": "Waited Count", + "lockname": "Lock name", + "stacktrace": "Stacktrace", + "show": "Show Stacktrace", + "hide": "Hide Stacktrace" + } + }, + "gc": { + "title": "Garbage collections", + "marksweepcount": "Mark Sweep count", + "marksweeptime": "Mark Sweep time", + "scavengecount": "Scavenge count", + "scavengetime": "Scavenge time" + }, + "http": { + "title": "HTTP requests (events per second)", + "active": "Active requests:", + "total": "Total requests:", + "table": { + "code": "Code", + "count": "Count", + "mean": "Mean", + "average": "Average" + }, + "code": { + "ok": "Ok", + "notfound": "Not found", + "servererror": "Server Error" + } + } + }, + "servicesstats": { + "title": "Services statistics (time in millisecond)", + "table": { + "name": "Service name", + "count": "Count", + "mean": "Mean", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + }, + "cache": { + "title": "Cache statistics", + "cachename": "Cache name", + "hits": "Cache Hits", + "misses": "Cache Misses", + "gets": "Cache Gets", + "puts": "Cache Puts", + "removals": "Cache Removals", + "evictions": "Cache Evictions", + "hitPercent": "Cache Hit %", + "missPercent": "Cache Miss %", + "averageGetTime": "Average get time (µs)", + "averagePutTime": "Average put time (µs)", + "averageRemoveTime": "Average remove time (µs)" + }, + "datasource": { + "usage": "Usage", + "title": "DataSource statistics (time in millisecond)", + "name": "Pool usage", + "count": "Count", + "mean": "Mean", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/password.json b/jhipster/src/main/webapp/i18n/en/password.json new file mode 100644 index 0000000000..46227a7702 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/password.json @@ -0,0 +1,12 @@ +{ + "password": { + "title": "Password for [{{username}}]", + "form": { + "button": "Save" + }, + "messages": { + "error": "An error has occurred! The password could not be changed.", + "success": "Password changed!" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/post.json b/jhipster/src/main/webapp/i18n/en/post.json new file mode 100644 index 0000000000..14c64f3f90 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/post.json @@ -0,0 +1,24 @@ +{ + "baeldungApp": { + "post" : { + "home": { + "title": "Posts", + "createLabel": "Create a new Post", + "createOrEditLabel": "Create or edit a Post" + }, + "created": "A new Post is created with identifier {{ param }}", + "updated": "A Post is updated with identifier {{ param }}", + "deleted": "A Post is deleted with identifier {{ param }}", + "delete": { + "question": "Are you sure you want to delete Post {{ id }}?" + }, + "detail": { + "title": "Post" + }, + "title": "Title", + "content": "Content", + "creationDate": "Creation Date", + "creator": "Creator" + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/register.json b/jhipster/src/main/webapp/i18n/en/register.json new file mode 100644 index 0000000000..df8f6e31b8 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/register.json @@ -0,0 +1,24 @@ +{ + "register": { + "title": "Registration", + "form": { + "button": "Register" + }, + "messages": { + "validate": { + "login": { + "required": "Your username is required.", + "minlength": "Your username is required to be at least 1 character.", + "maxlength": "Your username cannot be longer than 50 characters.", + "pattern": "Your username can only contain lower-case letters and digits." + } + }, + "success": "Registration saved! Please check your email for confirmation.", + "error": { + "fail": "Registration failed! Please try again later.", + "userexists": "Login name already registered! Please choose another one.", + "emailexists": "E-mail is already in use! Please choose another one." + } + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/reset.json b/jhipster/src/main/webapp/i18n/en/reset.json new file mode 100644 index 0000000000..fc61e0070f --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/reset.json @@ -0,0 +1,27 @@ +{ + "reset": { + "request": { + "title": "Reset your password", + "form": { + "button": "Reset password" + }, + "messages": { + "info": "Enter the e-mail address you used to register", + "success": "Check your e-mails for details on how to reset your password.", + "notfound": "E-Mail address isn't registered! Please check and try again" + } + }, + "finish" : { + "title": "Reset password", + "form": { + "button": "Validate new password" + }, + "messages": { + "info": "Choose a new password", + "success": "Your password has been reset. Please ", + "keymissing": "The reset key is missing.", + "error": "Your password couldn't be reset. Remember a password request is only valid for 24 hours." + } + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/sessions.json b/jhipster/src/main/webapp/i18n/en/sessions.json new file mode 100644 index 0000000000..d410035ee7 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/sessions.json @@ -0,0 +1,15 @@ +{ + "sessions": { + "title": "Active sessions for [{{username}}]", + "table": { + "ipaddress": "IP address", + "useragent": "User Agent", + "date": "Date", + "button": "Invalidate" + }, + "messages": { + "success": "Session invalidated!", + "error": "An error has occurred! The session could not be invalidated." + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/settings.json b/jhipster/src/main/webapp/i18n/en/settings.json new file mode 100644 index 0000000000..919ab51cc9 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/settings.json @@ -0,0 +1,32 @@ +{ + "settings": { + "title": "User settings for [{{username}}]", + "form": { + "firstname": "First Name", + "firstname.placeholder": "Your first name", + "lastname": "Last Name", + "lastname.placeholder": "Your last name", + "language": "Language", + "button": "Save" + }, + "messages": { + "error": { + "fail": "An error has occurred! Settings could not be saved.", + "emailexists": "E-mail is already in use! Please choose another one." + }, + "success": "Settings saved!", + "validate": { + "firstname": { + "required": "Your first name is required.", + "minlength": "Your first name is required to be at least 1 character", + "maxlength": "Your first name cannot be longer than 50 characters" + }, + "lastname": { + "required": "Your last name is required.", + "minlength": "Your last name is required to be at least 1 character", + "maxlength": "Your last name cannot be longer than 50 characters" + } + } + } + } +} diff --git a/jhipster/src/main/webapp/i18n/en/user-management.json b/jhipster/src/main/webapp/i18n/en/user-management.json new file mode 100644 index 0000000000..30c125b6d0 --- /dev/null +++ b/jhipster/src/main/webapp/i18n/en/user-management.json @@ -0,0 +1,30 @@ +{ + "userManagement": { + "home": { + "title": "Users", + "createLabel": "Create a new user", + "createOrEditLabel": "Create or edit a user" + }, + "created": "A new user is created with identifier {{ param }}", + "updated": "An user is updated with identifier {{ param }}", + "deleted": "An user is deleted with identifier {{ param }}", + "delete": { + "question": "Are you sure you want to delete user {{ login }}?" + }, + "detail": { + "title": "User" + }, + "login": "Login", + "firstName": "First name", + "lastName": "Last name", + "email": "Email", + "activated": "Activated", + "deactivated": "Deactivated", + "profiles": "Profiles", + "langKey": "Language", + "createdBy": "Created by", + "createdDate": "Created date", + "lastModifiedBy": "Modified by", + "lastModifiedDate": "Modified date" + } +} diff --git a/jhipster/src/main/webapp/index.html b/jhipster/src/main/webapp/index.html new file mode 100644 index 0000000000..6227d62823 --- /dev/null +++ b/jhipster/src/main/webapp/index.html @@ -0,0 +1,27 @@ + + + + + + + baeldung + + + + + + + + + + diff --git a/jhipster/src/main/webapp/robots.txt b/jhipster/src/main/webapp/robots.txt new file mode 100644 index 0000000000..7de2585cf6 --- /dev/null +++ b/jhipster/src/main/webapp/robots.txt @@ -0,0 +1,11 @@ +# robotstxt.org/ + +User-agent: * +Disallow: /api/account +Disallow: /api/account/change_password +Disallow: /api/account/sessions +Disallow: /api/audits/ +Disallow: /api/logs/ +Disallow: /api/users/ +Disallow: /management/ +Disallow: /v2/api-docs/ diff --git a/jhipster/src/main/webapp/swagger-ui/images/throbber.gif b/jhipster/src/main/webapp/swagger-ui/images/throbber.gif new file mode 100644 index 0000000000000000000000000000000000000000..06393889242fb3ea9e0205fa84369ec7bb66d15a GIT binary patch literal 9257 zcmd^^X;@R|x`tQg5wbE8AV3mAn1TjmQ&en2CK8~ENEH<+P_)pZ24y2E+7O0>K^a6u zQ3;5MiU^7p6*M3qDk!2=YEcHMQ>nzEYP;R`e2C@r+U+?#XaC*&gKPcB#k$`o&;7mu zYNhYYXe|Uo84#4ZIko#rcU5K8*yFL{qT47O&^5fZH$ zVZ@%(l~vVHjnm;H@KL8@r%yUHoo;rbHI_4lIH(_nsTT>S2`DFOD~uCb9_dF4`#QgI zy7ldMcLs+A_s%|e1pRPrbX-tpeNP!9(IpMFTce`t_5U%lP99z%&i6`1d~ zWeM!Rxc50<+d$e^9LT`?B+aMK~apR zHm?q;p<7{wN2g|I^aGlSws;VP84j(z%aQwvAWv83Z$}p(% zZ^?2;gxg(ey_`V5J7{;!o;o;KslW@z5EP~JGs|U)J7dF&(ff#A=6vU?cGQ$-4+;Jf z-ggJEa!yStn`_EWvl)#yhm6XVs}UUbsi;+agri;mCfjH^Uy;lH+Zw^h)4N?oZgZz4 zJk(fTZ|Bi^;+s_M=~+d#vyoxEPzTlOS=mX@sbl*uRj>=MaMr}cFIY8i?UM61>86uB zV$DlOUCiUJwbzJMP@D$urzK|lL2-PC!p1l47V-ZG<5Ev0Z5h~Kx?`KOp7gkAjV93A z-Gc7MrlxTf?wF;CbNc@tCHJH{TB3c;#{SVu%97}tyAM2n&|9W_?qv}$*Jt*%7Yxb# zV0;d;7|lDEltJYS+U)#aiJO};?_Jyy_4%syQ(uy?-J-Yx-9O5nKRk@@XSS~X<(2u~ zV-LamWm~!iqtH9wkpf8mAXZhOD&L#aA_%)4h2M;1M5jt zIR>Us+%W-GXa_f^opKg=DSrAs)AXeRa;Hp0aC1OgbxQ%Qr_QvTleM1jkR!2mkcX$3 ztsR8~G9iqh(-FJ@F_rQBIYDXV_6s7G9SxaVF^laZqcx$!D97m|7t16j6@Jt6UdDRy49Qyvs|c>RuA|@b%}`*wU}2^7q;&Vtc6@lb zcXl)T!6nYDzmMJ~%n$KNXyNlCG)GkJ4!82;v6@d3>s5r~E+3!O?049JDr14Y^PeMI02R`0lJ^=oJ zYd|*u9|SU(j7hY?+<=(?fP*mtV*zFhOrz6%{VA?ozdm&(Jf^V zMfPZ?>l`mS3{Uq8IM;e!+1YjJy2!mzK$O|wPeU{*QSbs9m+@`f5KxO3PBnQ=%RsZg%go*fJ`*w9TL{-WgZVIA$!YV}3BRcfeXaR$x#b zW)Tpd#8E4)^MyYdkH;4_;ChJuw%n+Be7Ko4;w-nHvyo$d_0e-YiF78Df&)_)(}fcr_r0mPH(4RRYWIu+d@t0&Ss@O^s! zOKyX&13)%N@83r^;QsgN{rl(!0|RF1FA)b1{CRXAy&1ySz@>olPiR4r$aMdq&_=nK zq|cFs8phWJ1@%dZ-gXd{zDbTILD>)qEvH-NU*Rf1b2J1Ri79`rBFl@ z8E^0I)OqEi{pH(a24b9YPG;Kz@t-qZW;3Mpe`MRlmYx{7bH-XZ&`RQ7Rb^%}gc&X| zd}Q-FZf|RWxHU?PR!(C?80zu(^l>*h{#ulSiid(O!J(8P-41bNM3tnX@U6NS5yo0? zdcF)~xFE&+&|gZ$23dV5t~?$$&ymZ;F8j7GGMncGSsDo%>J`26=&l=X#rSKv_64;0 zr;k6no@=gV`P)K!=kaHl>q?!`X>(A;84tg^Md<`zA%qbRLby1Z=fn*ZRdNqs%Tq|3 zOt}lZu0q9oKJhgz&+^7PCt$=UFW=R*w?a1)ePoL*`R$Gxj?TU@12tTHsT$giHQU+sqf;fS0FpT!< z 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 + + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/jhipster/src/test/gatling/conf/gatling.conf b/jhipster/src/test/gatling/conf/gatling.conf new file mode 100644 index 0000000000..e509853f22 --- /dev/null +++ b/jhipster/src/test/gatling/conf/gatling.conf @@ -0,0 +1,131 @@ +######################### +# Gatling Configuration # +######################### + +# This file contains all the settings configurable for Gatling with their default values + +gatling { + core { + #outputDirectoryBaseName = "" # The prefix for each simulation result folder (then suffixed by the report generation timestamp) + #runDescription = "" # The description for this simulation run, displayed in each report + #encoding = "utf-8" # Encoding to use throughout Gatling for file and string manipulation + #simulationClass = "" # The FQCN of the simulation to run (when used in conjunction with noReports, the simulation for which assertions will be validated) + #mute = false # When set to true, don't ask for simulation name nor run description (currently only used by Gatling SBT plugin) + #elFileBodiesCacheMaxCapacity = 200 # Cache size for request body EL templates, set to 0 to disable + #rawFileBodiesCacheMaxCapacity = 200 # Cache size for request body Raw templates, set to 0 to disable + #rawFileBodiesInMemoryMaxSize = 1000 # Below this limit, raw file bodies will be cached in memory + + extract { + regex { + #cacheMaxCapacity = 200 # Cache size for the compiled regexes, set to 0 to disable caching + } + xpath { + #cacheMaxCapacity = 200 # Cache size for the compiled XPath queries, set to 0 to disable caching + } + jsonPath { + #cacheMaxCapacity = 200 # Cache size for the compiled jsonPath queries, set to 0 to disable caching + #preferJackson = false # When set to true, prefer Jackson over Boon for JSON-related operations + } + css { + #cacheMaxCapacity = 200 # Cache size for the compiled CSS selectors queries, set to 0 to disable caching + } + } + directory { + #data = user-files/data # Folder where user's data (e.g. files used by Feeders) is located + #bodies = user-files/bodies # Folder where bodies are located + #simulations = user-files/simulations # Folder where the bundle's simulations are located + #reportsOnly = "" # If set, name of report folder to look for in order to generate its report + #binaries = "" # If set, name of the folder where compiles classes are located: Defaults to GATLING_HOME/target. + #results = results # Name of the folder where all reports folder are located + } + } + charting { + #noReports = false # When set to true, don't generate HTML reports + #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports + #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration. + indicators { + #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary + #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary + #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite + #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite + #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite + #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite + } + } + http { + #fetchedCssCacheMaxCapacity = 200 # Cache size for CSS parsed content, set to 0 to disable + #fetchedHtmlCacheMaxCapacity = 200 # Cache size for HTML parsed content, set to 0 to disable + #perUserCacheMaxCapacity = 200 # Per virtual user cache size, set to 0 to disable + #warmUpUrl = "http://gatling.io" # The URL to use to warm-up the HTTP stack (blank means disabled) + #enableGA = true # Very light Google Analytics, please support + ssl { + keyStore { + #type = "" # Type of SSLContext's KeyManagers store + #file = "" # Location of SSLContext's KeyManagers store + #password = "" # Password for SSLContext's KeyManagers store + #algorithm = "" # Algorithm used SSLContext's KeyManagers store + } + trustStore { + #type = "" # Type of SSLContext's TrustManagers store + #file = "" # Location of SSLContext's TrustManagers store + #password = "" # Password for SSLContext's TrustManagers store + #algorithm = "" # Algorithm used by SSLContext's TrustManagers store + } + } + ahc { + #keepAlive = true # Allow pooling HTTP connections (keep-alive header automatically added) + #connectTimeout = 10000 # Timeout when establishing a connection + #handshakeTimeout = 10000 # Timeout when performing TLS hashshake + #pooledConnectionIdleTimeout = 60000 # Timeout when a connection stays unused in the pool + #readTimeout = 60000 # Timeout when a used connection stays idle + #maxRetry = 2 # Number of times that a request should be tried again + #requestTimeout = 60000 # Timeout of the requests + #acceptAnyCertificate = true # When set to true, doesn't validate SSL certificates + #httpClientCodecMaxInitialLineLength = 4096 # Maximum length of the initial line of the response (e.g. "HTTP/1.0 200 OK") + #httpClientCodecMaxHeaderSize = 8192 # Maximum size, in bytes, of each request's headers + #httpClientCodecMaxChunkSize = 8192 # Maximum length of the content or each chunk + #webSocketMaxFrameSize = 10240000 # Maximum frame payload size + #sslEnabledProtocols = [TLSv1.2, TLSv1.1, TLSv1] # Array of enabled protocols for HTTPS, if empty use the JDK defaults + #sslEnabledCipherSuites = [] # Array of enabled cipher suites for HTTPS, if empty use the AHC defaults + #sslSessionCacheSize = 0 # SSLSession cache size, set to 0 to use JDK's default + #sslSessionTimeout = 0 # SSLSession timeout in seconds, set to 0 to use JDK's default (24h) + #useOpenSsl = false # if OpenSSL should be used instead of JSSE (requires tcnative jar) + #useNativeTransport = false # if native transport should be used instead of Java NIO (requires netty-transport-native-epoll, currently Linux only) + #tcpNoDelay = true + #soReuseAddress = false + #soLinger = -1 + #soSndBuf = -1 + #soRcvBuf = -1 + #allocator = "pooled" # switch to unpooled for unpooled ByteBufAllocator + #maxThreadLocalCharBufferSize = 200000 # Netty's default is 16k + } + dns { + #queryTimeout = 5000 # Timeout of each DNS query in millis + #maxQueriesPerResolve = 6 # Maximum allowed number of DNS queries for a given name resolution + } + } + jms { + #acknowledgedMessagesBufferSize = 5000 # size of the buffer used to tracked acknowledged messages and protect against duplicate receives + } + data { + #writers = [console, file] # The list of DataWriters to which Gatling write simulation data (currently supported : console, file, graphite, jdbc) + console { + #light = false # When set to true, displays a light version without detailed request stats + } + file { + #bufferSize = 8192 # FileDataWriter's internal data buffer size, in bytes + } + leak { + #noActivityTimeout = 30 # Period, in seconds, for which Gatling may have no activity before considering a leak may be happening + } + graphite { + #light = false # only send the all* stats + #host = "localhost" # The host where the Carbon server is located + #port = 2003 # The port to which the Carbon server listens to (2003 is default for plaintext, 2004 is default for pickle) + #protocol = "tcp" # The protocol used to send data to Carbon (currently supported : "tcp", "udp") + #rootPathPrefix = "gatling" # The common prefix of all metrics sent to Graphite + #bufferSize = 8192 # GraphiteDataWriter's internal data buffer size, in bytes + #writeInterval = 1 # GraphiteDataWriter's write interval, in seconds + } + } +} diff --git a/jhipster/src/test/gatling/conf/logback.xml b/jhipster/src/test/gatling/conf/logback.xml new file mode 100644 index 0000000000..7b037e6813 --- /dev/null +++ b/jhipster/src/test/gatling/conf/logback.xml @@ -0,0 +1,22 @@ + + + + + + %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx + false + + + + + + + + + + + + + + + diff --git a/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala b/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala new file mode 100644 index 0000000000..93d066f9bb --- /dev/null +++ b/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala @@ -0,0 +1,92 @@ +import _root_.io.gatling.core.scenario.Simulation +import ch.qos.logback.classic.{Level, LoggerContext} +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import org.slf4j.LoggerFactory + +import scala.concurrent.duration._ + +/** + * Performance test for the Comment entity. + */ +class CommentGatlingTest extends Simulation { + + val context: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext] + // Log all HTTP requests + //context.getLogger("io.gatling.http").setLevel(Level.valueOf("TRACE")) + // Log failed HTTP requests + //context.getLogger("io.gatling.http").setLevel(Level.valueOf("DEBUG")) + + val baseURL = Option(System.getProperty("baseURL")) getOrElse """http://127.0.0.1:8080""" + + val httpConf = http + .baseURL(baseURL) + .inferHtmlResources() + .acceptHeader("*/*") + .acceptEncodingHeader("gzip, deflate") + .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3") + .connectionHeader("keep-alive") + .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0") + + val headers_http = Map( + "Accept" -> """application/json""" + ) + + val headers_http_authentication = Map( + "Content-Type" -> """application/json""", + "Accept" -> """application/json""" + ) + + val headers_http_authenticated = Map( + "Accept" -> """application/json""", + "Authorization" -> "${access_token}" + ) + + val scn = scenario("Test the Comment entity") + .exec(http("First unauthenticated request") + .get("/api/account") + .headers(headers_http) + .check(status.is(401))).exitHereIfFailed + .pause(10) + .exec(http("Authentication") + .post("/api/authenticate") + .headers(headers_http_authentication) + .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON + .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed + .pause(1) + .exec(http("Authenticated request") + .get("/api/account") + .headers(headers_http_authenticated) + .check(status.is(200))) + .pause(10) + .repeat(2) { + exec(http("Get all comments") + .get("/api/comments") + .headers(headers_http_authenticated) + .check(status.is(200))) + .pause(10 seconds, 20 seconds) + .exec(http("Create new comment") + .post("/api/comments") + .headers(headers_http_authenticated) + .body(StringBody("""{"id":null, "text":"SAMPLE_TEXT", "creationDate":"2020-01-01T00:00:00.000Z"}""")).asJSON + .check(status.is(201)) + .check(headerRegex("Location", "(.*)").saveAs("new_comment_url"))).exitHereIfFailed + .pause(10) + .repeat(5) { + exec(http("Get created comment") + .get("${new_comment_url}") + .headers(headers_http_authenticated)) + .pause(10) + } + .exec(http("Delete created comment") + .delete("${new_comment_url}") + .headers(headers_http_authenticated)) + .pause(10) + } + + val users = scenario("Users").exec(scn) + + setUp( + users.inject(rampUsers(100) over (1 minutes)) + ).protocols(httpConf) +} diff --git a/jhipster/src/test/gatling/simulations/PostGatlingTest.scala b/jhipster/src/test/gatling/simulations/PostGatlingTest.scala new file mode 100644 index 0000000000..d76198c9ae --- /dev/null +++ b/jhipster/src/test/gatling/simulations/PostGatlingTest.scala @@ -0,0 +1,92 @@ +import _root_.io.gatling.core.scenario.Simulation +import ch.qos.logback.classic.{Level, LoggerContext} +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import org.slf4j.LoggerFactory + +import scala.concurrent.duration._ + +/** + * Performance test for the Post entity. + */ +class PostGatlingTest extends Simulation { + + val context: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext] + // Log all HTTP requests + //context.getLogger("io.gatling.http").setLevel(Level.valueOf("TRACE")) + // Log failed HTTP requests + //context.getLogger("io.gatling.http").setLevel(Level.valueOf("DEBUG")) + + val baseURL = Option(System.getProperty("baseURL")) getOrElse """http://127.0.0.1:8080""" + + val httpConf = http + .baseURL(baseURL) + .inferHtmlResources() + .acceptHeader("*/*") + .acceptEncodingHeader("gzip, deflate") + .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3") + .connectionHeader("keep-alive") + .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0") + + val headers_http = Map( + "Accept" -> """application/json""" + ) + + val headers_http_authentication = Map( + "Content-Type" -> """application/json""", + "Accept" -> """application/json""" + ) + + val headers_http_authenticated = Map( + "Accept" -> """application/json""", + "Authorization" -> "${access_token}" + ) + + val scn = scenario("Test the Post entity") + .exec(http("First unauthenticated request") + .get("/api/account") + .headers(headers_http) + .check(status.is(401))).exitHereIfFailed + .pause(10) + .exec(http("Authentication") + .post("/api/authenticate") + .headers(headers_http_authentication) + .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON + .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed + .pause(1) + .exec(http("Authenticated request") + .get("/api/account") + .headers(headers_http_authenticated) + .check(status.is(200))) + .pause(10) + .repeat(2) { + exec(http("Get all posts") + .get("/api/posts") + .headers(headers_http_authenticated) + .check(status.is(200))) + .pause(10 seconds, 20 seconds) + .exec(http("Create new post") + .post("/api/posts") + .headers(headers_http_authenticated) + .body(StringBody("""{"id":null, "title":"SAMPLE_TEXT", "content":"SAMPLE_TEXT", "creationDate":"2020-01-01T00:00:00.000Z"}""")).asJSON + .check(status.is(201)) + .check(headerRegex("Location", "(.*)").saveAs("new_post_url"))).exitHereIfFailed + .pause(10) + .repeat(5) { + exec(http("Get created post") + .get("${new_post_url}") + .headers(headers_http_authenticated)) + .pause(10) + } + .exec(http("Delete created post") + .delete("${new_post_url}") + .headers(headers_http_authenticated)) + .pause(10) + } + + val users = scenario("Users").exec(scn) + + setUp( + users.inject(rampUsers(100) over (1 minutes)) + ).protocols(httpConf) +} diff --git a/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java b/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java new file mode 100644 index 0000000000..b78a18790b --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.security; + +import org.junit.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +/** +* Test class for the SecurityUtils utility class. +* +* @see SecurityUtils +*/ +public class SecurityUtilsUnitTest { + + @Test + public void testgetCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + String login = SecurityUtils.getCurrentUserLogin(); + assertThat(login).isEqualTo("admin"); + } + + @Test + public void testIsAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isTrue(); + } + + @Test + public void testAnonymousIsNotAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isFalse(); + } +} diff --git a/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java b/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java new file mode 100644 index 0000000000..3fec4bfb88 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java @@ -0,0 +1,107 @@ +package com.baeldung.security.jwt; + +import com.baeldung.security.AuthoritiesConstants; +import io.github.jhipster.config.JHipsterProperties; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TokenProviderTest { + + private final String secretKey = "e5c9ee274ae87bc031adda32e27fa98b9290da83"; + private final long ONE_MINUTE = 60000; + private JHipsterProperties jHipsterProperties; + private TokenProvider tokenProvider; + + @Before + public void setup() { + jHipsterProperties = Mockito.mock(JHipsterProperties.class); + tokenProvider = new TokenProvider(jHipsterProperties); + ReflectionTestUtils.setField(tokenProvider, "secretKey", secretKey); + ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE); + } + + @Test + public void testReturnFalseWhenJWThasInvalidSignature() { + boolean isTokenValid = tokenProvider.validateToken(createTokenWithDifferentSignature()); + + assertThat(isTokenValid).isEqualTo(false); + } + + @Test + public void testReturnFalseWhenJWTisMalformed() { + Authentication authentication = createAuthentication(); + String token = tokenProvider.createToken(authentication, false); + String invalidToken = token.substring(1); + boolean isTokenValid = tokenProvider.validateToken(invalidToken); + + assertThat(isTokenValid).isEqualTo(false); + } + + @Test + public void testReturnFalseWhenJWTisExpired() { + ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", -ONE_MINUTE); + + Authentication authentication = createAuthentication(); + String token = tokenProvider.createToken(authentication, false); + + boolean isTokenValid = tokenProvider.validateToken(token); + + assertThat(isTokenValid).isEqualTo(false); + } + + @Test + public void testReturnFalseWhenJWTisUnsupported() { + Date expirationDate = new Date(new Date().getTime() + ONE_MINUTE); + + Authentication authentication = createAuthentication(); + + String unsupportedToken = createUnsupportedToken(); + + boolean isTokenValid = tokenProvider.validateToken(unsupportedToken); + + assertThat(isTokenValid).isEqualTo(false); + } + + @Test + public void testReturnFalseWhenJWTisInvalid() { + + boolean isTokenValid = tokenProvider.validateToken(""); + + assertThat(isTokenValid).isEqualTo(false); + } + + private Authentication createAuthentication() { + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + return new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities); + } + + private String createUnsupportedToken() { + return Jwts.builder() + .setPayload("payload") + .signWith(SignatureAlgorithm.HS512, secretKey) + .compact(); + } + + private String createTokenWithDifferentSignature() { + return Jwts.builder() + .setSubject("anonymous") + .signWith(SignatureAlgorithm.HS512, "e5c9ee274ae87bc031adda32e27fa98b9290da90") + .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) + .compact(); + } +} diff --git a/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java b/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java new file mode 100644 index 0000000000..968f0a7f08 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java @@ -0,0 +1,129 @@ +package com.baeldung.service; + +import com.baeldung.BaeldungApp; +import com.baeldung.domain.User; +import com.baeldung.config.Constants; +import com.baeldung.repository.UserRepository; +import com.baeldung.service.dto.UserDTO; +import java.time.ZonedDateTime; +import com.baeldung.service.util.RandomUtil; +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.transaction.annotation.Transactional; +import org.springframework.test.context.junit4.SpringRunner; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import java.util.Optional; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +/** + * Test class for the UserResource REST controller. + * + * @see UserService + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +@Transactional +public class UserServiceIntTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @Test + public void assertThatUserMustExistToResetPassword() { + Optional maybeUser = userService.requestPasswordReset("john.doe@localhost"); + assertThat(maybeUser.isPresent()).isFalse(); + + maybeUser = userService.requestPasswordReset("admin@localhost"); + assertThat(maybeUser.isPresent()).isTrue(); + + assertThat(maybeUser.get().getEmail()).isEqualTo("admin@localhost"); + assertThat(maybeUser.get().getResetDate()).isNotNull(); + assertThat(maybeUser.get().getResetKey()).isNotNull(); + } + + @Test + public void assertThatOnlyActivatedUserCanRequestPasswordReset() { + User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US"); + Optional maybeUser = userService.requestPasswordReset("john.doe@localhost"); + assertThat(maybeUser.isPresent()).isFalse(); + userRepository.delete(user); + } + + @Test + public void assertThatResetKeyMustNotBeOlderThan24Hours() { + User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US"); + + ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(25); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + + userRepository.save(user); + + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + + assertThat(maybeUser.isPresent()).isFalse(); + + userRepository.delete(user); + } + + @Test + public void assertThatResetKeyMustBeValid() { + User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US"); + + ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(25); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey("1234"); + userRepository.save(user); + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser.isPresent()).isFalse(); + userRepository.delete(user); + } + + @Test + public void assertThatUserCanResetPassword() { + User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US"); + String oldPassword = user.getPassword(); + ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(2); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + userRepository.save(user); + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser.isPresent()).isTrue(); + assertThat(maybeUser.get().getResetDate()).isNull(); + assertThat(maybeUser.get().getResetKey()).isNull(); + assertThat(maybeUser.get().getPassword()).isNotEqualTo(oldPassword); + + userRepository.delete(user); + } + + @Test + public void testFindNotActivatedUsersByCreationDateBefore() { + userService.removeNotActivatedUsers(); + ZonedDateTime now = ZonedDateTime.now(); + List users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3)); + assertThat(users).isEmpty(); + } + + @Test + public void assertThatAnonymousUserIsNotGet() { + final PageRequest pageable = new PageRequest(0, (int) userRepository.count()); + final Page allManagedUsers = userService.getAllManagedUsers(pageable); + assertThat(allManagedUsers.getContent().stream() + .noneMatch(user -> Constants.ANONYMOUS_USER.equals(user.getLogin()))) + .isTrue(); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java new file mode 100644 index 0000000000..e42ce1c6d4 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java @@ -0,0 +1,395 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; +import com.baeldung.domain.Authority; +import com.baeldung.domain.User; +import com.baeldung.repository.AuthorityRepository; +import com.baeldung.repository.UserRepository; +import com.baeldung.security.AuthoritiesConstants; +import com.baeldung.service.MailService; +import com.baeldung.service.UserService; +import com.baeldung.service.dto.UserDTO; +import com.baeldung.web.rest.vm.ManagedUserVM; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for the AccountResource REST controller. + * + * @see AccountResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class AccountResourceIntTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private AuthorityRepository authorityRepository; + + @Autowired + private UserService userService; + + @Mock + private UserService mockUserService; + + @Mock + private MailService mockMailService; + + private MockMvc restUserMockMvc; + + private MockMvc restMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + doNothing().when(mockMailService).sendActivationEmail(anyObject()); + + AccountResource accountResource = + new AccountResource(userRepository, userService, mockMailService); + + AccountResource accountUserMockResource = + new AccountResource(userRepository, mockUserService, mockMailService); + + this.restMvc = MockMvcBuilders.standaloneSetup(accountResource).build(); + this.restUserMockMvc = MockMvcBuilders.standaloneSetup(accountUserMockResource).build(); + } + + @Test + public void testNonAuthenticatedUser() throws Exception { + restUserMockMvc.perform(get("/api/authenticate") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + public void testAuthenticatedUser() throws Exception { + restUserMockMvc.perform(get("/api/authenticate") + .with(request -> { + request.setRemoteUser("test"); + return request; + }) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("test")); + } + + @Test + public void testGetExistingAccount() throws Exception { + Set authorities = new HashSet<>(); + Authority authority = new Authority(); + authority.setName(AuthoritiesConstants.ADMIN); + authorities.add(authority); + + User user = new User(); + user.setLogin("test"); + user.setFirstName("john"); + user.setLastName("doe"); + user.setEmail("john.doe@jhipster.com"); + user.setImageUrl("http://placehold.it/50x50"); + user.setAuthorities(authorities); + when(mockUserService.getUserWithAuthorities()).thenReturn(user); + + restUserMockMvc.perform(get("/api/account") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.login").value("test")) + .andExpect(jsonPath("$.firstName").value("john")) + .andExpect(jsonPath("$.lastName").value("doe")) + .andExpect(jsonPath("$.email").value("john.doe@jhipster.com")) + .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50")) + .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN)); + } + + @Test + public void testGetUnknownAccount() throws Exception { + when(mockUserService.getUserWithAuthorities()).thenReturn(null); + + restUserMockMvc.perform(get("/api/account") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isInternalServerError()); + } + + @Test + @Transactional + public void testRegisterValid() throws Exception { + ManagedUserVM validUser = new ManagedUserVM( + null, // id + "joe", // login + "password", // password + "Joe", // firstName + "Shmoe", // lastName + "joe@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + Optional user = userRepository.findOneByLogin("joe"); + assertThat(user.isPresent()).isTrue(); + } + + @Test + @Transactional + public void testRegisterInvalidLogin() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM( + null, // id + "funky-log!n", // login <-- invalid + "password", // password + "Funky", // firstName + "One", // lastName + "funky@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + restUserMockMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByEmail("funky@example.com"); + assertThat(user.isPresent()).isFalse(); + } + + @Test + @Transactional + public void testRegisterInvalidEmail() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM( + null, // id + "bob", // login + "password", // password + "Bob", // firstName + "Green", // lastName + "invalid", // e-mail <-- invalid + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + restUserMockMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByLogin("bob"); + assertThat(user.isPresent()).isFalse(); + } + + @Test + @Transactional + public void testRegisterInvalidPassword() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM( + null, // id + "bob", // login + "123", // password with only 3 digits + "Bob", // firstName + "Green", // lastName + "bob@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + restUserMockMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByLogin("bob"); + assertThat(user.isPresent()).isFalse(); + } + + @Test + @Transactional + public void testRegisterDuplicateLogin() throws Exception { + // Good + ManagedUserVM validUser = new ManagedUserVM( + null, // id + "alice", // login + "password", // password + "Alice", // firstName + "Something", // lastName + "alice@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + // Duplicate login, different e-mail + ManagedUserVM duplicatedUser = new ManagedUserVM(validUser.getId(), validUser.getLogin(), validUser.getPassword(), validUser.getLogin(), validUser.getLastName(), + "alicejr@example.com", true, validUser.getImageUrl(), validUser.getLangKey(), validUser.getCreatedBy(), validUser.getCreatedDate(), validUser.getLastModifiedBy(), validUser.getLastModifiedDate(), validUser.getAuthorities()); + + // Good user + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + // Duplicate login + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(duplicatedUser))) + .andExpect(status().is4xxClientError()); + + Optional userDup = userRepository.findOneByEmail("alicejr@example.com"); + assertThat(userDup.isPresent()).isFalse(); + } + + @Test + @Transactional + public void testRegisterDuplicateEmail() throws Exception { + // Good + ManagedUserVM validUser = new ManagedUserVM( + null, // id + "john", // login + "password", // password + "John", // firstName + "Doe", // lastName + "john@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))); + + // Duplicate e-mail, different login + ManagedUserVM duplicatedUser = new ManagedUserVM(validUser.getId(), "johnjr", validUser.getPassword(), validUser.getLogin(), validUser.getLastName(), + validUser.getEmail(), true, validUser.getImageUrl(), validUser.getLangKey(), validUser.getCreatedBy(), validUser.getCreatedDate(), validUser.getLastModifiedBy(), validUser.getLastModifiedDate(), validUser.getAuthorities()); + + // Good user + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + // Duplicate e-mail + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(duplicatedUser))) + .andExpect(status().is4xxClientError()); + + Optional userDup = userRepository.findOneByLogin("johnjr"); + assertThat(userDup.isPresent()).isFalse(); + } + + @Test + @Transactional + public void testRegisterAdminIsIgnored() throws Exception { + ManagedUserVM validUser = new ManagedUserVM( + null, // id + "badguy", // login + "password", // password + "Bad", // firstName + "Guy", // lastName + "badguy@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.ADMIN))); + + restMvc.perform( + post("/api/register") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + Optional userDup = userRepository.findOneByLogin("badguy"); + assertThat(userDup.isPresent()).isTrue(); + assertThat(userDup.get().getAuthorities()).hasSize(1) + .containsExactly(authorityRepository.findOne(AuthoritiesConstants.USER)); + } + + @Test + @Transactional + public void testSaveInvalidLogin() throws Exception { + UserDTO invalidUser = new UserDTO( + null, // id + "funky-log!n", // login <-- invalid + "Funky", // firstName + "One", // lastName + "funky@example.com", // e-mail + true, // activated + "http://placehold.it/50x50", //imageUrl + "en", // langKey + null, // createdBy + null, // createdDate + null, // lastModifiedBy + null, // lastModifiedDate + new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)) + ); + + restUserMockMvc.perform( + post("/api/account") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByEmail("funky@example.com"); + assertThat(user.isPresent()).isFalse(); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java new file mode 100644 index 0000000000..127cb36f07 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java @@ -0,0 +1,147 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; +import com.baeldung.config.audit.AuditEventConverter; +import com.baeldung.domain.PersistentAuditEvent; +import com.baeldung.repository.PersistenceAuditEventRepository; +import com.baeldung.service.AuditEventService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for the AuditResource REST controller. + * + * @see AuditResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +@Transactional +public class AuditResourceIntTest { + + private static final String SAMPLE_PRINCIPAL = "SAMPLE_PRINCIPAL"; + private static final String SAMPLE_TYPE = "SAMPLE_TYPE"; + private static final LocalDateTime SAMPLE_TIMESTAMP = LocalDateTime.parse("2015-08-04T10:11:30"); + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Autowired + private PersistenceAuditEventRepository auditEventRepository; + + @Autowired + private AuditEventConverter auditEventConverter; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private FormattingConversionService formattingConversionService; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + private PersistentAuditEvent auditEvent; + + private MockMvc restAuditMockMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + AuditEventService auditEventService = + new AuditEventService(auditEventRepository, auditEventConverter); + AuditResource auditResource = new AuditResource(auditEventService); + this.restAuditMockMvc = MockMvcBuilders.standaloneSetup(auditResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setConversionService(formattingConversionService) + .setMessageConverters(jacksonMessageConverter).build(); + } + + @Before + public void initTest() { + auditEventRepository.deleteAll(); + auditEvent = new PersistentAuditEvent(); + auditEvent.setAuditEventType(SAMPLE_TYPE); + auditEvent.setPrincipal(SAMPLE_PRINCIPAL); + auditEvent.setAuditEventDate(SAMPLE_TIMESTAMP); + } + + @Test + public void getAllAudits() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Get all the audits + restAuditMockMvc.perform(get("/management/audits")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); + } + + @Test + public void getAudit() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Get the audit + restAuditMockMvc.perform(get("/management/audits/{id}", auditEvent.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.principal").value(SAMPLE_PRINCIPAL)); + } + + @Test + public void getAuditsByDate() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Generate dates for selecting audits by date, making sure the period will contain the audit + String fromDate = SAMPLE_TIMESTAMP.minusDays(1).format(FORMATTER); + String toDate = SAMPLE_TIMESTAMP.plusDays(1).format(FORMATTER); + + // Get the audit + restAuditMockMvc.perform(get("/management/audits?fromDate="+fromDate+"&toDate="+toDate)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); + } + + @Test + public void getNonExistingAuditsByDate() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Generate dates for selecting audits by date, making sure the period will not contain the sample audit + String fromDate = SAMPLE_TIMESTAMP.minusDays(2).format(FORMATTER); + String toDate = SAMPLE_TIMESTAMP.minusDays(1).format(FORMATTER); + + // Query audits but expect no results + restAuditMockMvc.perform(get("/management/audits?fromDate=" + fromDate + "&toDate=" + toDate)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(header().string("X-Total-Count", "0")); + } + + @Test + public void getNonExistingAudit() throws Exception { + // Get the audit + restAuditMockMvc.perform(get("/management/audits/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java new file mode 100644 index 0000000000..04b16b25f8 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java @@ -0,0 +1,279 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; + +import com.baeldung.domain.Comment; +import com.baeldung.domain.Post; +import com.baeldung.repository.CommentRepository; +import com.baeldung.web.rest.errors.ExceptionTranslator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for the CommentResource REST controller. + * + * @see CommentResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class CommentResourceIntTest { + + private static final String DEFAULT_TEXT = "AAAAAAAAAA"; + private static final String UPDATED_TEXT = "BBBBBBBBBB"; + + private static final LocalDate DEFAULT_CREATION_DATE = LocalDate.ofEpochDay(0L); + private static final LocalDate UPDATED_CREATION_DATE = LocalDate.now(ZoneId.systemDefault()); + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + private MockMvc restCommentMockMvc; + + private Comment comment; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + CommentResource commentResource = new CommentResource(commentRepository); + this.restCommentMockMvc = MockMvcBuilders.standaloneSetup(commentResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setMessageConverters(jacksonMessageConverter).build(); + } + + /** + * Create an entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static Comment createEntity(EntityManager em) { + Comment comment = new Comment() + .text(DEFAULT_TEXT) + .creationDate(DEFAULT_CREATION_DATE); + // Add required entity + Post post = PostResourceIntTest.createEntity(em); + em.persist(post); + em.flush(); + comment.setPost(post); + return comment; + } + + @Before + public void initTest() { + comment = createEntity(em); + } + + @Test + @Transactional + public void createComment() throws Exception { + int databaseSizeBeforeCreate = commentRepository.findAll().size(); + + // Create the Comment + restCommentMockMvc.perform(post("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(comment))) + .andExpect(status().isCreated()); + + // Validate the Comment in the database + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeCreate + 1); + Comment testComment = commentList.get(commentList.size() - 1); + assertThat(testComment.getText()).isEqualTo(DEFAULT_TEXT); + assertThat(testComment.getCreationDate()).isEqualTo(DEFAULT_CREATION_DATE); + } + + @Test + @Transactional + public void createCommentWithExistingId() throws Exception { + int databaseSizeBeforeCreate = commentRepository.findAll().size(); + + // Create the Comment with an existing ID + comment.setId(1L); + + // An entity with an existing ID cannot be created, so this API call must fail + restCommentMockMvc.perform(post("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(comment))) + .andExpect(status().isBadRequest()); + + // Validate the Alice in the database + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeCreate); + } + + @Test + @Transactional + public void checkTextIsRequired() throws Exception { + int databaseSizeBeforeTest = commentRepository.findAll().size(); + // set the field null + comment.setText(null); + + // Create the Comment, which fails. + + restCommentMockMvc.perform(post("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(comment))) + .andExpect(status().isBadRequest()); + + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void checkCreationDateIsRequired() throws Exception { + int databaseSizeBeforeTest = commentRepository.findAll().size(); + // set the field null + comment.setCreationDate(null); + + // Create the Comment, which fails. + + restCommentMockMvc.perform(post("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(comment))) + .andExpect(status().isBadRequest()); + + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void getAllComments() throws Exception { + // Initialize the database + commentRepository.saveAndFlush(comment); + + // Get all the commentList + restCommentMockMvc.perform(get("/api/comments?sort=id,desc")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].id").value(hasItem(comment.getId().intValue()))) + .andExpect(jsonPath("$.[*].text").value(hasItem(DEFAULT_TEXT.toString()))) + .andExpect(jsonPath("$.[*].creationDate").value(hasItem(DEFAULT_CREATION_DATE.toString()))); + } + + @Test + @Transactional + public void getComment() throws Exception { + // Initialize the database + commentRepository.saveAndFlush(comment); + + // Get the comment + restCommentMockMvc.perform(get("/api/comments/{id}", comment.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.id").value(comment.getId().intValue())) + .andExpect(jsonPath("$.text").value(DEFAULT_TEXT.toString())) + .andExpect(jsonPath("$.creationDate").value(DEFAULT_CREATION_DATE.toString())); + } + + @Test + @Transactional + public void getNonExistingComment() throws Exception { + // Get the comment + restCommentMockMvc.perform(get("/api/comments/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void updateComment() throws Exception { + // Initialize the database + commentRepository.saveAndFlush(comment); + int databaseSizeBeforeUpdate = commentRepository.findAll().size(); + + // Update the comment + Comment updatedComment = commentRepository.findOne(comment.getId()); + updatedComment + .text(UPDATED_TEXT) + .creationDate(UPDATED_CREATION_DATE); + + restCommentMockMvc.perform(put("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(updatedComment))) + .andExpect(status().isOk()); + + // Validate the Comment in the database + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeUpdate); + Comment testComment = commentList.get(commentList.size() - 1); + assertThat(testComment.getText()).isEqualTo(UPDATED_TEXT); + assertThat(testComment.getCreationDate()).isEqualTo(UPDATED_CREATION_DATE); + } + + @Test + @Transactional + public void updateNonExistingComment() throws Exception { + int databaseSizeBeforeUpdate = commentRepository.findAll().size(); + + // Create the Comment + + // If the entity doesn't have an ID, it will be created instead of just being updated + restCommentMockMvc.perform(put("/api/comments") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(comment))) + .andExpect(status().isCreated()); + + // Validate the Comment in the database + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeUpdate + 1); + } + + @Test + @Transactional + public void deleteComment() throws Exception { + // Initialize the database + commentRepository.saveAndFlush(comment); + int databaseSizeBeforeDelete = commentRepository.findAll().size(); + + // Get the comment + restCommentMockMvc.perform(delete("/api/comments/{id}", comment.getId()) + .accept(TestUtil.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + + // Validate the database is empty + List commentList = commentRepository.findAll(); + assertThat(commentList).hasSize(databaseSizeBeforeDelete - 1); + } + + @Test + @Transactional + public void equalsVerifier() throws Exception { + TestUtil.equalsVerifier(Comment.class); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java new file mode 100644 index 0000000000..92bb976205 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java @@ -0,0 +1,59 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; +import com.baeldung.web.rest.vm.LoggerVM; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Test class for the LogsResource REST controller. + * + * @see LogsResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class LogsResourceIntTest { + + private MockMvc restLogsMockMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + LogsResource logsResource = new LogsResource(); + this.restLogsMockMvc = MockMvcBuilders + .standaloneSetup(logsResource) + .build(); + } + + @Test + public void getAllLogs()throws Exception { + restLogsMockMvc.perform(get("/management/logs")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)); + } + + @Test + public void changeLogs()throws Exception { + LoggerVM logger = new LoggerVM(); + logger.setLevel("INFO"); + logger.setName("ROOT"); + + restLogsMockMvc.perform(put("/management/logs") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(logger))) + .andExpect(status().isNoContent()); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java new file mode 100644 index 0000000000..2a23452711 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java @@ -0,0 +1,306 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; + +import com.baeldung.domain.Post; +import com.baeldung.domain.User; +import com.baeldung.repository.PostRepository; +import com.baeldung.web.rest.errors.ExceptionTranslator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for the PostResource REST controller. + * + * @see PostResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class PostResourceIntTest { + + private static final String DEFAULT_TITLE = "AAAAAAAAAA"; + private static final String UPDATED_TITLE = "BBBBBBBBBB"; + + private static final String DEFAULT_CONTENT = "AAAAAAAAAA"; + private static final String UPDATED_CONTENT = "BBBBBBBBBB"; + + private static final LocalDate DEFAULT_CREATION_DATE = LocalDate.ofEpochDay(0L); + private static final LocalDate UPDATED_CREATION_DATE = LocalDate.now(ZoneId.systemDefault()); + + @Autowired + private PostRepository postRepository; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + private MockMvc restPostMockMvc; + + private Post post; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + PostResource postResource = new PostResource(postRepository); + this.restPostMockMvc = MockMvcBuilders.standaloneSetup(postResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setMessageConverters(jacksonMessageConverter).build(); + } + + /** + * Create an entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static Post createEntity(EntityManager em) { + Post post = new Post() + .title(DEFAULT_TITLE) + .content(DEFAULT_CONTENT) + .creationDate(DEFAULT_CREATION_DATE); + // Add required entity + User creator = UserResourceIntTest.createEntity(em); + em.persist(creator); + em.flush(); + post.setCreator(creator); + return post; + } + + @Before + public void initTest() { + post = createEntity(em); + } + + @Test + @Transactional + public void createPost() throws Exception { + int databaseSizeBeforeCreate = postRepository.findAll().size(); + + // Create the Post + restPostMockMvc.perform(post("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isCreated()); + + // Validate the Post in the database + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeCreate + 1); + Post testPost = postList.get(postList.size() - 1); + assertThat(testPost.getTitle()).isEqualTo(DEFAULT_TITLE); + assertThat(testPost.getContent()).isEqualTo(DEFAULT_CONTENT); + assertThat(testPost.getCreationDate()).isEqualTo(DEFAULT_CREATION_DATE); + } + + @Test + @Transactional + public void createPostWithExistingId() throws Exception { + int databaseSizeBeforeCreate = postRepository.findAll().size(); + + // Create the Post with an existing ID + post.setId(1L); + + // An entity with an existing ID cannot be created, so this API call must fail + restPostMockMvc.perform(post("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isBadRequest()); + + // Validate the Alice in the database + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeCreate); + } + + @Test + @Transactional + public void checkTitleIsRequired() throws Exception { + int databaseSizeBeforeTest = postRepository.findAll().size(); + // set the field null + post.setTitle(null); + + // Create the Post, which fails. + + restPostMockMvc.perform(post("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isBadRequest()); + + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void checkContentIsRequired() throws Exception { + int databaseSizeBeforeTest = postRepository.findAll().size(); + // set the field null + post.setContent(null); + + // Create the Post, which fails. + + restPostMockMvc.perform(post("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isBadRequest()); + + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void checkCreationDateIsRequired() throws Exception { + int databaseSizeBeforeTest = postRepository.findAll().size(); + // set the field null + post.setCreationDate(null); + + // Create the Post, which fails. + + restPostMockMvc.perform(post("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isBadRequest()); + + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void getAllPosts() throws Exception { + // Initialize the database + postRepository.saveAndFlush(post); + + // Get all the postList + restPostMockMvc.perform(get("/api/posts?sort=id,desc")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].id").value(hasItem(post.getId().intValue()))) + .andExpect(jsonPath("$.[*].title").value(hasItem(DEFAULT_TITLE.toString()))) + .andExpect(jsonPath("$.[*].content").value(hasItem(DEFAULT_CONTENT.toString()))) + .andExpect(jsonPath("$.[*].creationDate").value(hasItem(DEFAULT_CREATION_DATE.toString()))); + } + + @Test + @Transactional + public void getPost() throws Exception { + // Initialize the database + postRepository.saveAndFlush(post); + + // Get the post + restPostMockMvc.perform(get("/api/posts/{id}", post.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.id").value(post.getId().intValue())) + .andExpect(jsonPath("$.title").value(DEFAULT_TITLE.toString())) + .andExpect(jsonPath("$.content").value(DEFAULT_CONTENT.toString())) + .andExpect(jsonPath("$.creationDate").value(DEFAULT_CREATION_DATE.toString())); + } + + @Test + @Transactional + public void getNonExistingPost() throws Exception { + // Get the post + restPostMockMvc.perform(get("/api/posts/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void updatePost() throws Exception { + // Initialize the database + postRepository.saveAndFlush(post); + int databaseSizeBeforeUpdate = postRepository.findAll().size(); + + // Update the post + Post updatedPost = postRepository.findOne(post.getId()); + updatedPost + .title(UPDATED_TITLE) + .content(UPDATED_CONTENT) + .creationDate(UPDATED_CREATION_DATE); + + restPostMockMvc.perform(put("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(updatedPost))) + .andExpect(status().isOk()); + + // Validate the Post in the database + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeUpdate); + Post testPost = postList.get(postList.size() - 1); + assertThat(testPost.getTitle()).isEqualTo(UPDATED_TITLE); + assertThat(testPost.getContent()).isEqualTo(UPDATED_CONTENT); + assertThat(testPost.getCreationDate()).isEqualTo(UPDATED_CREATION_DATE); + } + + @Test + @Transactional + public void updateNonExistingPost() throws Exception { + int databaseSizeBeforeUpdate = postRepository.findAll().size(); + + // Create the Post + + // If the entity doesn't have an ID, it will be created instead of just being updated + restPostMockMvc.perform(put("/api/posts") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(post))) + .andExpect(status().isCreated()); + + // Validate the Post in the database + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeUpdate + 1); + } + + @Test + @Transactional + public void deletePost() throws Exception { + // Initialize the database + postRepository.saveAndFlush(post); + int databaseSizeBeforeDelete = postRepository.findAll().size(); + + // Get the post + restPostMockMvc.perform(delete("/api/posts/{id}", post.getId()) + .accept(TestUtil.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + + // Validate the database is empty + List postList = postRepository.findAll(); + assertThat(postList).hasSize(databaseSizeBeforeDelete - 1); + } + + @Test + @Transactional + public void equalsVerifier() throws Exception { + TestUtil.equalsVerifier(Post.class); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java new file mode 100644 index 0000000000..df3544f344 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java @@ -0,0 +1,86 @@ +package com.baeldung.web.rest; + +import io.github.jhipster.config.JHipsterProperties; +import com.baeldung.BaeldungApp; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Test class for the ProfileInfoResource REST controller. + * + * @see ProfileInfoResource + **/ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class ProfileInfoResourceIntTest { + + @Mock + private Environment environment; + + @Mock + private JHipsterProperties jHipsterProperties; + + private MockMvc restProfileMockMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + String mockProfile[] = {"test"}; + JHipsterProperties.Ribbon ribbon = new JHipsterProperties.Ribbon(); + ribbon.setDisplayOnActiveProfiles(mockProfile); + when(jHipsterProperties.getRibbon()).thenReturn(ribbon); + + String activeProfiles[] = {"test"}; + when(environment.getDefaultProfiles()).thenReturn(activeProfiles); + when(environment.getActiveProfiles()).thenReturn(activeProfiles); + + ProfileInfoResource profileInfoResource = new ProfileInfoResource(environment, jHipsterProperties); + this.restProfileMockMvc = MockMvcBuilders + .standaloneSetup(profileInfoResource) + .build(); + } + + @Test + public void getProfileInfoWithRibbon() throws Exception { + restProfileMockMvc.perform(get("/api/profile-info")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)); + } + + @Test + public void getProfileInfoWithoutRibbon() throws Exception { + JHipsterProperties.Ribbon ribbon = new JHipsterProperties.Ribbon(); + ribbon.setDisplayOnActiveProfiles(null); + when(jHipsterProperties.getRibbon()).thenReturn(ribbon); + + restProfileMockMvc.perform(get("/api/profile-info")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)); + } + + @Test + public void getProfileInfoWithoutActiveProfiles() throws Exception { + String emptyProfile[] = {}; + when(environment.getDefaultProfiles()).thenReturn(emptyProfile); + when(environment.getActiveProfiles()).thenReturn(emptyProfile); + + restProfileMockMvc.perform(get("/api/profile-info")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java b/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java new file mode 100644 index 0000000000..64d092fdf1 --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java @@ -0,0 +1,120 @@ +package com.baeldung.web.rest; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.http.MediaType; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Utility class for testing REST controllers. + */ +public class TestUtil { + + /** MediaType for JSON UTF8 */ + public static final MediaType APPLICATION_JSON_UTF8 = new MediaType( + MediaType.APPLICATION_JSON.getType(), + MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); + + /** + * Convert an object to JSON byte array. + * + * @param object + * the object to convert + * @return the JSON byte array + * @throws IOException + */ + public static byte[] convertObjectToJsonBytes(Object object) + throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + JavaTimeModule module = new JavaTimeModule(); + mapper.registerModule(module); + + return mapper.writeValueAsBytes(object); + } + + /** + * Create a byte array with a specific size filled with specified data. + * + * @param size the size of the byte array + * @param data the data to put in the byte array + * @return the JSON byte array + */ + public static byte[] createByteArray(int size, String data) { + byte[] byteArray = new byte[size]; + for (int i = 0; i < size; i++) { + byteArray[i] = Byte.parseByte(data, 2); + } + return byteArray; + } + + /** + * A matcher that tests that the examined string represents the same instant as the reference datetime. + */ + public static class ZonedDateTimeMatcher extends TypeSafeDiagnosingMatcher { + + private final ZonedDateTime date; + + public ZonedDateTimeMatcher(ZonedDateTime date) { + this.date = date; + } + + @Override + protected boolean matchesSafely(String item, Description mismatchDescription) { + try { + if (!date.isEqual(ZonedDateTime.parse(item))) { + mismatchDescription.appendText("was ").appendValue(item); + return false; + } + return true; + } catch (DateTimeParseException e) { + mismatchDescription.appendText("was ").appendValue(item) + .appendText(", which could not be parsed as a ZonedDateTime"); + return false; + } + + } + + @Override + public void describeTo(Description description) { + description.appendText("a String representing the same Instant as ").appendValue(date); + } + } + + /** + * Creates a matcher that matches when the examined string reprensents the same instant as the reference datetime + * @param date the reference datetime against which the examined string is checked + */ + public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) { + return new ZonedDateTimeMatcher(date); + } + + /** + * Verifies the equals/hashcode contract on the domain object. + */ + public static void equalsVerifier(Class clazz) throws Exception { + Object domainObject1 = clazz.getConstructor().newInstance(); + assertThat(domainObject1.toString()).isNotNull(); + assertThat(domainObject1).isEqualTo(domainObject1); + assertThat(domainObject1.hashCode()).isEqualTo(domainObject1.hashCode()); + // Test with an instance of another class + Object testOtherObject = new Object(); + assertThat(domainObject1).isNotEqualTo(testOtherObject); + // Test with an instance of the same class + Object domainObject2 = clazz.getConstructor().newInstance(); + assertThat(domainObject1).isNotEqualTo(domainObject2); + // HashCodes are equals because the objects are not persisted yet + assertThat(domainObject1.hashCode()).isEqualTo(domainObject2.hashCode()); + } +} diff --git a/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java new file mode 100644 index 0000000000..74df23283a --- /dev/null +++ b/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java @@ -0,0 +1,522 @@ +package com.baeldung.web.rest; + +import com.baeldung.BaeldungApp; +import com.baeldung.domain.User; +import com.baeldung.repository.UserRepository; +import com.baeldung.service.MailService; +import com.baeldung.service.UserService; +import com.baeldung.web.rest.errors.ExceptionTranslator; +import com.baeldung.web.rest.vm.ManagedUserVM; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Test class for the UserResource REST controller. + * + * @see UserResource + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BaeldungApp.class) +public class UserResourceIntTest { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final String UPDATED_LOGIN = "jhipster"; + + private static final String DEFAULT_PASSWORD = "passjohndoe"; + private static final String UPDATED_PASSWORD = "passjhipster"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + private static final String UPDATED_EMAIL = "jhipster@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + private static final String UPDATED_FIRSTNAME = "jhipsterFirstName"; + + private static final String DEFAULT_LASTNAME = "doe"; + private static final String UPDATED_LASTNAME = "jhipsterLastName"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + private static final String UPDATED_IMAGEURL = "http://placehold.it/40x40"; + + private static final String DEFAULT_LANGKEY = "en"; + private static final String UPDATED_LANGKEY = "fr"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private MailService mailService; + + @Autowired + private UserService userService; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + private MockMvc restUserMockMvc; + + private User user; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + UserResource userResource = new UserResource(userRepository, mailService, userService); + this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setMessageConverters(jacksonMessageConverter) + .build(); + } + + /** + * Create a User. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which has a required relationship to the User entity. + */ + public static User createEntity(EntityManager em) { + User user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + user.setEmail(DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + return user; + } + + @Before + public void initTest() { + user = createEntity(em); + } + + @Test + @Transactional + public void createUser() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + // Create the User + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + null, + DEFAULT_LOGIN, + DEFAULT_PASSWORD, + DEFAULT_FIRSTNAME, + DEFAULT_LASTNAME, + DEFAULT_EMAIL, + true, + DEFAULT_IMAGEURL, + DEFAULT_LANGKEY, + null, + null, + null, + null, + autorities); + + restUserMockMvc.perform(post("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isCreated()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeCreate + 1); + User testUser = userList.get(userList.size() - 1); + assertThat(testUser.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + } + + @Test + @Transactional + public void createUserWithExistingId() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + 1L, + DEFAULT_LOGIN, + DEFAULT_PASSWORD, + DEFAULT_FIRSTNAME, + DEFAULT_LASTNAME, + DEFAULT_EMAIL, + true, + DEFAULT_IMAGEURL, + DEFAULT_LANGKEY, + null, + null, + null, + null, + autorities); + + // An entity with an existing ID cannot be created, so this API call must fail + restUserMockMvc.perform(post("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeCreate); + } + + @Test + @Transactional + public void createUserWithExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + null, + DEFAULT_LOGIN, // this login should already be used + DEFAULT_PASSWORD, + DEFAULT_FIRSTNAME, + DEFAULT_LASTNAME, + "anothermail@localhost", + true, + DEFAULT_IMAGEURL, + DEFAULT_LANGKEY, + null, + null, + null, + null, + autorities); + + // Create the User + restUserMockMvc.perform(post("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeCreate); + } + + @Test + @Transactional + public void createUserWithExistingEmail() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + null, + "anotherlogin", + DEFAULT_PASSWORD, + DEFAULT_FIRSTNAME, + DEFAULT_LASTNAME, + DEFAULT_EMAIL, // this email should already be used + true, + DEFAULT_IMAGEURL, + DEFAULT_LANGKEY, + null, + null, + null, + null, + autorities); + + // Create the User + restUserMockMvc.perform(post("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeCreate); + } + + @Test + @Transactional + public void getAllUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc.perform(get("/api/users?sort=id,desc") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) + .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME))) + .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME))) + .andExpect(jsonPath("$.[*].email").value(hasItem(DEFAULT_EMAIL))) + .andExpect(jsonPath("$.[*].imageUrl").value(hasItem(DEFAULT_IMAGEURL))) + .andExpect(jsonPath("$.[*].langKey").value(hasItem(DEFAULT_LANGKEY))); + } + + @Test + @Transactional + public void getUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get the user + restUserMockMvc.perform(get("/api/users/{login}", user.getLogin())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.login").value(user.getLogin())) + .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME)) + .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME)) + .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL)) + .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL)) + .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY)); + } + + @Test + @Transactional + public void getNonExistingUser() throws Exception { + restUserMockMvc.perform(get("/api/users/unknown")) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void updateUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findOne(user.getId()); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + updatedUser.getId(), + updatedUser.getLogin(), + UPDATED_PASSWORD, + UPDATED_FIRSTNAME, + UPDATED_LASTNAME, + UPDATED_EMAIL, + updatedUser.getActivated(), + UPDATED_IMAGEURL, + UPDATED_LANGKEY, + updatedUser.getCreatedBy(), + updatedUser.getCreatedDate(), + updatedUser.getLastModifiedBy(), + updatedUser.getLastModifiedDate(), + autorities); + + restUserMockMvc.perform(put("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isOk()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeUpdate); + User testUser = userList.get(userList.size() - 1); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + } + + @Test + @Transactional + public void updateUserLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findOne(user.getId()); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + updatedUser.getId(), + UPDATED_LOGIN, + UPDATED_PASSWORD, + UPDATED_FIRSTNAME, + UPDATED_LASTNAME, + UPDATED_EMAIL, + updatedUser.getActivated(), + UPDATED_IMAGEURL, + UPDATED_LANGKEY, + updatedUser.getCreatedBy(), + updatedUser.getCreatedDate(), + updatedUser.getLastModifiedBy(), + updatedUser.getLastModifiedDate(), + autorities); + + restUserMockMvc.perform(put("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isOk()); + + // Validate the User in the database + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeUpdate); + User testUser = userList.get(userList.size() - 1); + assertThat(testUser.getLogin()).isEqualTo(UPDATED_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + } + + @Test + @Transactional + public void updateUserExistingEmail() throws Exception { + // Initialize the database with 2 users + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.random(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findOne(user.getId()); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + updatedUser.getId(), + updatedUser.getLogin(), + updatedUser.getPassword(), + updatedUser.getFirstName(), + updatedUser.getLastName(), + "jhipster@localhost", // this email should already be used by anotherUser + updatedUser.getActivated(), + updatedUser.getImageUrl(), + updatedUser.getLangKey(), + updatedUser.getCreatedBy(), + updatedUser.getCreatedDate(), + updatedUser.getLastModifiedBy(), + updatedUser.getLastModifiedDate(), + autorities); + + restUserMockMvc.perform(put("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + public void updateUserExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.random(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findOne(user.getId()); + + Set autorities = new HashSet<>(); + autorities.add("ROLE_USER"); + ManagedUserVM managedUserVM = new ManagedUserVM( + updatedUser.getId(), + "jhipster", // this login should already be used by anotherUser + updatedUser.getPassword(), + updatedUser.getFirstName(), + updatedUser.getLastName(), + updatedUser.getEmail(), + updatedUser.getActivated(), + updatedUser.getImageUrl(), + updatedUser.getLangKey(), + updatedUser.getCreatedBy(), + updatedUser.getCreatedDate(), + updatedUser.getLastModifiedBy(), + updatedUser.getLastModifiedDate(), + autorities); + + restUserMockMvc.perform(put("/api/users") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(managedUserVM))) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + public void deleteUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeDelete = userRepository.findAll().size(); + + // Delete the user + restUserMockMvc.perform(delete("/api/users/{login}", user.getLogin()) + .accept(TestUtil.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()); + + // Validate the database is empty + List userList = userRepository.findAll(); + assertThat(userList).hasSize(databaseSizeBeforeDelete - 1); + } + + @Test + @Transactional + public void equalsVerifier() throws Exception { + User userA = new User(); + userA.setLogin("AAA"); + User userB = new User(); + userB.setLogin("BBB"); + assertThat(userA).isNotEqualTo(userB); + } +} diff --git a/jhipster/src/test/javascript/e2e/account/account.spec.ts b/jhipster/src/test/javascript/e2e/account/account.spec.ts new file mode 100644 index 0000000000..cfa9fae078 --- /dev/null +++ b/jhipster/src/test/javascript/e2e/account/account.spec.ts @@ -0,0 +1,108 @@ +import { browser, element, by, $ } from 'protractor'; + +describe('account', () => { + + const username = element(by.id('username')); + const password = element(by.id('password')); + const accountMenu = element(by.id('account-menu')); + const login = element(by.id('login')); + const logout = element(by.id('logout')); + + beforeAll(() => { + browser.get('/'); + }); + + it('should fail to login with bad password', () => { + const expect1 = /home.title/; + element.all(by.css('h1')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + accountMenu.click(); + login.click(); + + username.sendKeys('admin'); + password.sendKeys('foo'); + element(by.css('button[type=submit]')).click(); + + const expect2 = /login.messages.error.authentication/; + element.all(by.css('.alert-danger')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect2); + }); + }); + + it('should login successfully with admin account', () => { + const expect1 = /login.title/; + element.all(by.css('.modal-content h1')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + username.clear(); + username.sendKeys('admin'); + password.clear(); + password.sendKeys('admin'); + element(by.css('button[type=submit]')).click(); + + browser.waitForAngular(); + + const expect2 = /home.logged.message/; + element.all(by.css('.alert-success span')).getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect2); + }); + }); + + it('should be able to update settings', () => { + accountMenu.click(); + element(by.css('[routerLink="settings"]')).click(); + + const expect1 = /settings.title/; + element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + element(by.css('button[type=submit]')).click(); + + const expect2 = /settings.messages.success/; + element.all(by.css('.alert-success')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect2); + }); + }); + + it('should be able to update password', () => { + accountMenu.click(); + element(by.css('[routerLink="password"]')).click(); + + const expect1 = /password.title/; + element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + password.sendKeys('newpassword'); + element(by.id('confirmPassword')).sendKeys('newpassword'); + element(by.css('button[type=submit]')).click(); + + const expect2 = /password.messages.success/; + element.all(by.css('.alert-success')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect2); + }); + accountMenu.click(); + logout.click(); + + accountMenu.click(); + login.click(); + + username.sendKeys('admin'); + password.sendKeys('newpassword'); + element(by.css('button[type=submit]')).click(); + + accountMenu.click(); + element(by.css('[routerLink="password"]')).click(); + // change back to default + password.clear(); + password.sendKeys('admin'); + element(by.id('confirmPassword')).clear(); + element(by.id('confirmPassword')).sendKeys('admin'); + element(by.css('button[type=submit]')).click(); + }); + + afterAll(() => { + accountMenu.click(); + logout.click(); + }); +}); diff --git a/jhipster/src/test/javascript/e2e/admin/administration.spec.ts b/jhipster/src/test/javascript/e2e/admin/administration.spec.ts new file mode 100644 index 0000000000..0516f7a27d --- /dev/null +++ b/jhipster/src/test/javascript/e2e/admin/administration.spec.ts @@ -0,0 +1,80 @@ +import { browser, element, by, $ } from 'protractor'; + +describe('administration', () => { + + const username = element(by.id('username')); + const password = element(by.id('password')); + const accountMenu = element(by.id('account-menu')); + const adminMenu = element(by.id('admin-menu')); + const login = element(by.id('login')); + const logout = element(by.id('logout')); + + beforeAll(() => { + browser.get('/'); + + accountMenu.click(); + login.click(); + + username.sendKeys('admin'); + password.sendKeys('admin'); + element(by.css('button[type=submit]')).click(); + browser.waitForAngular(); + }); + + beforeEach(() => { + adminMenu.click(); + }); + + it('should load user management', () => { + element(by.css('[routerLink="user-management"]')).click(); + const expect1 = /userManagement.home.title/; + element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + it('should load metrics', () => { + element(by.css('[routerLink="jhi-metrics"]')).click(); + const expect1 = /metrics.title/; + element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + it('should load health', () => { + element(by.css('[routerLink="jhi-health"]')).click(); + const expect1 = /health.title/; + element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + it('should load configuration', () => { + element(by.css('[routerLink="jhi-configuration"]')).click(); + const expect1 = /configuration.title/; + element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + it('should load audits', () => { + element(by.css('[routerLink="audits"]')).click(); + const expect1 = /audits.title/; + element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + it('should load logs', () => { + element(by.css('[routerLink="logs"]')).click(); + const expect1 = /logs.title/; + element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expect1); + }); + }); + + afterAll(() => { + accountMenu.click(); + logout.click(); + }); +}); diff --git a/jhipster/src/test/javascript/e2e/entities/comment.spec.ts b/jhipster/src/test/javascript/e2e/entities/comment.spec.ts new file mode 100644 index 0000000000..3032bd1364 --- /dev/null +++ b/jhipster/src/test/javascript/e2e/entities/comment.spec.ts @@ -0,0 +1,49 @@ +import { browser, element, by, $ } from 'protractor'; + +describe('Comment e2e test', () => { + + const username = element(by.id('username')); + const password = element(by.id('password')); + const entityMenu = element(by.id('entity-menu')); + const accountMenu = element(by.id('account-menu')); + const login = element(by.id('login')); + const logout = element(by.id('logout')); + + beforeAll(() => { + browser.get('/'); + + accountMenu.click(); + login.click(); + + username.sendKeys('admin'); + password.sendKeys('admin'); + element(by.css('button[type=submit]')).click(); + browser.waitForAngular(); + }); + + it('should load Comments', () => { + entityMenu.click(); + element.all(by.css('[routerLink="comment"]')).first().click().then(() => { + const expectVal = /baeldungApp.comment.home.title/; + element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expectVal); + }); + }); + }); + + it('should load create Comment dialog', function () { + element(by.css('button.create-comment')).click().then(() => { + const expectVal = /baeldungApp.comment.home.createOrEditLabel/; + element.all(by.css('h4.modal-title')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expectVal); + }); + + element(by.css('button.close')).click(); + }); + }); + + afterAll(function () { + accountMenu.click(); + logout.click(); + }); +}); diff --git a/jhipster/src/test/javascript/e2e/entities/post.spec.ts b/jhipster/src/test/javascript/e2e/entities/post.spec.ts new file mode 100644 index 0000000000..3c8d04f731 --- /dev/null +++ b/jhipster/src/test/javascript/e2e/entities/post.spec.ts @@ -0,0 +1,49 @@ +import { browser, element, by, $ } from 'protractor'; + +describe('Post e2e test', () => { + + const username = element(by.id('username')); + const password = element(by.id('password')); + const entityMenu = element(by.id('entity-menu')); + const accountMenu = element(by.id('account-menu')); + const login = element(by.id('login')); + const logout = element(by.id('logout')); + + beforeAll(() => { + browser.get('/'); + + accountMenu.click(); + login.click(); + + username.sendKeys('admin'); + password.sendKeys('admin'); + element(by.css('button[type=submit]')).click(); + browser.waitForAngular(); + }); + + it('should load Posts', () => { + entityMenu.click(); + element.all(by.css('[routerLink="post"]')).first().click().then(() => { + const expectVal = /baeldungApp.post.home.title/; + element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expectVal); + }); + }); + }); + + it('should load create Post dialog', function () { + element(by.css('button.create-post')).click().then(() => { + const expectVal = /baeldungApp.post.home.createOrEditLabel/; + element.all(by.css('h4.modal-title')).first().getAttribute('jhiTranslate').then((value) => { + expect(value).toMatch(expectVal); + }); + + element(by.css('button.close')).click(); + }); + }); + + afterAll(function () { + accountMenu.click(); + logout.click(); + }); +}); diff --git a/jhipster/src/test/javascript/karma.conf.js b/jhipster/src/test/javascript/karma.conf.js new file mode 100644 index 0000000000..1b10226955 --- /dev/null +++ b/jhipster/src/test/javascript/karma.conf.js @@ -0,0 +1,126 @@ +'use strict'; + +const path = require('path'); +const webpack = require('webpack'); +const WATCH = process.argv.indexOf('--watch') > -1; +const LoaderOptionsPlugin = require("webpack/lib/LoaderOptionsPlugin"); + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: './', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine', 'intl-shim'], + + // list of files / patterns to load in the browser + files: [ + 'spec/entry.ts' + ], + + + // list of files to exclude + exclude: ['e2e/**'], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'spec/entry.ts': ['webpack', 'sourcemap'] + }, + + webpack: { + resolve: { + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts$/, enforce: 'pre', loader: 'tslint-loader', exclude: /(test|node_modules)/ + }, + { + test: /\.ts$/, + loaders: ['awesome-typescript-loader', 'angular2-template-loader?keepUrl=true'], + exclude: /node_modules/ + }, + { + test: /\.(html|css)$/, + loader: 'raw-loader', + exclude: /\.async\.(html|css)$/ + }, + { + test: /\.async\.(html|css)$/, + loaders: ['file?name=[name].[hash].[ext]', 'extract'] + }, + { + test: /\.scss$/, + loaders: ['to-string-loader', 'css-loader', 'sass-loader'] + }, + { + test: /src\/main\/webapp\/.+\.ts$/, + enforce: 'post', + exclude: /(test|node_modules)/, + loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=true' + }] + }, + devtool: 'inline-source-map', + plugins: [ + new webpack.ContextReplacementPlugin( + // The (\\|\/) piece accounts for path separators in *nix and Windows + /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, + root('./src') // location of your src + ), + new LoaderOptionsPlugin({ + options: { + tslint: { + emitErrors: !WATCH, + failOnHint: false + } + } + }) + ] + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['dots', 'junit', 'progress', 'karma-remap-istanbul'], + + junitReporter: { + outputFile: '../../../../target/test-results/karma/TESTS-results.xml' + }, + + remapIstanbulReporter: { + reports: { // eslint-disable-line + 'html': 'target/test-results/coverage', + 'text-summary': null + } + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: WATCH, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: !WATCH + }); +}; + +function root(__path) { + return path.join(__dirname, __path); +} diff --git a/jhipster/src/test/javascript/protractor.conf.js b/jhipster/src/test/javascript/protractor.conf.js new file mode 100644 index 0000000000..e79b09e17b --- /dev/null +++ b/jhipster/src/test/javascript/protractor.conf.js @@ -0,0 +1,48 @@ +var HtmlScreenshotReporter = require("protractor-jasmine2-screenshot-reporter"); +var JasmineReporters = require('jasmine-reporters'); + +exports.config = { + allScriptsTimeout: 20000, + + specs: [ + './e2e/account/*.spec.ts', + './e2e/admin/*.spec.ts', + './e2e/entities/*.spec.ts' + ], + + capabilities: { + 'browserName': 'chrome', + 'phantomjs.binary.path': require('phantomjs-prebuilt').path, + 'phantomjs.ghostdriver.cli.args': ['--loglevel=DEBUG'] + }, + + directConnect: true, + + baseUrl: 'http://localhost:8080/', + + framework: 'jasmine2', + + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000 + }, + + beforeLaunch: function() { + require('ts-node').register({ + project: '' + }); + }, + + onPrepare: function() { + browser.driver.manage().window().setSize(1280, 1024); + jasmine.getEnv().addReporter(new JasmineReporters.JUnitXmlReporter({ + savePath: 'target/reports/e2e', + consolidateAll: false + })); + jasmine.getEnv().addReporter(new HtmlScreenshotReporter({ + dest: "target/reports/e2e/screenshots" + })); + }, + + useAllAngular2AppRoots: true +}; diff --git a/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts new file mode 100644 index 0000000000..76a6e9f941 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts @@ -0,0 +1,84 @@ +import { TestBed, async, tick, fakeAsync, inject } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { BaeldungTestModule } from '../../../test.module'; +import { MockActivatedRoute } from '../../../helpers/mock-route.service'; +import { LoginModalService } from '../../../../../../main/webapp/app/shared'; +import { Activate } from '../../../../../../main/webapp/app/account/activate/activate.service'; +import { ActivateComponent } from '../../../../../../main/webapp/app/account/activate/activate.component'; + +describe('Component Tests', () => { + + describe('ActivateComponent', () => { + + let comp: ActivateComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [ActivateComponent], + providers: [ + Activate, + { + provide: ActivatedRoute, + useValue: new MockActivatedRoute({'key': 'ABC123'}) + }, + { + provide: LoginModalService, + useValue: null + } + ] + }).overrideComponent(ActivateComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + let fixture = TestBed.createComponent(ActivateComponent); + comp = fixture.componentInstance; + }); + + it('calls activate.get with the key from params', + inject([Activate], + fakeAsync((service: Activate) => { + spyOn(service, 'get').and.returnValue(Observable.of()); + + comp.ngOnInit(); + tick(); + + expect(service.get).toHaveBeenCalledWith('ABC123'); + }) + ) + ); + + it('should set set success to OK upon successful activation', + inject([Activate], + fakeAsync((service: Activate) => { + spyOn(service, 'get').and.returnValue(Observable.of({})); + + comp.ngOnInit(); + tick(); + + expect(comp.error).toBe(null); + expect(comp.success).toEqual('OK'); + }) + ) + ); + + it('should set set error to ERROR upon activation failure', + inject([Activate], + fakeAsync((service: Activate) => { + spyOn(service, 'get').and.returnValue(Observable.throw('ERROR')); + + comp.ngOnInit(); + tick(); + + expect(comp.error).toBe('ERROR'); + expect(comp.success).toEqual(null); + }) + ) + ); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts new file mode 100644 index 0000000000..537c58351e --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts @@ -0,0 +1,79 @@ +import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; +import { Renderer, ElementRef } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { LoginModalService } from '../../../../../../../main/webapp/app/shared'; +import { Observable } from 'rxjs/Rx'; +import { BaeldungTestModule } from '../../../../test.module'; +import { PasswordResetFinishComponent } from '../../../../../../../main/webapp/app/account/password-reset/finish/password-reset-finish.component'; +import { PasswordResetFinish } from '../../../../../../../main/webapp/app/account/password-reset/finish/password-reset-finish.service'; +import { MockActivatedRoute } from '../../../../helpers/mock-route.service'; + + +describe('Component Tests', () => { + + describe('PasswordResetFinishComponent', () => { + + let fixture: ComponentFixture; + let comp: PasswordResetFinishComponent; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [PasswordResetFinishComponent], + providers: [ + PasswordResetFinish, + { + provide: LoginModalService, + useValue: null + }, + { + provide: ActivatedRoute, + useValue: new MockActivatedRoute({'key': 'XYZPDQ'}) + }, + { + provide: Renderer, + useValue: { + invokeElementMethod(renderElement: any, methodName: string, args?: any[]) {} + } + }, + { + provide: ElementRef, + useValue: new ElementRef(null) + } + ] + }).overrideComponent(PasswordResetFinishComponent, { + set: { + template: '' + } + }).createComponent(PasswordResetFinishComponent); + comp = fixture.componentInstance; + }); + + it('should define its initial state', function () { + comp.ngOnInit(); + + expect(comp.keyMissing).toBeFalsy(); + expect(comp.key).toEqual('XYZPDQ'); + expect(comp.resetAccount).toEqual({}); + }); + + it('sets focus after the view has been initialized', + inject([ElementRef], (elementRef: ElementRef) => { + let element = fixture.nativeElement; + let node = { + focus() {} + }; + + elementRef.nativeElement = element; + spyOn(element, 'querySelector').and.returnValue(node); + spyOn(node, 'focus'); + + comp.ngAfterViewInit(); + + expect(element.querySelector).toHaveBeenCalledWith('#password'); + expect(node.focus).toHaveBeenCalled(); + }) + ); + + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts new file mode 100644 index 0000000000..55c0a81922 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts @@ -0,0 +1,115 @@ +import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; +import { Renderer, ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { BaeldungTestModule } from '../../../../test.module'; +import { PasswordResetInitComponent } from '../../../../../../../main/webapp/app/account/password-reset/init/password-reset-init.component'; +import { PasswordResetInit } from '../../../../../../../main/webapp/app/account/password-reset/init/password-reset-init.service'; + + +describe('Component Tests', () => { + + describe('PasswordResetInitComponent', function () { + let fixture: ComponentFixture; + let comp: PasswordResetInitComponent; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [PasswordResetInitComponent], + providers: [ + PasswordResetInit, + { + provide: Renderer, + useValue: { + invokeElementMethod(renderElement: any, methodName: string, args?: any[]) {} + } + }, + { + provide: ElementRef, + useValue: new ElementRef(null) + } + ] + }).overrideComponent(PasswordResetInitComponent, { + set: { + template: '' + } + }).createComponent(PasswordResetInitComponent); + comp = fixture.componentInstance; + comp.ngOnInit(); + }); + + it('should define its initial state', function () { + expect(comp.success).toBeUndefined(); + expect(comp.error).toBeUndefined(); + expect(comp.errorEmailNotExists).toBeUndefined(); + expect(comp.resetAccount).toEqual({}); + }); + + it('sets focus after the view has been initialized', + inject([ElementRef], (elementRef: ElementRef) => { + let element = fixture.nativeElement; + let node = { + focus() {} + }; + + elementRef.nativeElement = element; + spyOn(element, 'querySelector').and.returnValue(node); + spyOn(node, 'focus'); + + comp.ngAfterViewInit(); + + expect(element.querySelector).toHaveBeenCalledWith('#email'); + expect(node.focus).toHaveBeenCalled(); + }) + ); + + it('notifies of success upon successful requestReset', + inject([PasswordResetInit], (service: PasswordResetInit) => { + spyOn(service, 'save').and.returnValue(Observable.of({})); + comp.resetAccount.email = 'user@domain.com'; + + comp.requestReset(); + + expect(service.save).toHaveBeenCalledWith('user@domain.com'); + expect(comp.success).toEqual('OK'); + expect(comp.error).toBeNull(); + expect(comp.errorEmailNotExists).toBeNull(); + }) + ); + + it('notifies of unknown email upon e-mail address not registered/400', + inject([PasswordResetInit], (service: PasswordResetInit) => { + spyOn(service, 'save').and.returnValue(Observable.throw({ + status: 400, + data: 'e-mail address not registered' + })); + comp.resetAccount.email = 'user@domain.com'; + + comp.requestReset(); + + expect(service.save).toHaveBeenCalledWith('user@domain.com'); + expect(comp.success).toBeNull(); + expect(comp.error).toBeNull(); + expect(comp.errorEmailNotExists).toEqual('ERROR'); + }) + ); + + it('notifies of error upon error response', + inject([PasswordResetInit], (service: PasswordResetInit) => { + spyOn(service, 'save').and.returnValue(Observable.throw({ + status: 503, + data: 'something else' + })); + comp.resetAccount.email = 'user@domain.com'; + + comp.requestReset(); + + expect(service.save).toHaveBeenCalledWith('user@domain.com'); + expect(comp.success).toBeNull(); + expect(comp.errorEmailNotExists).toBeNull(); + expect(comp.error).toEqual('ERROR'); + }) + ); + + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts new file mode 100644 index 0000000000..9cdc55529c --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts @@ -0,0 +1,53 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; + +import { PasswordStrengthBarComponent } from '../../../../../../main/webapp/app/account/password/password-strength-bar.component'; + +describe('Component Tests', () => { + + describe('PasswordStrengthBarComponent', () => { + + let comp: PasswordStrengthBarComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PasswordStrengthBarComponent] + }).overrideComponent(PasswordStrengthBarComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordStrengthBarComponent); + comp = fixture.componentInstance; + }); + + describe('PasswordStrengthBarComponents', () => { + it('should initialize with default values', () => { + expect(comp.measureStrength('')).toBe(0); + expect(comp.colors).toEqual(['#F00', '#F90', '#FF0', '#9F0', '#0F0']); + expect(comp.getColor(0).idx).toBe(1); + expect(comp.getColor(0).col).toBe(comp.colors[0]); + }); + + it('should increase strength upon password value change', () => { + expect(comp.measureStrength('')).toBe(0); + expect(comp.measureStrength('aa')).toBeGreaterThanOrEqual(comp.measureStrength('')); + expect(comp.measureStrength('aa^6')).toBeGreaterThanOrEqual(comp.measureStrength('aa')); + expect(comp.measureStrength('Aa090(**)')).toBeGreaterThanOrEqual(comp.measureStrength('aa^6')); + expect(comp.measureStrength('Aa090(**)+-07365')).toBeGreaterThanOrEqual(comp.measureStrength('Aa090(**)')); + }); + + it('should change the color based on strength', () => { + expect(comp.getColor(0).col).toBe(comp.colors[0]); + expect(comp.getColor(11).col).toBe(comp.colors[1]); + expect(comp.getColor(22).col).toBe(comp.colors[2]); + expect(comp.getColor(33).col).toBe(comp.colors[3]); + expect(comp.getColor(44).col).toBe(comp.colors[4]); + }); + }); + }); +}); + diff --git a/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts new file mode 100644 index 0000000000..e6f4983785 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts @@ -0,0 +1,92 @@ +import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing'; +import { Observable } from 'rxjs/Rx'; +import { BaeldungTestModule } from '../../../test.module'; +import { PasswordComponent } from '../../../../../../main/webapp/app/account/password/password.component'; +import { Password } from '../../../../../../main/webapp/app/account/password/password.service'; +import { Principal } from '../../../../../../main/webapp/app/shared/auth/principal.service'; +import { AccountService } from '../../../../../../main/webapp/app/shared/auth/account.service'; + + +describe('Component Tests', () => { + + describe('PasswordComponent', () => { + + let comp: PasswordComponent; + let fixture: ComponentFixture; + let service: Password; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [PasswordComponent], + providers: [ + Principal, + AccountService, + Password + ] + }).overrideComponent(PasswordComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(Password); + }); + + it('should show error if passwords do not match', () => { + // GIVEN + comp.password = 'password1'; + comp.confirmPassword = 'password2'; + // WHEN + comp.changePassword(); + // THEN + expect(comp.doNotMatch).toBe('ERROR'); + expect(comp.error).toBeNull(); + expect(comp.success).toBeNull(); + }); + + it('should call Auth.changePassword when passwords match', () => { + // GIVEN + spyOn(service, 'save').and.returnValue(Observable.of(true)); + comp.password = comp.confirmPassword = 'myPassword'; + + // WHEN + comp.changePassword(); + + // THEN + expect(service.save).toHaveBeenCalledWith('myPassword'); + }); + + it('should set success to OK upon success', function() { + // GIVEN + spyOn(service, 'save').and.returnValue(Observable.of(true)); + comp.password = comp.confirmPassword = 'myPassword'; + + // WHEN + comp.changePassword(); + + // THEN + expect(comp.doNotMatch).toBeNull(); + expect(comp.error).toBeNull(); + expect(comp.success).toBe('OK'); + }); + + it('should notify of error if change password fails', function() { + // GIVEN + spyOn(service, 'save').and.returnValue(Observable.throw('ERROR')); + comp.password = comp.confirmPassword = 'myPassword'; + + // WHEN + comp.changePassword(); + + // THEN + expect(comp.doNotMatch).toBeNull(); + expect(comp.success).toBeNull(); + expect(comp.error).toBe('ERROR'); + }); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts new file mode 100644 index 0000000000..c475c2f3d2 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts @@ -0,0 +1,138 @@ +import { ComponentFixture, TestBed, async, inject, tick, fakeAsync } from '@angular/core/testing'; +import { Renderer, ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { JhiLanguageService } from 'ng-jhipster'; +import { MockLanguageService } from '../../../helpers/mock-language.service'; +import { BaeldungTestModule } from '../../../test.module'; +import { LoginModalService } from '../../../../../../main/webapp/app/shared'; +import { Register } from '../../../../../../main/webapp/app/account/register/register.service'; +import { RegisterComponent } from '../../../../../../main/webapp/app/account/register/register.component'; + + +describe('Component Tests', () => { + + describe('RegisterComponent', () => { + let fixture: ComponentFixture; + let comp: RegisterComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [RegisterComponent], + providers: [ + Register, + { + provide: LoginModalService, + useValue: null + }, + { + provide: Renderer, + useValue: null + }, + { + provide: ElementRef, + useValue: null + } + ] + }).overrideComponent(RegisterComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterComponent); + comp = fixture.componentInstance; + comp.ngOnInit(); + }); + + it('should ensure the two passwords entered match', function () { + comp.registerAccount.password = 'password'; + comp.confirmPassword = 'non-matching'; + + comp.register(); + + expect(comp.doNotMatch).toEqual('ERROR'); + }); + + it('should update success to OK after creating an account', + inject([Register, JhiLanguageService], + fakeAsync((service: Register, mockTranslate: MockLanguageService) => { + spyOn(service, 'save').and.returnValue(Observable.of({})); + comp.registerAccount.password = comp.confirmPassword = 'password'; + + comp.register(); + tick(); + + expect(service.save).toHaveBeenCalledWith({ + password: 'password', + langKey: 'en' + }); + expect(comp.success).toEqual(true); + expect(comp.registerAccount.langKey).toEqual('en'); + expect(mockTranslate.getCurrentSpy).toHaveBeenCalled(); + expect(comp.errorUserExists).toBeNull(); + expect(comp.errorEmailExists).toBeNull(); + expect(comp.error).toBeNull(); + }) + ) + ); + + it('should notify of user existence upon 400/login already in use', + inject([Register], + fakeAsync((service: Register) => { + spyOn(service, 'save').and.returnValue(Observable.throw({ + status: 400, + _body: 'login already in use' + })); + comp.registerAccount.password = comp.confirmPassword = 'password'; + + comp.register(); + tick(); + + expect(comp.errorUserExists).toEqual('ERROR'); + expect(comp.errorEmailExists).toBeNull(); + expect(comp.error).toBeNull(); + }) + ) + ); + + it('should notify of email existence upon 400/e-mail address already in use', + inject([Register], + fakeAsync((service: Register) => { + spyOn(service, 'save').and.returnValue(Observable.throw({ + status: 400, + _body: 'e-mail address already in use' + })); + comp.registerAccount.password = comp.confirmPassword = 'password'; + + comp.register(); + tick(); + + expect(comp.errorEmailExists).toEqual('ERROR'); + expect(comp.errorUserExists).toBeNull(); + expect(comp.error).toBeNull(); + }) + ) + ); + + it('should notify of generic error', + inject([Register], + fakeAsync((service: Register) => { + spyOn(service, 'save').and.returnValue(Observable.throw({ + status: 503 + })); + comp.registerAccount.password = comp.confirmPassword = 'password'; + + comp.register(); + tick(); + + expect(comp.errorUserExists).toBeNull(); + expect(comp.errorEmailExists).toBeNull(); + expect(comp.error).toEqual('ERROR'); + }) + ) + ); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts new file mode 100644 index 0000000000..266a33be79 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts @@ -0,0 +1,103 @@ +import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing'; +import { Observable } from 'rxjs/Rx'; +import { JhiLanguageHelper } from '../../../../../../main/webapp/app/shared'; +import { BaeldungTestModule } from '../../../test.module'; +import { Principal, AccountService } from '../../../../../../main/webapp/app/shared'; +import { SettingsComponent } from '../../../../../../main/webapp/app/account/settings/settings.component'; +import { MockAccountService } from '../../../helpers/mock-account.service'; +import { MockPrincipal } from '../../../helpers/mock-principal.service'; + + +describe('Component Tests', () => { + + describe('SettingsComponent', () => { + + let comp: SettingsComponent; + let fixture: ComponentFixture; + let mockAuth: MockAccountService; + let mockPrincipal: MockPrincipal; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [SettingsComponent], + providers: [ + { + provide: Principal, + useClass: MockPrincipal + }, + { + provide: AccountService, + useClass: MockAccountService + }, + { + provide: JhiLanguageHelper, + useValue: null + }, + ] + }).overrideComponent(SettingsComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + comp = fixture.componentInstance; + mockAuth = fixture.debugElement.injector.get(AccountService); + mockPrincipal = fixture.debugElement.injector.get(Principal); + }); + + it('should send the current identity upon save', function () { + // GIVEN + let accountValues = { + firstName: 'John', + lastName: 'Doe', + + activated: true, + email: 'john.doe@mail.com', + langKey: 'en', + login: 'john' + }; + mockPrincipal.setResponse(accountValues); + + // WHEN + comp.settingsAccount = accountValues; + comp.save(); + + // THEN + expect(mockPrincipal.identitySpy).toHaveBeenCalled(); + expect(mockAuth.saveSpy).toHaveBeenCalledWith(accountValues); + expect(comp.settingsAccount).toEqual(accountValues); + }); + + it('should notify of success upon successful save', function () { + // GIVEN + let accountValues = { + firstName: 'John', + lastName: 'Doe' + }; + mockPrincipal.setResponse(accountValues); + + // WHEN + comp.save(); + + // THEN + expect(comp.error).toBeNull(); + expect(comp.success).toBe('OK'); + }); + + it('should notify of error upon failed save', function () { + // GIVEN + mockAuth.saveSpy.and.returnValue(Observable.throw('ERROR')); + + // WHEN + comp.save(); + + // THEN + expect(comp.error).toEqual('ERROR'); + expect(comp.success).toBeNull(); + }); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts b/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts new file mode 100644 index 0000000000..d16673de03 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts @@ -0,0 +1,82 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { DatePipe } from '@angular/common'; +import { NgbPaginationConfig} from '@ng-bootstrap/ng-bootstrap'; +import { ParseLinks } from 'ng-jhipster'; +import { BaeldungTestModule } from '../../../test.module'; +import { PaginationConfig } from '../../../../../../main/webapp/app/blocks/config/uib-pagination.config' +import { AuditsComponent } from '../../../../../../main/webapp/app/admin/audits/audits.component'; +import { AuditsService } from '../../../../../../main/webapp/app/admin/audits/audits.service'; +import { ITEMS_PER_PAGE } from '../../../../../../main/webapp/app/shared'; + + +function getDate(isToday= true){ + let date: Date = new Date(); + if (isToday) { + // Today + 1 day - needed if the current day must be included + date.setDate(date.getDate() + 1); + return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + } + return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; +} + +describe('Component Tests', () => { + + describe('AuditsComponent', () => { + + let comp: AuditsComponent; + let fixture: ComponentFixture; + let service: AuditsService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [AuditsComponent], + providers: [ + AuditsService, + NgbPaginationConfig, + ParseLinks, + PaginationConfig, + DatePipe + ] + }) + .overrideComponent(AuditsComponent, { + set: { + template: '' + } + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AuditsComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(AuditsService); + }); + + describe('today function ', () => { + it('should set toDate to current date', () => { + comp.today(); + expect(comp.toDate).toBe(getDate()); + }); + }); + + describe('previousMonth function ', () => { + it('should set toDate to current date', () => { + comp.previousMonth(); + expect(comp.fromDate).toBe(getDate(false)); + }); + }); + + describe('By default, on init', () => { + it('should set all default values correctly', () => { + fixture.detectChanges(); + expect(comp.toDate).toBe(getDate()); + expect(comp.fromDate).toBe(getDate(false)); + expect(comp.itemsPerPage).toBe(ITEMS_PER_PAGE); + expect(comp.page).toBe(1); + expect(comp.reverse).toBeFalsy(); + expect(comp.orderProp).toBe('timestamp'); + }); + }); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts b/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts new file mode 100644 index 0000000000..b80c96db66 --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts @@ -0,0 +1,295 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { BaeldungTestModule } from '../../../test.module'; +import { JhiHealthCheckComponent } from '../../../../../../main/webapp/app/admin/health/health.component'; +import { JhiHealthService } from '../../../../../../main/webapp/app/admin/health/health.service'; + + +describe('Component Tests', () => { + + describe('JhiHealthCheckComponent', () => { + + let comp: JhiHealthCheckComponent; + let fixture: ComponentFixture; + let service: JhiHealthService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BaeldungTestModule], + declarations: [JhiHealthCheckComponent], + providers: [ + JhiHealthService, + { + provide: NgbModal, + useValue: null + } + ] + }) + .overrideComponent(JhiHealthCheckComponent, { + set: { + template: '' + } + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(JhiHealthCheckComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(JhiHealthService); + }); + + describe('baseName and subSystemName', () => { + it('should return the basename when it has no sub system', () => { + expect(comp.baseName('base')).toBe('base'); + }); + + it('should return the basename when it has sub systems', () => { + expect(comp.baseName('base.subsystem.system')).toBe('base'); + }); + + it('should return the sub system name', () => { + expect(comp.subSystemName('subsystem')).toBe(''); + }); + + it('should return the subsystem when it has multiple keys', () => { + expect(comp.subSystemName('subsystem.subsystem.system')).toBe(' - subsystem.system'); + }); + }); + + describe('transformHealthData', () => { + it('should flatten empty health data', () => { + const data = {}; + const expected = []; + expect(service.transformHealthData(data)).toEqual(expected); + }); + }); + + it('should flatten health data with no subsystems', () => { + const data = { + 'status': 'UP', + 'db': { + 'status': 'UP', + 'database': 'H2', + 'hello': '1' + }, + 'mail': { + 'status': 'UP', + 'error': 'mail.a.b.c' + } + }; + const expected = [ + { + 'name': 'db', + 'error': undefined, + 'status': 'UP', + 'details': { + 'database': 'H2', + 'hello': '1' + } + }, + { + 'name': 'mail', + 'error': 'mail.a.b.c', + 'status': 'UP' + } + ]; + expect(service.transformHealthData(data)).toEqual(expected); + }); + + it('should flatten health data with subsystems at level 1, main system has no additional information', () => { + const data = { + 'status': 'UP', + 'db': { + 'status': 'UP', + 'database': 'H2', + 'hello': '1' + }, + 'mail': { + 'status': 'UP', + 'error': 'mail.a.b.c' + }, + 'system': { + 'status': 'DOWN', + 'subsystem1': { + 'status': 'UP', + 'property1': 'system.subsystem1.property1' + }, + 'subsystem2': { + 'status': 'DOWN', + 'error': 'system.subsystem1.error', + 'property2': 'system.subsystem2.property2' + } + } + }; + const expected = [ + { + 'name': 'db', + 'error': undefined, + 'status': 'UP', + 'details': { + 'database': 'H2', + 'hello': '1' + } + }, + { + 'name': 'mail', + 'error': 'mail.a.b.c', + 'status': 'UP' + }, + { + 'name': 'system.subsystem1', + 'error': undefined, + 'status': 'UP', + 'details': { + 'property1': 'system.subsystem1.property1' + } + }, + { + 'name': 'system.subsystem2', + 'error': 'system.subsystem1.error', + 'status': 'DOWN', + 'details': { + 'property2': 'system.subsystem2.property2' + } + } + ]; + expect(service.transformHealthData(data)).toEqual(expected); + }); + + it('should flatten health data with subsystems at level 1, main system has additional information', () => { + const data = { + 'status': 'UP', + 'db': { + 'status': 'UP', + 'database': 'H2', + 'hello': '1' + }, + 'mail': { + 'status': 'UP', + 'error': 'mail.a.b.c' + }, + 'system': { + 'status': 'DOWN', + 'property1': 'system.property1', + 'subsystem1': { + 'status': 'UP', + 'property1': 'system.subsystem1.property1' + }, + 'subsystem2': { + 'status': 'DOWN', + 'error': 'system.subsystem1.error', + 'property2': 'system.subsystem2.property2' + } + } + }; + const expected = [ + { + 'name': 'db', + 'error': undefined, + 'status': 'UP', + 'details': { + 'database': 'H2', + 'hello': '1' + } + }, + { + 'name': 'mail', + 'error': 'mail.a.b.c', + 'status': 'UP' + }, + { + 'name': 'system', + 'error': undefined, + 'status': 'DOWN', + 'details': { + 'property1': 'system.property1' + } + }, + { + 'name': 'system.subsystem1', + 'error': undefined, + 'status': 'UP', + 'details': { + 'property1': 'system.subsystem1.property1' + } + }, + { + 'name': 'system.subsystem2', + 'error': 'system.subsystem1.error', + 'status': 'DOWN', + 'details': { + 'property2': 'system.subsystem2.property2' + } + } + ]; + expect(service.transformHealthData(data)).toEqual(expected); + }); + + it('should flatten health data with subsystems at level 1, main system has additional error', () => { + const data = { + 'status': 'UP', + 'db': { + 'status': 'UP', + 'database': 'H2', + 'hello': '1' + }, + 'mail': { + 'status': 'UP', + 'error': 'mail.a.b.c' + }, + 'system': { + 'status': 'DOWN', + 'error': 'show me', + 'subsystem1': { + 'status': 'UP', + 'property1': 'system.subsystem1.property1' + }, + 'subsystem2': { + 'status': 'DOWN', + 'error': 'system.subsystem1.error', + 'property2': 'system.subsystem2.property2' + } + } + }; + const expected = [ + { + 'name': 'db', + 'error': undefined, + 'status': 'UP', + 'details': { + 'database': 'H2', + 'hello': '1' + } + }, + { + 'name': 'mail', + 'error': 'mail.a.b.c', + 'status': 'UP' + }, + { + 'name': 'system', + 'error': 'show me', + 'status': 'DOWN' + }, + { + 'name': 'system.subsystem1', + 'error': undefined, + 'status': 'UP', + 'details': { + 'property1': 'system.subsystem1.property1' + } + }, + { + 'name': 'system.subsystem2', + 'error': 'system.subsystem1.error', + 'status': 'DOWN', + 'details': { + 'property2': 'system.subsystem2.property2' + } + } + ]; + expect(service.transformHealthData(data)).toEqual(expected); + }); + }); +}); diff --git a/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts b/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts new file mode 100644 index 0000000000..b7c6b77b8c --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts @@ -0,0 +1,79 @@ +import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing'; +import { MockBackend } from '@angular/http/testing'; +import { Http, BaseRequestOptions } from '@angular/http'; +import { OnInit } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { DateUtils, DataUtils } from 'ng-jhipster'; +import { JhiLanguageService } from 'ng-jhipster'; +import { MockLanguageService } from '../../../helpers/mock-language.service'; +import { MockActivatedRoute } from '../../../helpers/mock-route.service'; +import { CommentDetailComponent } from '../../../../../../main/webapp/app/entities/comment/comment-detail.component'; +import { CommentService } from '../../../../../../main/webapp/app/entities/comment/comment.service'; +import { Comment } from '../../../../../../main/webapp/app/entities/comment/comment.model'; + +describe('Component Tests', () => { + + describe('Comment Management Detail Component', () => { + let comp: CommentDetailComponent; + let fixture: ComponentFixture; + let service: CommentService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CommentDetailComponent], + providers: [ + MockBackend, + BaseRequestOptions, + DateUtils, + DataUtils, + DatePipe, + { + provide: ActivatedRoute, + useValue: new MockActivatedRoute({id: 123}) + }, + { + provide: Http, + useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { + return new Http(backendInstance, defaultOptions); + }, + deps: [MockBackend, BaseRequestOptions] + }, + { + provide: JhiLanguageService, + useClass: MockLanguageService + }, + CommentService + ] + }).overrideComponent(CommentDetailComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentDetailComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(CommentService); + }); + + + describe('OnInit', () => { + it('Should call load all on init', () => { + // GIVEN + + spyOn(service, 'find').and.returnValue(Observable.of(new Comment(10))); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.find).toHaveBeenCalledWith(123); + expect(comp.comment).toEqual(jasmine.objectContaining({id:10})); + }); + }); + }); + +}); diff --git a/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts b/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts new file mode 100644 index 0000000000..3ccb9cf6ad --- /dev/null +++ b/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts @@ -0,0 +1,79 @@ +import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing'; +import { MockBackend } from '@angular/http/testing'; +import { Http, BaseRequestOptions } from '@angular/http'; +import { OnInit } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { DateUtils, DataUtils } from 'ng-jhipster'; +import { JhiLanguageService } from 'ng-jhipster'; +import { MockLanguageService } from '../../../helpers/mock-language.service'; +import { MockActivatedRoute } from '../../../helpers/mock-route.service'; +import { PostDetailComponent } from '../../../../../../main/webapp/app/entities/post/post-detail.component'; +import { PostService } from '../../../../../../main/webapp/app/entities/post/post.service'; +import { Post } from '../../../../../../main/webapp/app/entities/post/post.model'; + +describe('Component Tests', () => { + + describe('Post Management Detail Component', () => { + let comp: PostDetailComponent; + let fixture: ComponentFixture; + let service: PostService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PostDetailComponent], + providers: [ + MockBackend, + BaseRequestOptions, + DateUtils, + DataUtils, + DatePipe, + { + provide: ActivatedRoute, + useValue: new MockActivatedRoute({id: 123}) + }, + { + provide: Http, + useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { + return new Http(backendInstance, defaultOptions); + }, + deps: [MockBackend, BaseRequestOptions] + }, + { + provide: JhiLanguageService, + useClass: MockLanguageService + }, + PostService + ] + }).overrideComponent(PostDetailComponent, { + set: { + template: '' + } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PostDetailComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(PostService); + }); + + + describe('OnInit', () => { + it('Should call load all on init', () => { + // GIVEN + + spyOn(service, 'find').and.returnValue(Observable.of(new Post(10))); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.find).toHaveBeenCalledWith(123); + expect(comp.post).toEqual(jasmine.objectContaining({id:10})); + }); + }); + }); + +}); diff --git a/jhipster/src/test/javascript/spec/entry.ts b/jhipster/src/test/javascript/spec/entry.ts new file mode 100644 index 0000000000..64edbafb93 --- /dev/null +++ b/jhipster/src/test/javascript/spec/entry.ts @@ -0,0 +1,19 @@ +/// +import 'core-js'; +import 'zone.js/dist/zone'; +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/proxy'; +import 'zone.js/dist/jasmine-patch'; +import 'rxjs'; +import 'intl/locale-data/jsonp/en-US.js'; +import { TestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); + +declare let require: any; +const testsContext: any = require.context('./', true, /\.spec/); +testsContext.keys().forEach(testsContext); diff --git a/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts new file mode 100644 index 0000000000..e21c10a370 --- /dev/null +++ b/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts @@ -0,0 +1,26 @@ +import { SpyObject } from './spyobject'; +import { AccountService } from '../../../../main/webapp/app/shared/auth/account.service'; +import Spy = jasmine.Spy; + +export class MockAccountService extends SpyObject { + + getSpy: Spy; + saveSpy: Spy; + fakeResponse: any; + + constructor() { + super(AccountService); + + this.fakeResponse = null; + this.getSpy = this.spy('get').andReturn(this); + this.saveSpy = this.spy('save').andReturn(this); + } + + subscribe(callback: any) { + callback(this.fakeResponse); + } + + setResponse(json: any): void { + this.fakeResponse = json; + } +} diff --git a/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts new file mode 100644 index 0000000000..0c7dec92f1 --- /dev/null +++ b/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts @@ -0,0 +1,26 @@ +import { SpyObject } from './spyobject'; +import { JhiLanguageService } from 'ng-jhipster'; +import Spy = jasmine.Spy; + +export class MockLanguageService extends SpyObject { + + getCurrentSpy: Spy; + fakeResponse: any; + + constructor() { + super(JhiLanguageService); + + this.fakeResponse = 'en'; + this.getCurrentSpy = this.spy('getCurrent').andReturn(Promise.resolve(this.fakeResponse)); + } + + init() {} + + changeLanguage(languageKey: string) {} + + setLocations(locations: string[]) {} + + addLocation(location: string) {} + + reload() {} +} diff --git a/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts new file mode 100644 index 0000000000..89b932b83c --- /dev/null +++ b/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts @@ -0,0 +1,20 @@ +import { SpyObject } from './spyobject'; +import { Principal } from '../../../../main/webapp/app/shared/auth/principal.service'; +import Spy = jasmine.Spy; + +export class MockPrincipal extends SpyObject { + + identitySpy: Spy; + fakeResponse: any; + + constructor() { + super(Principal); + + this.fakeResponse = {}; + this.identitySpy = this.spy('identity').andReturn(Promise.resolve(this.fakeResponse)); + } + + setResponse(json: any): void { + this.fakeResponse = json; + } +} diff --git a/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts new file mode 100644 index 0000000000..3ddb291721 --- /dev/null +++ b/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts @@ -0,0 +1,15 @@ +import { ActivatedRoute, Params } from '@angular/router'; +import { Observable } from 'rxjs'; + +export class MockActivatedRoute extends ActivatedRoute { + + constructor(parameters?: any) { + super(); + this.queryParams = Observable.of(parameters); + this.params = Observable.of(parameters); + } +} + +export class MockRouter { + navigate = jasmine.createSpy('navigate'); +} diff --git a/jhipster/src/test/javascript/spec/helpers/spyobject.ts b/jhipster/src/test/javascript/spec/helpers/spyobject.ts new file mode 100644 index 0000000000..4db41fb8df --- /dev/null +++ b/jhipster/src/test/javascript/spec/helpers/spyobject.ts @@ -0,0 +1,69 @@ +export interface GuinessCompatibleSpy extends jasmine.Spy { + /** By chaining the spy with and.returnValue, all calls to the function will return a specific + * value. */ + andReturn(val: any): void; + /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied + * function. */ + andCallFake(fn: Function): GuinessCompatibleSpy; + /** removes all recorded calls */ + reset(); +} + +export class SpyObject { + static stub(object = null, config = null, overrides = null) { + if (!(object instanceof SpyObject)) { + overrides = config; + config = object; + object = new SpyObject(); + } + + let m = {}; + Object.keys(config).forEach((key) => m[key] = config[key]); + Object.keys(overrides).forEach((key) => m[key] = overrides[key]); + Object.keys(m).forEach((key) => { + object.spy(key).andReturn(m[key]); + }); + return object; + } + + constructor(type = null) { + if (type) { + Object.keys(type.prototype).forEach((prop) => { + let m = null; + try { + m = type.prototype[prop]; + } catch (e) { + // As we are creating spys for abstract classes, + // these classes might have getters that throw when they are accessed. + // As we are only auto creating spys for methods, this + // should not matter. + } + if (typeof m === 'function') { + this.spy(prop); + } + }); + } + } + + spy(name) { + if (!this[name]) { + this[name] = this._createGuinnessCompatibleSpy(name); + } + return this[name]; + } + + prop(name, value) { + this[name] = value; + } + + /** @internal */ + _createGuinnessCompatibleSpy(name): GuinessCompatibleSpy { + let newSpy: GuinessCompatibleSpy = < any > jasmine.createSpy(name); + newSpy.andCallFake = < any > newSpy.and.callFake; + newSpy.andReturn = < any > newSpy.and.returnValue; + newSpy.reset = < any > newSpy.calls.reset; + // revisit return null here (previously needed for rtts_assert). + newSpy.and.returnValue(null); + return newSpy; + } +} diff --git a/jhipster/src/test/javascript/spec/test.module.ts b/jhipster/src/test/javascript/spec/test.module.ts new file mode 100644 index 0000000000..65ce439cb0 --- /dev/null +++ b/jhipster/src/test/javascript/spec/test.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { MockBackend } from '@angular/http/testing'; +import { Http, BaseRequestOptions } from '@angular/http'; +import { JhiLanguageService } from 'ng-jhipster'; +import { MockLanguageService } from './helpers/mock-language.service'; + +@NgModule({ + providers: [ + MockBackend, + BaseRequestOptions, + { + provide: JhiLanguageService, + useClass: MockLanguageService + }, + { + provide: Http, + useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { + return new Http(backendInstance, defaultOptions); + }, + deps: [MockBackend, BaseRequestOptions] + } + ] +}) +export class BaeldungTestModule {} diff --git a/jhipster/src/test/resources/config/application.yml b/jhipster/src/test/resources/config/application.yml new file mode 100644 index 0000000000..a7939c838c --- /dev/null +++ b/jhipster/src/test/resources/config/application.yml @@ -0,0 +1,96 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests. +# +# More information on profiles: https://jhipster.github.io/profiles/ +# More information on configuration properties: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + + +spring: + application: + name: baeldung + jackson: + serialization.write_dates_as_timestamps: false + cache: + type: none + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:h2:mem:baeldung;DB_CLOSE_DELAY=-1 + name: + username: + password: + jpa: + database-platform: io.github.jhipster.domain.util.FixedH2Dialect + database: H2 + open-in-view: false + show-sql: true + hibernate: + ddl-auto: none + naming: + physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + properties: + hibernate.id.new_generator_mappings: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: true + hibernate.hbm2ddl.auto: validate + mail: + host: localhost + messages: + basename: i18n/messages + mvc: + favicon: + enabled: false + thymeleaf: + mode: XHTML + +liquibase: + contexts: test + +security: + basic: + enabled: false + +server: + port: 10344 + address: localhost + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://jhipster.github.io/common-application-properties/ +# =================================================================== + +jhipster: + async: + core-pool-size: 2 + max-pool-size: 50 + queue-capacity: 10000 + security: + authentication: + jwt: + secret: e1d4b69d3f953e3fa622121e882e6f459ca20ca4 + # Token is valid 24 hours + token-validity-in-seconds: 86400 + metrics: # DropWizard Metrics configuration, used by MetricsConfiguration + jmx.enabled: true + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://jhipster.github.io/common-application-properties/ +# =================================================================== + +application: diff --git a/jhipster/src/test/resources/logback-test.xml b/jhipster/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..c0acd00401 --- /dev/null +++ b/jhipster/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/jhipster/tsconfig.json b/jhipster/tsconfig.json new file mode 100644 index 0000000000..354ae048ad --- /dev/null +++ b/jhipster/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": false, + "suppressImplicitAnyIndexErrors": true, + "outDir": "target/www/app", + "lib": ["es6", "dom"], + "typeRoots": [ + "node_modules/@types" + ] + }, + "include": [ + "src/main/webapp/app", + "src/test/javascript" + ] +} \ No newline at end of file diff --git a/jhipster/tslint.json b/jhipster/tslint.json new file mode 100644 index 0000000000..ee6491cf69 --- /dev/null +++ b/jhipster/tslint.json @@ -0,0 +1,107 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + + "directive-selector": [true, "attribute", "jhi", "camelCase"], + "component-selector": [true, "element", "jhi", "kebab-case"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": false, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +} diff --git a/jhipster/webpack/webpack.common.js b/jhipster/webpack/webpack.common.js new file mode 100644 index 0000000000..4916bb2db5 --- /dev/null +++ b/jhipster/webpack/webpack.common.js @@ -0,0 +1,117 @@ +const webpack = require('webpack'); +const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const StringReplacePlugin = require('string-replace-webpack-plugin'); +const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); +const path = require('path'); + +module.exports = function (options) { + const DATAS = { + VERSION: JSON.stringify(require("../package.json").version), + DEBUG_INFO_ENABLED: options.env === 'dev' + }; + return { + entry: { + 'polyfills': './src/main/webapp/app/polyfills', + 'global': './src/main/webapp/content/scss/global.scss', + 'main': './src/main/webapp/app/app.main' + }, + resolve: { + extensions: ['.ts', '.js'], + modules: ['node_modules'] + }, + module: { + rules: [ + { test: /bootstrap\/dist\/js\/umd\//, loader: 'imports-loader?jQuery=jquery' }, + { + test: /\.ts$/, + loaders: [ + 'angular2-template-loader', + 'awesome-typescript-loader' + ], + exclude: ['node_modules/generator-jhipster'] + }, + { + test: /\.html$/, + loader: 'raw-loader', + exclude: ['./src/main/webapp/index.html'] + }, + { + test: /\.scss$/, + loaders: ['to-string-loader', 'css-loader', 'sass-loader'], + exclude: /(vendor\.scss|global\.scss)/ + }, + { + test: /(vendor\.scss|global\.scss)/, + loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] + }, + { + test: /\.css$/, + loaders: ['to-string-loader', 'css-loader'], + exclude: /(vendor\.css|global\.css)/ + }, + { + test: /(vendor\.css|global\.css)/, + loaders: ['style-loader', 'css-loader'] + }, + { + test: /\.(jpe?g|png|gif|svg|woff|woff2|ttf|eot)$/i, + loaders: [ + 'file-loader?hash=sha512&digest=hex&name=[hash].[ext]', { + loader: 'image-webpack-loader', + query: { + gifsicle: { + interlaced: false + }, + optipng: { + optimizationLevel: 7 + } + } + } + ] + }, + { + test: /app.constants.ts$/, + loader: StringReplacePlugin.replace({ + replacements: [{ + pattern: /\/\* @toreplace (\w*?) \*\//ig, + replacement: function (match, p1, offset, string) { + return `_${p1} = ${DATAS[p1]};`; + } + }] + }) + } + ] + }, + plugins: [ + new CommonsChunkPlugin({ + names: ['manifest', 'polyfills'].reverse() + }), + new webpack.DllReferencePlugin({ + context: './', + manifest: require(path.resolve('./target/www/vendor.json')), + }), + new CopyWebpackPlugin([ + { from: './node_modules/swagger-ui/dist', to: 'swagger-ui/dist' }, + { from: './src/main/webapp/swagger-ui/', to: 'swagger-ui' }, + { from: './src/main/webapp/favicon.ico', to: 'favicon.ico' }, + { from: './src/main/webapp/robots.txt', to: 'robots.txt' }, + { from: './src/main/webapp/i18n', to: 'i18n' } + ]), + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery" + }), + new HtmlWebpackPlugin({ + template: './src/main/webapp/index.html', + chunksSortMode: 'dependency', + inject: 'body' + }), + new AddAssetHtmlPlugin([ + { filepath: path.resolve('./target/www/vendor.dll.js'), includeSourcemap: false } + ]), + new StringReplacePlugin() + ] + }; +}; diff --git a/jhipster/webpack/webpack.dev.js b/jhipster/webpack/webpack.dev.js new file mode 100644 index 0000000000..f612a44b09 --- /dev/null +++ b/jhipster/webpack/webpack.dev.js @@ -0,0 +1,65 @@ +const webpack = require('webpack'); +const path = require('path'); +const commonConfig = require('./webpack.common.js'); +const writeFilePlugin = require('write-file-webpack-plugin'); +const webpackMerge = require('webpack-merge'); +const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); +const ExtractTextPlugin = require("extract-text-webpack-plugin"); +const ENV = 'dev'; +const execSync = require('child_process').execSync; +const fs = require('fs'); +const ddlPath = './target/www/vendor.json'; + +if (!fs.existsSync(ddlPath)) { + execSync('webpack --config webpack/webpack.vendor.js'); +} + +module.exports = webpackMerge(commonConfig({ env: ENV }), { + devtool: 'inline-source-map', + devServer: { + contentBase: './target/www', + proxy: [{ + context: [ + '/api', + '/management', + '/swagger-resources', + '/v2/api-docs', + '/h2-console' + ], + target: 'http://127.0.0.1:8080', + secure: false + }] + }, + output: { + path: path.resolve('target/www'), + filename: '[name].bundle.js', + chunkFilename: '[id].chunk.js' + }, + module: { + rules: [{ + test: /\.ts$/, + loaders: [ + 'tslint-loader' + ], + exclude: ['node_modules', new RegExp('reflect-metadata\\' + path.sep + 'Reflect\\.ts')] + }] + }, + plugins: [ + new BrowserSyncPlugin({ + host: 'localhost', + port: 9000, + proxy: { + target: 'http://localhost:9060' + } + }, { + reload: false + }), + new ExtractTextPlugin('styles.css'), + new webpack.NoEmitOnErrorsPlugin(), + new webpack.NamedModulesPlugin(), + new writeFilePlugin(), + new webpack.WatchIgnorePlugin([ + path.resolve('./src/test'), + ]) + ] +}); diff --git a/jhipster/webpack/webpack.prod.js b/jhipster/webpack/webpack.prod.js new file mode 100644 index 0000000000..28f0f2152b --- /dev/null +++ b/jhipster/webpack/webpack.prod.js @@ -0,0 +1,22 @@ +const commonConfig = require('./webpack.common.js'); +const webpackMerge = require('webpack-merge'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const ExtractTextPlugin = require("extract-text-webpack-plugin"); +const Visualizer = require('webpack-visualizer-plugin'); +const ENV = 'prod'; + +module.exports = webpackMerge(commonConfig({ env: ENV }), { + devtool: 'source-map', + output: { + path: './target/www', + filename: '[hash].[name].bundle.js', + chunkFilename: '[hash].[id].chunk.js' + }, + plugins: [ + new ExtractTextPlugin('[hash].styles.css'), + new Visualizer({ + // Webpack statistics in target folder + filename: '../stats.html' + }) + ] +}); diff --git a/jhipster/webpack/webpack.vendor.js b/jhipster/webpack/webpack.vendor.js new file mode 100644 index 0000000000..449024d102 --- /dev/null +++ b/jhipster/webpack/webpack.vendor.js @@ -0,0 +1,63 @@ +var webpack = require('webpack'); +module.exports = { + entry: { + 'vendor': [ + './src/main/webapp/app/vendor', + '@angular/common', + '@angular/compiler', + '@angular/core', + '@angular/forms', + '@angular/http', + '@angular/platform-browser', + '@angular/platform-browser-dynamic', + '@angular/router', + '@ng-bootstrap/ng-bootstrap', + 'angular2-cookie', + 'angular2-infinite-scroll', + 'jquery', + 'ng-jhipster', + 'ng2-webstorage', + 'rxjs' + ] + }, + resolve: { + extensions: ['.ts', '.js'], + modules: ['node_modules'] + }, + module: { + exprContextCritical: false, + rules: [ + { + test: /(vendor\.scss|global\.scss)/, + loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] + }, + { + test: /\.(jpe?g|png|gif|svg|woff|woff2|ttf|eot)$/i, + loaders: [ + 'file-loader?hash=sha512&digest=hex&name=[hash].[ext]', { + loader: 'image-webpack-loader', + query: { + gifsicle: { + interlaced: false + }, + optipng: { + optimizationLevel: 7 + } + } + } + ] + } + ] + }, + output: { + filename: '[name].dll.js', + path: './target/www', + library: '[name]' + }, + plugins: [ + new webpack.DllPlugin({ + name: '[name]', + path: './target/www/[name].json' + }) + ] +}; diff --git a/mockito2/src/test/java/com/baeldung/mockito/java8/ArgumentMatcherWithoutLambdaUnitTest.java b/mockito2/src/test/java/com/baeldung/mockito/java8/ArgumentMatcherWithoutLambdaUnitTest.java index 786062ee57..aaa8d03585 100644 --- a/mockito2/src/test/java/com/baeldung/mockito/java8/ArgumentMatcherWithoutLambdaUnitTest.java +++ b/mockito2/src/test/java/com/baeldung/mockito/java8/ArgumentMatcherWithoutLambdaUnitTest.java @@ -13,6 +13,16 @@ import static org.mockito.Mockito.when; public class ArgumentMatcherWithoutLambdaUnitTest { + private class PeterArgumentMatcher implements ArgumentMatcher { + + @Override + public boolean matches(Person p) { + return p + .getName() + .equals("Peter"); + } + } + @InjectMocks private UnemploymentServiceImpl unemploymentService; @@ -34,16 +44,6 @@ public class ArgumentMatcherWithoutLambdaUnitTest { assertFalse(unemploymentService.personIsEntitledToUnemploymentSupport(peter)); } - private class PeterArgumentMatcher implements ArgumentMatcher { - - @Override - public boolean matches(Person p) { - return p - .getName() - .equals("Peter"); - } - } - @Before public void init() { MockitoAnnotations.initMocks(this); diff --git a/mockito2/src/test/java/com/baeldung/mockito/java8/CustomAnswerWithoutLambdaUnitTest.java b/mockito2/src/test/java/com/baeldung/mockito/java8/CustomAnswerWithoutLambdaUnitTest.java index 9d1aa3a3c0..d5b9d6d1ce 100644 --- a/mockito2/src/test/java/com/baeldung/mockito/java8/CustomAnswerWithoutLambdaUnitTest.java +++ b/mockito2/src/test/java/com/baeldung/mockito/java8/CustomAnswerWithoutLambdaUnitTest.java @@ -17,7 +17,21 @@ import static org.mockito.Mockito.when; public class CustomAnswerWithoutLambdaUnitTest { + + private class PersonAnswer implements Answer> { + @Override + public Stream answer(InvocationOnMock invocation) throws Throwable { + Person person = invocation.getArgument(0); + + if(person.getName().equals("Peter")) { + return Stream.builder().add(new JobPosition("Teacher")).build(); + } + + return Stream.empty(); + } + } + @InjectMocks private UnemploymentServiceImpl unemploymentService; @@ -37,17 +51,6 @@ public class CustomAnswerWithoutLambdaUnitTest { assertFalse(unemploymentService.searchJob(linda, "").isPresent()); } - - private class PersonAnswer implements Answer> { - - @Override - public Stream answer(InvocationOnMock invocation) throws Throwable { - Person person = invocation.getArgument(0); - - return Stream.of(new JobPosition("Teacher")) - .filter(p -> person.getName().equals("Peter")); - } - } @Before public void init() { diff --git a/pom.xml b/pom.xml index e0556a7c50..bc89f839eb 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,7 @@ javaxval jaxb jee7 + jhipster jjwt jooq jpa-storedprocedure From faea5eb5109ae8ea3606588988d46c397a56629b Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Tue, 21 Mar 2017 16:49:29 +0100 Subject: [PATCH 054/149] Refactor Javaslang samples (#1469) --- javaslang/pom.xml | 2 +- .../baeldung/javaslang/PropertyBasedTest.java | 70 ++++++++++--------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/javaslang/pom.xml b/javaslang/pom.xml index 7bb23c0daf..941aac0802 100644 --- a/javaslang/pom.xml +++ b/javaslang/pom.xml @@ -38,7 +38,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.3 + 3.5.1 1.8 1.8 diff --git a/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java index 3acac34550..43f3d6e6a0 100644 --- a/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java +++ b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java @@ -1,6 +1,5 @@ package com.baeldung.javaslang; - import javaslang.CheckedFunction1; import javaslang.collection.Stream; import javaslang.test.Arbitrary; @@ -8,42 +7,42 @@ import javaslang.test.CheckResult; import javaslang.test.Property; import org.junit.Test; +import java.util.function.Predicate; + +import static javaslang.API.*; + public class PropertyBasedTest { - public Stream stringsSupplier() { - return Stream.from(0).map(i -> { - boolean divByTwo = i % 2 == 0; - boolean divByFive = i % 5 == 0; + private static Predicate divisibleByTwo = i -> i % 2 == 0; + private static Predicate divisibleByFive = i -> i % 5 == 0; - if(divByFive && divByTwo){ - return "DividedByTwoAndFiveWithoutRemainder"; - }else if(divByFive){ - return "DividedByFiveWithoutRemainder"; - }else if(divByTwo){ - return "DividedByTwoWithoutRemainder"; - } - return ""; - }); + private Stream stringsSupplier() { + return Stream.from(0).map(i -> Match(i).of( + Case($(divisibleByFive.and(divisibleByTwo)), "DividedByTwoAndFiveWithoutRemainder"), + Case($(divisibleByFive), "DividedByFiveWithoutRemainder"), + Case($(divisibleByTwo), "DividedByTwoWithoutRemainder"), + Case($(), ""))); } @Test public void givenArbitrarySeq_whenCheckThatEverySecondElementIsEqualToString_thenTestPass() { //given - Arbitrary multiplesOf2 = Arbitrary.integer() - .filter(i -> i > 0) - .filter(i -> i % 2 == 0 && i % 5 != 0); + Arbitrary multiplesOf2 = Arbitrary + .integer() + .filter(i -> i > 0) + .filter(i -> i % 2 == 0 && i % 5 != 0); //when - CheckedFunction1 mustEquals = - i -> stringsSupplier().get(i).equals("DividedByTwoWithoutRemainder"); - + CheckedFunction1 mustEquals = i -> stringsSupplier() + .get(i) + .equals("DividedByTwoWithoutRemainder"); //then CheckResult result = Property - .def("Every second element must equal to DividedByTwoWithoutRemainder") - .forAll(multiplesOf2) - .suchThat(mustEquals) - .check(10_000, 100); + .def("Every second element must equal to DividedByTwoWithoutRemainder") + .forAll(multiplesOf2) + .suchThat(mustEquals) + .check(10_000, 100); result.assertIsSatisfied(); } @@ -51,19 +50,22 @@ public class PropertyBasedTest { @Test public void givenArbitrarySeq_whenCheckThatEveryFifthElementIsEqualToString_thenTestPass() { //given - Arbitrary multiplesOf5 = Arbitrary.integer() - .filter(i -> i > 0) - .filter(i -> i % 5 == 0 && i % 2 == 0); + Arbitrary multiplesOf5 = Arbitrary + .integer() + .filter(i -> i > 0) + .filter(i -> i % 5 == 0 && i % 2 == 0); //when - CheckedFunction1 mustEquals = i -> - stringsSupplier().get(i).endsWith("DividedByTwoAndFiveWithoutRemainder"); + CheckedFunction1 mustEquals = i -> stringsSupplier() + .get(i) + .endsWith("DividedByTwoAndFiveWithoutRemainder"); //then - Property.def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder") - .forAll(multiplesOf5) - .suchThat(mustEquals) - .check(10_000, 1_000) - .assertIsSatisfied(); + Property + .def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder") + .forAll(multiplesOf5) + .suchThat(mustEquals) + .check(10_000, 1_000) + .assertIsSatisfied(); } } From eab4a5f8ca47df80b1587069fccf76ac214e41b0 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Tue, 21 Mar 2017 17:10:30 +0100 Subject: [PATCH 055/149] Move custom filter examples (#1465) * Move custom filter examples * Remove unused README entry --- spring-security-basic-auth/README.md | 1 - .../src/main/resources/webSecurityConfig.xml | 5 ---- spring-security-rest-basic-auth/README.md | 1 + .../org/baeldung}/filter/CustomFilter.java | 2 +- .../CustomWebSecurityConfigurerAdapter.java | 24 +++++++++++++------ 5 files changed, 19 insertions(+), 14 deletions(-) rename {spring-security-basic-auth/src/main/java/org/baeldung/security => spring-security-rest-basic-auth/src/main/java/org/baeldung}/filter/CustomFilter.java (92%) rename {spring-security-basic-auth/src/main/java/org/baeldung/security/filter/configuration => spring-security-rest-basic-auth/src/main/java/org/baeldung/filter}/CustomWebSecurityConfigurerAdapter.java (63%) diff --git a/spring-security-basic-auth/README.md b/spring-security-basic-auth/README.md index 8aa299f8cc..ebb404063f 100644 --- a/spring-security-basic-auth/README.md +++ b/spring-security-basic-auth/README.md @@ -7,7 +7,6 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com ### Relevant Article: - [Spring Security Basic Authentication](http://www.baeldung.com/spring-security-basic-authentication) -- [Writing a Custom Filter in Spring Security](http://www.baeldung.com/spring-security-custom-filter) ### Notes diff --git a/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml b/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml index f6d15980ae..b0d483768b 100644 --- a/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml +++ b/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml @@ -11,8 +11,6 @@ - - @@ -22,7 +20,4 @@ - - - \ No newline at end of file diff --git a/spring-security-rest-basic-auth/README.md b/spring-security-rest-basic-auth/README.md index 3bd46bdd2a..328f46ed46 100644 --- a/spring-security-rest-basic-auth/README.md +++ b/spring-security-rest-basic-auth/README.md @@ -9,3 +9,4 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com - [RestTemplate with Basic Authentication in Spring](http://www.baeldung.com/2012/04/16/how-to-use-resttemplate-with-basic-authentication-in-spring-3-1) - [HttpClient Timeout](http://www.baeldung.com/httpclient-timeout) - [HttpClient with SSL](http://www.baeldung.com/httpclient-ssl) +- [Writing a Custom Filter in Spring Security](http://www.baeldung.com/spring-security-custom-filter) \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/security/filter/CustomFilter.java b/spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomFilter.java similarity index 92% rename from spring-security-basic-auth/src/main/java/org/baeldung/security/filter/CustomFilter.java rename to spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomFilter.java index 8d2b919cb0..01e5b0b59d 100644 --- a/spring-security-basic-auth/src/main/java/org/baeldung/security/filter/CustomFilter.java +++ b/spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomFilter.java @@ -1,4 +1,4 @@ -package org.baeldung.security.filter; +package org.baeldung.filter; import org.springframework.web.filter.GenericFilterBean; diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/security/filter/configuration/CustomWebSecurityConfigurerAdapter.java b/spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomWebSecurityConfigurerAdapter.java similarity index 63% rename from spring-security-basic-auth/src/main/java/org/baeldung/security/filter/configuration/CustomWebSecurityConfigurerAdapter.java rename to spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomWebSecurityConfigurerAdapter.java index d03d9cc018..2ff0e30f94 100644 --- a/spring-security-basic-auth/src/main/java/org/baeldung/security/filter/configuration/CustomWebSecurityConfigurerAdapter.java +++ b/spring-security-rest-basic-auth/src/main/java/org/baeldung/filter/CustomWebSecurityConfigurerAdapter.java @@ -1,7 +1,6 @@ -package org.baeldung.security.filter.configuration; +package org.baeldung.filter; -import org.baeldung.security.basic.MyBasicAuthenticationEntryPoint; -import org.baeldung.security.filter.CustomFilter; +import org.baeldung.security.RestAuthenticationEntryPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -14,17 +13,28 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi @EnableWebSecurity public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { - @Autowired - private MyBasicAuthenticationEntryPoint authenticationEntryPoint; + @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication().withUser("user1").password("user1Pass").authorities("ROLE_USER"); + auth + .inMemoryAuthentication() + .withUser("user1") + .password("user1Pass") + .authorities("ROLE_USER"); } @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests().antMatchers("/securityNone").permitAll().anyRequest().authenticated().and().httpBasic().authenticationEntryPoint(authenticationEntryPoint); + http + .authorizeRequests() + .antMatchers("/securityNone") + .permitAll() + .anyRequest() + .authenticated() + .and() + .httpBasic() + .authenticationEntryPoint(authenticationEntryPoint); http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class); } From bd237b2115e045ae003d0157e85b70a87ac50d9b Mon Sep 17 00:00:00 2001 From: Alexandre Lombard Date: Tue, 21 Mar 2017 17:33:31 +0100 Subject: [PATCH 056/149] master (#1455) * Example code to return image with @ResponseBody * Example code showing how to return images using @ResponseBody * Example code showing how to return images using @ResponseBody --- .../baeldung/produceimage/Application.java | 14 +++++++ .../controller/DataProducerController.java | 38 ++++++++++++++++++ .../com/baeldung/produceimage/data.txt | 1 + .../com/baeldung/produceimage/image.jpg | Bin 0 -> 6170 bytes 4 files changed, 53 insertions(+) create mode 100644 spring-rest/src/main/java/com/baeldung/produceimage/Application.java create mode 100644 spring-rest/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java create mode 100644 spring-rest/src/main/resources/com/baeldung/produceimage/data.txt create mode 100644 spring-rest/src/main/resources/com/baeldung/produceimage/image.jpg diff --git a/spring-rest/src/main/java/com/baeldung/produceimage/Application.java b/spring-rest/src/main/java/com/baeldung/produceimage/Application.java new file mode 100644 index 0000000000..179671d094 --- /dev/null +++ b/spring-rest/src/main/java/com/baeldung/produceimage/Application.java @@ -0,0 +1,14 @@ +package com.baeldung.produceimage; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; + +@EnableAutoConfiguration +@ComponentScan("com.baeldung.produceimage") +public class Application { + public static void main(final String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-rest/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java b/spring-rest/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java new file mode 100644 index 0000000000..6f34bdb9ae --- /dev/null +++ b/spring-rest/src/main/java/com/baeldung/produceimage/controller/DataProducerController.java @@ -0,0 +1,38 @@ +package com.baeldung.produceimage.controller; + +import org.apache.commons.io.IOUtils; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.io.IOException; +import java.io.InputStream; + +@Controller +public class DataProducerController { + + @GetMapping("/get-text") + public @ResponseBody String getText() { + return "Hello world"; + } + + @GetMapping(value = "/get-image") + public @ResponseBody byte[] getImage() throws IOException { + final InputStream in = getClass().getResourceAsStream("/com/baeldung/produceimage/image.jpg"); + return IOUtils.toByteArray(in); + } + + @GetMapping(value = "/get-image-with-media-type", produces = MediaType.IMAGE_JPEG_VALUE) + public @ResponseBody byte[] getImageWithMediaType() throws IOException { + final InputStream in = getClass().getResourceAsStream("/com/baeldung/produceimage/image.jpg"); + return IOUtils.toByteArray(in); + } + + @GetMapping(value = "/get-file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public @ResponseBody byte[] getFile() throws IOException { + final InputStream in = getClass().getResourceAsStream("/com/baeldung/produceimage/data.txt"); + return IOUtils.toByteArray(in); + } + +} diff --git a/spring-rest/src/main/resources/com/baeldung/produceimage/data.txt b/spring-rest/src/main/resources/com/baeldung/produceimage/data.txt new file mode 100644 index 0000000000..3cd18170c0 --- /dev/null +++ b/spring-rest/src/main/resources/com/baeldung/produceimage/data.txt @@ -0,0 +1 @@ +This is a sample file containing text data \ No newline at end of file diff --git a/spring-rest/src/main/resources/com/baeldung/produceimage/image.jpg b/spring-rest/src/main/resources/com/baeldung/produceimage/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0262656a33a02978a6c4a4754856801a839fd597 GIT binary patch literal 6170 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 literal 0 HcmV?d00001 From d66703b5d9340c67a94ec59e5e66686cb0058691 Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Tue, 21 Mar 2017 17:46:06 -0500 Subject: [PATCH 057/149] BAEL-680: rename test methods (#1470) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md * BAEL-680: renamed test methods --- .../java8/comparator/Java8ComparatorTest.java | 60 ++++--------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java b/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java index 5c338101d8..57e3898274 100644 --- a/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java +++ b/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java @@ -60,7 +60,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingComparing_thenCheckingSort() { + public void whenComparing_thenSortedByName() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Arrays.sort(employees, employeeNameComparator); // System.out.println(Arrays.toString(employees)); @@ -68,7 +68,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingComparingWithComparator_thenCheckingSort() { + public void whenComparingWithComparator_thenSortedByNameDesc() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName, (s1, s2) -> { return s2.compareTo(s1); }); @@ -78,7 +78,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingComparingInt_thenCheckingSort() { + public void whenComparingInt_thenSortedByAge() { Comparator employeeAgeComparator = Comparator.comparingInt(Employee::getAge); Arrays.sort(employees, employeeAgeComparator); // System.out.println(Arrays.toString(employees)); @@ -86,7 +86,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingComparingLong_thenCheckingSort() { + public void whenComparingLong_thenSortedByMobile() { Comparator employeeMobileComparator = Comparator.comparingLong(Employee::getMobile); Arrays.sort(employees, employeeMobileComparator); // System.out.println(Arrays.toString(employees)); @@ -94,7 +94,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingComparingDouble_thenCheckingSort() { + public void whenComparingDouble_thenSortedBySalary() { Comparator employeeSalaryComparator = Comparator.comparingDouble(Employee::getSalary); Arrays.sort(employees, employeeSalaryComparator); // System.out.println(Arrays.toString(employees)); @@ -102,7 +102,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingNaturalOrder_thenCheckingSort() { + public void whenNaturalOrder_thenSortedByName() { Comparator employeeNameComparator = Comparator. naturalOrder(); Arrays.sort(employees, employeeNameComparator); // System.out.println(Arrays.toString(employees)); @@ -110,7 +110,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingReverseOrder_thenCheckingSort() { + public void whenReverseOrder_thenSortedByNameDesc() { Comparator employeeNameComparator = Comparator. reverseOrder(); Arrays.sort(employees, employeeNameComparator); // System.out.println(Arrays.toString(employees)); @@ -118,7 +118,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingNullFirst_thenCheckingSort() { + public void whenNullsFirst_thenSortedByNameWithNullsFirst() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparator_nullFirst = Comparator.nullsFirst(employeeNameComparator); Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullFirst); @@ -127,7 +127,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingNullLast_thenCheckingSort() { + public void whenNullsLast_thenSortedByNameWithNullsLast() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparator_nullLast = Comparator.nullsLast(employeeNameComparator); Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullLast); @@ -136,7 +136,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingThenComparing_thenCheckingSort() { + public void whenThenComparing_thenSortedByAgeName() { Comparator employee_Age_Name_Comparator = Comparator.comparing(Employee::getAge).thenComparing(Employee::getName); Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator); @@ -145,7 +145,7 @@ public class Java8ComparatorTest { } @Test - public void givenEmployeeArray_whenUsingThenComparingInt_thenCheckingSort() { + public void whenThenComparing_thenSortedByNameAge() { Comparator employee_Name_Age_Comparator = Comparator.comparing(Employee::getName).thenComparingInt(Employee::getAge); Arrays.sort(someMoreEmployees, employee_Name_Age_Comparator); @@ -153,40 +153,4 @@ public class Java8ComparatorTest { assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByNameAge)); } - @Before - public void printData() { -// System.out.println("employees"); -// System.out.println(Arrays.toString(employees)); - // -// System.out.println("employeesArrayWithNulls"); -// System.out.println(Arrays.toString(employeesArrayWithNulls)); - // - // System.out.println("sortedEmployeesByName"); - // System.out.println(Arrays.toString(sortedEmployeesByName)); - // - // System.out.println("sortedEmployeesByNameDesc"); - // System.out.println(Arrays.toString(sortedEmployeesByNameDesc)); - // - // System.out.println("sortedEmployeesByAge"); - // System.out.println(Arrays.toString(sortedEmployeesByAge)); - // - // System.out.println("sortedEmployeesByMobile"); - // System.out.println(Arrays.toString(sortedEmployeesByMobile)); - // - // System.out.println("sortedEmployeesBySalary"); - // System.out.println(Arrays.toString(sortedEmployeesBySalary)); - // - // System.out.println("sortedEmployeesArray_WithNullsFirst"); - // System.out.println(Arrays.toString(sortedEmployeesArray_WithNullsFirst)); - // - // System.out.println("sortedEmployeesArray_WithNullsLast"); - // System.out.println(Arrays.toString(sortedEmployeesArray_WithNullsLast)); - // - // System.out.println("sortedEmployeesByNameAge"); - // System.out.println(Arrays.toString(sortedEmployeesByNameAge)); - // -// System.out.println("someMoreEmployees"); -// System.out.println(Arrays.toString(someMoreEmployees)); - // - } -} +} \ No newline at end of file From e71358a9dec1baab03c32323940facc5645f73c6 Mon Sep 17 00:00:00 2001 From: Justin Wilson Date: Wed, 22 Mar 2017 12:47:23 +0000 Subject: [PATCH 058/149] BAEL-503: initial commit of a simple Spring AMQL example application (#1467) --- pom.xml | 1 + spring-amqp-simple/pom.xml | 46 ++++++++++++++++++ .../springamqpsimple/MessageConsumer.java | 15 ++++++ .../springamqpsimple/MessageController.java | 26 ++++++++++ .../springamqpsimple/MessageProducer.java | 20 ++++++++ .../SpringAmqpApplication.java | 12 +++++ .../springamqpsimple/SpringAmqpConfig.java | 48 +++++++++++++++++++ .../src/main/resources/application.yaml | 4 ++ .../MessageControllerTest.java | 45 +++++++++++++++++ 9 files changed, 217 insertions(+) create mode 100644 spring-amqp-simple/pom.xml create mode 100644 spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageConsumer.java create mode 100644 spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageController.java create mode 100644 spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageProducer.java create mode 100644 spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpApplication.java create mode 100644 spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpConfig.java create mode 100644 spring-amqp-simple/src/main/resources/application.yaml create mode 100644 spring-amqp-simple/src/test/java/com/baeldung/springamqpsimple/MessageControllerTest.java diff --git a/pom.xml b/pom.xml index 1a1108cb99..15e0e3322e 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,7 @@ spring-akka spring-amqp spring-all + spring-amqp-simple spring-apache-camel spring-batch spring-boot diff --git a/spring-amqp-simple/pom.xml b/spring-amqp-simple/pom.xml new file mode 100644 index 0000000000..38738d875f --- /dev/null +++ b/spring-amqp-simple/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.5.2.RELEASE + + + com.baeldung + spring-amqp-simple + 1.0.0-SNAPSHOT + Spring AMQP Simple App + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageConsumer.java b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageConsumer.java new file mode 100644 index 0000000000..b757dfebe8 --- /dev/null +++ b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageConsumer.java @@ -0,0 +1,15 @@ +package com.baeldung.springamqpsimple; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +@Component +public class MessageConsumer { + + private static final Logger logger = LogManager.getLogger(MessageConsumer.class); + + public void receiveMessage(String message) { + logger.info("Received Message: " + message); + } +} diff --git a/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageController.java b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageController.java new file mode 100644 index 0000000000..deef22c4d6 --- /dev/null +++ b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageController.java @@ -0,0 +1,26 @@ +package com.baeldung.springamqpsimple; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Controller +public class MessageController { + + private final MessageProducer messageProducer; + + @Autowired + public MessageController(MessageProducer messageProducer) { + this.messageProducer = messageProducer; + } + + @RequestMapping(value="/messages", method= RequestMethod.POST) + @ResponseStatus(value= HttpStatus.CREATED) + public void sendMessage(@RequestBody String message) { + messageProducer.sendMessage(message); + } +} diff --git a/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageProducer.java b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageProducer.java new file mode 100644 index 0000000000..225f37bdd0 --- /dev/null +++ b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/MessageProducer.java @@ -0,0 +1,20 @@ +package com.baeldung.springamqpsimple; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class MessageProducer { + + private final RabbitTemplate rabbitTemplate; + + @Autowired + public MessageProducer(RabbitTemplate rabbitTemplate) { + this.rabbitTemplate = rabbitTemplate; + } + + public void sendMessage(String message) { + rabbitTemplate.convertAndSend(SpringAmqpConfig.queueName, message); + } +} diff --git a/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpApplication.java b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpApplication.java new file mode 100644 index 0000000000..b84a49a230 --- /dev/null +++ b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.springamqpsimple; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringAmqpApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(SpringAmqpApplication.class, args); + } +} diff --git a/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpConfig.java b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpConfig.java new file mode 100644 index 0000000000..78d79dd47a --- /dev/null +++ b/spring-amqp-simple/src/main/java/com/baeldung/springamqpsimple/SpringAmqpConfig.java @@ -0,0 +1,48 @@ +package com.baeldung.springamqpsimple; + +import org.springframework.amqp.core.*; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("!test") +public class SpringAmqpConfig { + + public final static String queueName = "com.baeldung.spring-amqp-simple.queue"; + public final static String exchangeName = "com.baeldung.spring-amqp-simple.exchange"; + + @Bean + Queue queue() { + return new Queue(queueName, false); + } + + @Bean + Exchange exchange() { + return new DirectExchange(exchangeName); + } + + @Bean + Binding binding(Queue queue, DirectExchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with(queueName); + } + + @Bean + SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter) { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.setQueueNames(queueName); + container.setMessageListener(listenerAdapter); + return container; + } + + @Bean + MessageListenerAdapter listenerAdapter(MessageConsumer messageReceiver) { + return new MessageListenerAdapter(messageReceiver, "receiveMessage"); + } + +} diff --git a/spring-amqp-simple/src/main/resources/application.yaml b/spring-amqp-simple/src/main/resources/application.yaml new file mode 100644 index 0000000000..4aca1bb783 --- /dev/null +++ b/spring-amqp-simple/src/main/resources/application.yaml @@ -0,0 +1,4 @@ +spring: + rabbitmq: + username: baeldung + password: baeldung \ No newline at end of file diff --git a/spring-amqp-simple/src/test/java/com/baeldung/springamqpsimple/MessageControllerTest.java b/spring-amqp-simple/src/test/java/com/baeldung/springamqpsimple/MessageControllerTest.java new file mode 100644 index 0000000000..c62c86290a --- /dev/null +++ b/spring-amqp-simple/src/test/java/com/baeldung/springamqpsimple/MessageControllerTest.java @@ -0,0 +1,45 @@ +package com.baeldung.springamqpsimple; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; + +@RunWith(SpringRunner.class) +@ActiveProfiles("test") +@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MessageControllerTest { + + @Autowired + private TestRestTemplate restTemplate; + + @MockBean + private RabbitTemplate rabbitTemplate; + + @Test + public void whenPostingMessage_thenMessageIsCreated() { + final String message = "Hello World!"; + ResponseEntity responseEntity = restTemplate.postForEntity("/messages", message, Void.class); + + assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode()); + } + + @Test + public void whenPostingMessage_thenMessageIsSentToBroker() { + final String message = "Hello World!"; + restTemplate.postForEntity("/messages", message, Void.class); + + verify(rabbitTemplate).convertAndSend(SpringAmqpConfig.queueName, message); + } +} \ No newline at end of file From a055ab5f81698fdf2330edd9d0a51371d4e18c4f Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Wed, 22 Mar 2017 19:29:59 -0500 Subject: [PATCH 059/149] BAEL-714: Updated README.md (#1475) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md * BAEL-680: renamed test methods * BAEL-714: Updated README.md --- spring-mvc-forms/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-mvc-forms/README.md b/spring-mvc-forms/README.md index 745851a102..86abd7e4c1 100644 --- a/spring-mvc-forms/README.md +++ b/spring-mvc-forms/README.md @@ -3,3 +3,4 @@ ### Relevant Articles - [MaxUploadSizeExceededException in Spring](http://www.baeldung.com/spring-maxuploadsizeexceeded) - [Getting Started with Forms in Spring MVC](http://www.baeldung.com/spring-mvc-form-tutorial) +- [Form Validation with AngularJS and Spring MVC](http://www.baeldung.com/validation-angularjs-spring-mvc) From b01c2c5a949339419e54ebcd7635ccdc12f311ef Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Thu, 23 Mar 2017 13:46:32 +0100 Subject: [PATCH 060/149] Bael 361 jackson streaming (#1459) * BAEL-361 tests for Streaming API * BAEL-361 do not use deprecated API * BAEL-361 return to not read whole json document, only needed field --- .../streaming/JacksonStreamingAPITest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 jackson/src/test/java/com/baeldung/jackson/streaming/JacksonStreamingAPITest.java diff --git a/jackson/src/test/java/com/baeldung/jackson/streaming/JacksonStreamingAPITest.java b/jackson/src/test/java/com/baeldung/jackson/streaming/JacksonStreamingAPITest.java new file mode 100644 index 0000000000..6f61793315 --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/streaming/JacksonStreamingAPITest.java @@ -0,0 +1,119 @@ +package com.baeldung.jackson.streaming; + + +import com.fasterxml.jackson.core.*; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; + +public class JacksonStreamingAPITest { + + @Test + public void givenJsonGenerator_whenAppendJsonToIt_thenGenerateJson() throws IOException { + //given + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + JsonFactory jfactory = new JsonFactory(); + JsonGenerator jGenerator = jfactory.createGenerator(stream, JsonEncoding.UTF8); + + //when + jGenerator.writeStartObject(); + jGenerator.writeStringField("name", "Tom"); + jGenerator.writeNumberField("age", 25); + jGenerator.writeFieldName("address"); + jGenerator.writeStartArray(); + jGenerator.writeString("Poland"); + jGenerator.writeString("5th avenue"); + jGenerator.writeEndArray(); + jGenerator.writeEndObject(); + jGenerator.close(); + + //then + String json = new String(stream.toByteArray(), "UTF-8"); + assertEquals(json, "{\"name\":\"Tom\",\"age\":25,\"address\":[\"Poland\",\"5th avenue\"]}"); + } + + @Test + public void givenJson_whenReadItUsingStreamAPI_thenShouldCreateProperJsonObject() throws IOException { + //given + String json = "{\"name\":\"Tom\",\"age\":25,\"address\":[\"Poland\",\"5th avenue\"]}"; + JsonFactory jfactory = new JsonFactory(); + JsonParser jParser = jfactory.createParser(json); + + String parsedName = null; + Integer parsedAge = null; + List addresses = new LinkedList<>(); + + //when + while (jParser.nextToken() != JsonToken.END_OBJECT) { + + String fieldname = jParser.getCurrentName(); + if ("name".equals(fieldname)) { + jParser.nextToken(); + parsedName = jParser.getText(); + + } + + if ("age".equals(fieldname)) { + jParser.nextToken(); + parsedAge = jParser.getIntValue(); + + } + + if ("address".equals(fieldname)) { + jParser.nextToken(); + + while (jParser.nextToken() != JsonToken.END_ARRAY) { + addresses.add(jParser.getText()); + } + } + + } + jParser.close(); + + //then + assertEquals(parsedName, "Tom"); + assertEquals(parsedAge, (Integer) 25); + assertEquals(addresses, Arrays.asList("Poland", "5th avenue")); + + } + + @Test + public void givenJson_whenWantToExtractPartOfIt_thenShouldExtractOnlyNeededFieldWithoutGoingThroughWholeJSON() throws IOException { + //given + String json = "{\"name\":\"Tom\",\"age\":25,\"address\":[\"Poland\",\"5th avenue\"]}"; + JsonFactory jfactory = new JsonFactory(); + JsonParser jParser = jfactory.createParser(json); + + String parsedName = null; + Integer parsedAge = null; + List addresses = new LinkedList<>(); + + //when + while (jParser.nextToken() != JsonToken.END_OBJECT) { + + String fieldname = jParser.getCurrentName(); + + if ("age".equals(fieldname)) { + jParser.nextToken(); + parsedAge = jParser.getIntValue(); + return; + } + + } + jParser.close(); + + //then + assertNull(parsedName); + assertEquals(parsedAge, (Integer) 25); + assertTrue(addresses.isEmpty()); + + } +} From e884e3f92455a9dd9f620d57952e8826d54f4639 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Date: Fri, 24 Mar 2017 02:20:30 +0530 Subject: [PATCH 061/149] update spring-kafka project with support for multiple partitions and JSON serializer (#1472) --- spring-kafka/README.md | 26 ++++- spring-kafka/pom.xml | 12 +- .../com/baeldung/spring/kafka/Greeting.java | 37 ++++++ .../spring/kafka/KafkaApplication.java | 105 +++++++++++++++++- .../spring/kafka/KafkaConsumerConfig.java | 41 ++++++- .../spring/kafka/KafkaProducerConfig.java | 20 +++- .../src/main/resources/application.properties | 3 + 7 files changed, 228 insertions(+), 16 deletions(-) create mode 100644 spring-kafka/src/main/java/com/baeldung/spring/kafka/Greeting.java diff --git a/spring-kafka/README.md b/spring-kafka/README.md index 2731eca042..c8f01cc28b 100644 --- a/spring-kafka/README.md +++ b/spring-kafka/README.md @@ -2,8 +2,28 @@ This is a simple Spring Boot app to demonstrate sending and receiving of messages in Kafka using spring-kafka. -As Kafka topics are not created automatically by default, this application requires that a topic named 'baeldung' is created manually. +As Kafka topics are not created automatically by default, this application requires that you create the following topics manually. -`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic baeldung` +`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic baeldung`
+`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 5 --topic partitioned`
+`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic filtered`
+`$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic greeting`
-Two listeners with group Ids **foo** and **bar** are configured. When run successfully, the *Hello World!* message will be received by both the listeners and logged on console. +When the application runs successfully, following output is logged on to console (along with spring logs): + +#### Message received from the 'baeldung' topic by the basic listeners with groups foo and bar +>Received Messasge in group 'foo': Hello, World!
+Received Messasge in group 'bar': Hello, World! + +#### Message received from the 'baeldung' topic, with the partition info +>Received Messasge: Hello, World! from partition: 0 + +#### Message received from the 'partitioned' topic, only from specific partitions +>Received Message: Hello To Partioned Topic! from partition: 0
+Received Message: Hello To Partioned Topic! from partition: 3 + +#### Message received from the 'filtered' topic after filtering +>Recieved Message in filtered listener: Hello Baeldung! + +#### Message (Serialized Java Object) received from the 'greeting' topic +>Recieved greeting message: Greetings, World!! \ No newline at end of file diff --git a/spring-kafka/pom.xml b/spring-kafka/pom.xml index 73eaf3acff..11810a17dd 100644 --- a/spring-kafka/pom.xml +++ b/spring-kafka/pom.xml @@ -12,6 +12,7 @@ 1.8 1.1.3.RELEASE + 2.6.7 @@ -21,17 +22,22 @@ - + org.springframework.boot spring-boot-starter - + org.springframework.kafka spring-kafka - + + + com.fasterxml.jackson.core + jackson-databind + + diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/Greeting.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/Greeting.java new file mode 100644 index 0000000000..b4633e802a --- /dev/null +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/Greeting.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.kafka; + +public class Greeting { + + private String msg; + private String name; + + public Greeting() { + + } + + public Greeting(String msg, String name) { + this.msg = msg; + this.name = name; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return msg + ", " + name + "!"; + } +} diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java index 252054a9f1..50978d5ea9 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaApplication.java @@ -10,21 +10,61 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.annotation.TopicPartition; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Payload; @SpringBootApplication public class KafkaApplication { public static void main(String[] args) throws Exception { + ConfigurableApplicationContext context = SpringApplication.run(KafkaApplication.class, args); + MessageProducer producer = context.getBean(MessageProducer.class); - producer.sendMessage("Hello, World!"); - MessageListener listener = context.getBean(MessageListener.class); - listener.latch.await(20, TimeUnit.SECONDS); - Thread.sleep(60000); - context.close(); + /* + * Sending a Hello World message to topic 'baeldung'. + * Must be recieved by both listeners with group foo + * and bar with containerFactory fooKafkaListenerContainerFactory + * and barKafkaListenerContainerFactory respectively. + * It will also be recieved by the listener with + * headersKafkaListenerContainerFactory as container factory + */ + producer.sendMessage("Hello, World!"); + listener.latch.await(10, TimeUnit.SECONDS); + /* + * Sending message to a topic with 5 partition, + * each message to a different partition. But as per + * listener configuration, only the messages from + * partition 0 and 3 will be consumed. + */ + for (int i = 0; i < 5; i++) { + producer.sendMessageToPartion("Hello To Partioned Topic!", i); + } + listener.partitionLatch.await(10, TimeUnit.SECONDS); + + /* + * Sending message to 'filtered' topic. As per listener + * configuration, all messages with char sequence + * 'World' will be discarded. + */ + producer.sendMessageToFiltered("Hello Baeldung!"); + producer.sendMessageToFiltered("Hello World!"); + listener.filterLatch.await(10, TimeUnit.SECONDS); + + /* + * Sending message to 'greeting' topic. This will send + * and recieved a java object with the help of + * greetingKafkaListenerContainerFactory. + */ + producer.sendGreetingMessage(new Greeting("Greetings", "World!")); + listener.greetingLatch.await(10, TimeUnit.SECONDS); + + context.close(); } @Bean @@ -42,18 +82,47 @@ public class KafkaApplication { @Autowired private KafkaTemplate kafkaTemplate; + @Autowired + private KafkaTemplate greetingKafkaTemplate; + @Value(value = "${message.topic.name}") private String topicName; + @Value(value = "${partitioned.topic.name}") + private String partionedTopicName; + + @Value(value = "${filtered.topic.name}") + private String filteredTopicName; + + @Value(value = "${greeting.topic.name}") + private String greetingTopicName; + public void sendMessage(String message) { kafkaTemplate.send(topicName, message); } + public void sendMessageToPartion(String message, int partition) { + kafkaTemplate.send(partionedTopicName, partition, message); + } + + public void sendMessageToFiltered(String message) { + kafkaTemplate.send(filteredTopicName, message); + } + + public void sendGreetingMessage(Greeting greeting) { + greetingKafkaTemplate.send(greetingTopicName, greeting); + } } public static class MessageListener { - private CountDownLatch latch = new CountDownLatch(2); + private CountDownLatch latch = new CountDownLatch(3); + + private CountDownLatch partitionLatch = new CountDownLatch(2); + + private CountDownLatch filterLatch = new CountDownLatch(2); + + private CountDownLatch greetingLatch = new CountDownLatch(1); @KafkaListener(topics = "${message.topic.name}", group = "foo", containerFactory = "fooKafkaListenerContainerFactory") public void listenGroupFoo(String message) { @@ -67,6 +136,30 @@ public class KafkaApplication { latch.countDown(); } + @KafkaListener(topics = "${message.topic.name}", containerFactory = "headersKafkaListenerContainerFactory") + public void listenWithHeaders(@Payload String message, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) { + System.out.println("Received Messasge: " + message + " from partition: " + partition); + latch.countDown(); + } + + @KafkaListener(topicPartitions = @TopicPartition(topic = "${partitioned.topic.name}", partitions = { "0", "3" })) + public void listenToParition(@Payload String message, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) { + System.out.println("Received Message: " + message + " from partition: " + partition); + this.partitionLatch.countDown(); + } + + @KafkaListener(topics = "${filtered.topic.name}", containerFactory = "filterKafkaListenerContainerFactory") + public void listenWithFilter(String message) { + System.out.println("Recieved Message in filtered listener: " + message); + this.filterLatch.countDown(); + } + + @KafkaListener(topics = "${greeting.topic.name}", containerFactory = "greetingKafkaListenerContainerFactory") + public void greetingListener(Greeting greeting) { + System.out.println("Recieved greeting message: " + greeting); + this.greetingLatch.countDown(); + } + } } diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java index f9edda2435..9353e63ff6 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaConsumerConfig.java @@ -12,6 +12,7 @@ import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.support.serializer.JsonDeserializer; @EnableKafka @Configuration @@ -35,11 +36,49 @@ public class KafkaConsumerConfig { factory.setConsumerFactory(consumerFactory("foo")); return factory; } - + @Bean public ConcurrentKafkaListenerContainerFactory barKafkaListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory("bar")); return factory; } + + @Bean + public ConcurrentKafkaListenerContainerFactory headersKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("headers")); + return factory; + } + + @Bean + public ConcurrentKafkaListenerContainerFactory partitionsKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("partitions")); + return factory; + } + + @Bean + public ConcurrentKafkaListenerContainerFactory filterKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("filter")); + factory.setRecordFilterStrategy(record -> record.value() + .contains("World")); + return factory; + } + + public ConsumerFactory greetingConsumerFactory() { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "greeting"); + return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(), new JsonDeserializer<>(Greeting.class)); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory greetingKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(greetingConsumerFactory()); + return factory; + } + } diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java index 4f9f9719ee..84d57c9e92 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.support.serializer.JsonSerializer; @Configuration public class KafkaProducerConfig { @@ -29,8 +30,21 @@ public class KafkaProducerConfig { @Bean public KafkaTemplate kafkaTemplate() { - KafkaTemplate template = - new KafkaTemplate(producerFactory()); - return template; + return new KafkaTemplate(producerFactory()); } + + @Bean + public ProducerFactory greetingProducerFactory() { + Map configProps = new HashMap(); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + return new DefaultKafkaProducerFactory(configProps); + } + + @Bean + public KafkaTemplate greetingKafkaTemplate() { + return new KafkaTemplate(greetingProducerFactory()); + } + } diff --git a/spring-kafka/src/main/resources/application.properties b/spring-kafka/src/main/resources/application.properties index a1d73b204c..eaf113191e 100644 --- a/spring-kafka/src/main/resources/application.properties +++ b/spring-kafka/src/main/resources/application.properties @@ -1,2 +1,5 @@ kafka.bootstrapAddress=localhost:9092 message.topic.name=baeldung +greeting.topic.name=greeting +filtered.topic.name=filtered +partitioned.topic.name=partitioned \ No newline at end of file From 2d556cd763f0261efb1c2604815656a26a6e83bc Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Fri, 24 Mar 2017 15:29:14 +0100 Subject: [PATCH 062/149] Bael 738 (#1478) * BAEL-724 code for put/patch article * BAEL-724 fix typo * BAEL-728 more generic patch approach --- .../repository/HeavyResourceRepository.java | 10 ++++-- .../controller/HeavyResourceController.java | 18 +++++++---- .../web/dto/HeavyResourceAddressOnly.java | 31 +++++++++++++++++++ .../HeavyResourceControllerTest.java | 19 ++++++++++-- 4 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressOnly.java diff --git a/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java index cff78442d0..6de9e75450 100644 --- a/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java +++ b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java @@ -1,14 +1,20 @@ package org.baeldung.repository; import org.baeldung.web.dto.HeavyResource; -import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; +import org.baeldung.web.dto.HeavyResourceAddressOnly; + +import java.util.Map; public class HeavyResourceRepository { public void save(HeavyResource heavyResource) { } - public void save(HeavyResourceAddressPartialUpdate partialUpdate) { + public void save(HeavyResourceAddressOnly partialUpdate) { + + } + + public void save(Map updates, String id) { } } diff --git a/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java index a2d5cfbd7b..f2c4ffaa51 100644 --- a/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java +++ b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java @@ -3,13 +3,12 @@ package org.baeldung.web.controller; import org.baeldung.repository.HeavyResourceRepository; import org.baeldung.web.dto.HeavyResource; -import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; +import org.baeldung.web.dto.HeavyResourceAddressOnly; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; @RestController public class HeavyResourceController { @@ -23,9 +22,16 @@ public class HeavyResourceController { } @RequestMapping(value = "/heavy", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity partialUpdateName(@RequestBody HeavyResourceAddressPartialUpdate partialUpdate) { + public ResponseEntity partialUpdateName(@RequestBody HeavyResourceAddressOnly partialUpdate) { heavyResourceRepository.save(partialUpdate); return ResponseEntity.ok("resource address updated"); } + @RequestMapping(value = "/heavy/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity partialUpdateGeneric(@RequestBody Map updates, + @PathVariable("id") String id) { + heavyResourceRepository.save(updates, id); + return ResponseEntity.ok("resource updated"); + } + } diff --git a/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressOnly.java b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressOnly.java new file mode 100644 index 0000000000..f96347d60c --- /dev/null +++ b/spring-rest/src/main/java/org/baeldung/web/dto/HeavyResourceAddressOnly.java @@ -0,0 +1,31 @@ +package org.baeldung.web.dto; + + +public class HeavyResourceAddressOnly { + private Integer id; + private String address; + + public HeavyResourceAddressOnly() { + } + + public HeavyResourceAddressOnly(Integer id, String address) { + this.id = id; + this.address = address; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java index e68506701d..e283c5f5f6 100644 --- a/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java +++ b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java @@ -3,7 +3,7 @@ package org.baeldung.web.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.baeldung.config.WebConfig; import org.baeldung.web.dto.HeavyResource; -import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; +import org.baeldung.web.dto.HeavyResourceAddressOnly; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -16,6 +16,8 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import java.util.HashMap; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -47,10 +49,21 @@ public class HeavyResourceControllerTest { } @Test - public void givenNewAddressOfResource_whenExecutePutRequest_thenUpdateResourcePartially() throws Exception { + public void givenNewAddressOfResource_whenExecutePatchRequest_thenUpdateResourcePartially() throws Exception { mockMvc.perform(patch("/heavy") .contentType(MediaType.APPLICATION_JSON_VALUE) - .content(objectMapper.writeValueAsString(new HeavyResourceAddressPartialUpdate(1, "5th avenue"))) + .content(objectMapper.writeValueAsString(new HeavyResourceAddressOnly(1, "5th avenue"))) + ).andExpect(status().isOk()); + } + + @Test + public void givenNewAddressOfResource_whenExecutePatchGeneric_thenUpdateResourcePartially() throws Exception { + HashMap updates = new HashMap<>(); + updates.put("address", "5th avenue"); + + mockMvc.perform(patch("/heavy/1") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(updates)) ).andExpect(status().isOk()); } From 94e1fc2da631a7d49837fec8aa548f4254dfd323 Mon Sep 17 00:00:00 2001 From: pivovarit Date: Fri, 24 Mar 2017 15:52:46 +0100 Subject: [PATCH 063/149] Remove unused tests --- .../org/baeldung/boot/FooComponentTests.java | 70 ------------------- .../org/baeldung/boot/FooIntegrationTest.java | 43 ------------ .../java/org/baeldung/boot/FooJPATest.java | 34 --------- .../java/org/baeldung/boot/FooJsonTest.java | 35 ---------- 4 files changed, 182 deletions(-) delete mode 100644 spring-boot/src/test/java/org/baeldung/boot/FooComponentTests.java delete mode 100644 spring-boot/src/test/java/org/baeldung/boot/FooIntegrationTest.java delete mode 100644 spring-boot/src/test/java/org/baeldung/boot/FooJPATest.java delete mode 100644 spring-boot/src/test/java/org/baeldung/boot/FooJsonTest.java diff --git a/spring-boot/src/test/java/org/baeldung/boot/FooComponentTests.java b/spring-boot/src/test/java/org/baeldung/boot/FooComponentTests.java deleted file mode 100644 index 72ccc0bfb8..0000000000 --- a/spring-boot/src/test/java/org/baeldung/boot/FooComponentTests.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.baeldung.boot; - -import org.baeldung.boot.components.FooService; -import org.baeldung.boot.model.Foo; -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.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doReturn; - -@RunWith(SpringRunner.class) -@SpringBootTest( - classes = DemoApplication.class, - webEnvironment = WebEnvironment.RANDOM_PORT) -public class FooComponentTests { - - @Autowired - private TestRestTemplate testRestTemplate; - - @SpyBean - private FooService fooService; - - @Before - public void init() throws Exception { - Foo foo = new Foo(); - foo.setId(5); - foo.setName("MOCKED_FOO"); - - doReturn(foo).when(fooService).getFooWithId(anyInt()); - - // doCallRealMethod().when(fooComponent).getFooWithName(anyString()); - } - - @Test - public void givenInquiryingFooWithId_whenFooComponentIsMocked_thenAssertMockedResult() { - Map pathVariables = new HashMap<>(); - pathVariables.put("id", "1"); - ResponseEntity fooResponse = testRestTemplate.getForEntity("/{id}", Foo.class, pathVariables); - - assertNotNull(fooResponse); - assertEquals(HttpStatus.OK, fooResponse.getStatusCode()); - assertEquals(5, fooResponse.getBody().getId().longValue()); - assertEquals("MOCKED_FOO", fooResponse.getBody().getName()); - } - - @Test - public void givenInquiryingFooWithName_whenFooComponentIsMocked_thenAssertMockedResult() { - Map pathVariables = new HashMap<>(); - pathVariables.put("name", "Foo_Name"); - ResponseEntity fooResponse = testRestTemplate.getForEntity("/?name={name}", Foo.class, pathVariables); - - assertNotNull(fooResponse); - assertEquals(HttpStatus.OK, fooResponse.getStatusCode()); - assertEquals(1, fooResponse.getBody().getId().longValue()); - } -} \ No newline at end of file diff --git a/spring-boot/src/test/java/org/baeldung/boot/FooIntegrationTest.java b/spring-boot/src/test/java/org/baeldung/boot/FooIntegrationTest.java deleted file mode 100644 index 932cce26d5..0000000000 --- a/spring-boot/src/test/java/org/baeldung/boot/FooIntegrationTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.baeldung.boot; -import java.util.HashMap; -import java.util.Map; - -import org.baeldung.boot.DemoApplication; -import org.baeldung.boot.model.Foo; -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.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes=DemoApplication.class,webEnvironment = WebEnvironment.RANDOM_PORT) -public class FooIntegrationTest { - - @Autowired - private TestRestTemplate testRestTemplate; - - - @Test - public void givenInquiryingFooWithId_whenIdIsValid_thenHttpStatusOK(){ - Map pathVariables = new HashMap(); - pathVariables.put("id", "1"); - ResponseEntity fooResponse = testRestTemplate.getForEntity("/{id}", Foo.class, pathVariables); - Assert.assertNotNull(fooResponse); - Assert.assertEquals(HttpStatus.OK,fooResponse.getStatusCode()); - } - - @Test - public void givenInquiryingFooWithName_whenNameIsValid_thenHttpStatusOK(){ - Map pathVariables = new HashMap(); - pathVariables.put("name", "Foo_Name"); - ResponseEntity fooResponse = testRestTemplate.getForEntity("/?name={name}", Foo.class, pathVariables); - Assert.assertNotNull(fooResponse); - Assert.assertEquals(HttpStatus.OK,fooResponse.getStatusCode()); - } -} \ No newline at end of file diff --git a/spring-boot/src/test/java/org/baeldung/boot/FooJPATest.java b/spring-boot/src/test/java/org/baeldung/boot/FooJPATest.java deleted file mode 100644 index c29aa64e6c..0000000000 --- a/spring-boot/src/test/java/org/baeldung/boot/FooJPATest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.baeldung.boot; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import org.baeldung.boot.model.Foo; -import org.baeldung.boot.repository.FooRepository; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@DataJpaTest -public class FooJPATest { - - @Autowired - private TestEntityManager entityManager; - - @Autowired - private FooRepository repository; - - @Test - public void findFooByName() { - this.entityManager.persist(new Foo("Foo_Name_2")); - Foo foo = this.repository.findByName("Foo_Name_2"); - assertNotNull(foo); - assertEquals("Foo_Name_2",foo.getName()); - // Due to having Insert query for Foo with Id 1, so TestEntityManager generates new Id of 2 - assertEquals(2l,foo.getId().longValue()); - } -} \ No newline at end of file diff --git a/spring-boot/src/test/java/org/baeldung/boot/FooJsonTest.java b/spring-boot/src/test/java/org/baeldung/boot/FooJsonTest.java deleted file mode 100644 index 2789ed0a8c..0000000000 --- a/spring-boot/src/test/java/org/baeldung/boot/FooJsonTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.baeldung.boot; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.baeldung.boot.model.Foo; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.json.JsonTest; -import org.springframework.boot.test.json.JacksonTester; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@JsonTest -public class FooJsonTest { - - @Autowired - private JacksonTester json; - - - @Test - public void testSerialize() throws Exception { - Foo foo = new Foo(3, "Foo_Name_3"); - assertThat(this.json.write(foo)).isEqualToJson("expected.json"); - assertThat(this.json.write(foo)).hasJsonPathStringValue("@.name"); - assertThat(this.json.write(foo)).extractingJsonPathStringValue("@.name").isEqualTo("Foo_Name_3"); - } - - @Test - public void testDeserialize() throws Exception { - String content = "{\"id\":4,\"name\":\"Foo_Name_4\"}"; - assertThat(this.json.parseObject(content).getName()).isEqualTo("Foo_Name_4"); - assertThat(this.json.parseObject(content).getId()==4); - } -} \ No newline at end of file From ae52b822554f52224129d8da35e3338f958865d8 Mon Sep 17 00:00:00 2001 From: pivovarit Date: Fri, 24 Mar 2017 16:00:05 +0100 Subject: [PATCH 064/149] mvn test --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 120d365569..7737fd4b08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java -install: travis_wait 60 mvn -q clean install +install: travis_wait 60 mvn -q test before_script: - echo "MAVEN_OPTS='-Xmx2048M -Xss128M -XX:MaxPermSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:-UseGCOverheadLimit'" > ~/.mavenrc From cd0873751c7663c93b9f86199ba9af4e09e7d61d Mon Sep 17 00:00:00 2001 From: pivovarit Date: Fri, 24 Mar 2017 16:03:20 +0100 Subject: [PATCH 065/149] add testing profile --- spring-security-mvc-login/pom.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spring-security-mvc-login/pom.xml b/spring-security-mvc-login/pom.xml index 3809dc9f26..f208c74dc4 100644 --- a/spring-security-mvc-login/pom.xml +++ b/spring-security-mvc-login/pom.xml @@ -200,11 +200,9 @@ ${maven-surefire-plugin.version} - + **/*IntegrationTest.java + **/*LiveTest.java - - -
From d0b0debd062018d8fa6106f063a9b89898460d5b Mon Sep 17 00:00:00 2001 From: pivovarit Date: Fri, 24 Mar 2017 16:17:34 +0100 Subject: [PATCH 066/149] Exclude integration tests --- apache-poi/temp.xlsx | Bin 3492 -> 3492 bytes core-java/pom.xml | 1 - ...CountdownLatchExampleIntegrationTest.java} | 2 +- ...va => JavaProcessUnitIntegrationTest.java} | 2 +- ...dPoolInParallelStreamIntegrationTest.java} | 76 +++++++++--------- ...XmlApplicationContextIntegrationTest.java} | 14 ++-- spring-data-rest/pom.xml | 13 ++- ...ringDataRelationshipsIntegrationTest.java} | 2 +- ...ringDataRestValidatorIntegrationTest.java} | 19 +++-- spring-security-cache-control/pom.xml | 16 ++++ spring-security-mvc-login/pom.xml | 2 - xmlunit2/pom.xml | 12 ++- 12 files changed, 96 insertions(+), 63 deletions(-) rename core-java/src/test/java/com/baeldung/concurrent/countdownlatch/{CountdownLatchExampleTest.java => CountdownLatchExampleIntegrationTest.java} (98%) rename core-java/src/test/java/org/baeldung/java/shell/{JavaProcessUnitTest.java => JavaProcessUnitIntegrationTest.java} (98%) rename core-java/src/test/java/org/baeldung/java/streams/{ThreadPoolInParallelStreamTest.java => ThreadPoolInParallelStreamIntegrationTest.java} (93%) rename spring-core/src/test/java/com/baeldung/applicationcontext/{ClasspathXmlApplicationContextTest.java => ClasspathXmlApplicationContextIntegrationTest.java} (97%) rename spring-data-rest/src/test/java/com/baeldung/relationships/{SpringDataRelationshipsTest.java => SpringDataRelationshipsIntegrationTest.java} (98%) rename spring-data-rest/src/test/java/com/baeldung/validator/{SpringDataRestValidatorTest.java => SpringDataRestValidatorIntegrationTest.java} (98%) diff --git a/apache-poi/temp.xlsx b/apache-poi/temp.xlsx index cbea3a410d193a848a5321af8e311bcf61f7160f..12a9b2656c24c1605df94f1a00c1f792a580f154 100644 GIT binary patch delta 460 zcmZ1?y+oQfz?+#xgn@&DgW*79#YSEkMrI(rS%DmQx=gyeR8`p2OU13g<4GuJ#h$ggec8*Dd~TU>w9kC)$`)mhp}fBCbUK_uR8*UW+g z%qL8wB7STzoc#V|a%JV!cLBiyRnsOKX`AWVCYBuLs9g79{okWMLM5)p9Py~~_+Eb9 zSbfK~$z1g>5}FO{q7cRm4rusp z=Uf5_-#BhFaQIH=wgb~QxUD&W;q3)7eX=x}_~@y5bkUalycw)P z0um05eDf~!Op-Ti2>y4!wQ|!0_xt)+Ec$LnoQc`fF=_ej*}s4O*mC{yd!~Y#pHI>b zG4d+}nr!%xn5=ex^6^KXR^5DDHT81tEOAZS{1J*)MTRAd(% zzWY_3MepqrmHJ8RCi@3?vvZiJ)SIe_8q1u}iIG> org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin.version} **/*IntegrationTest.java diff --git a/core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleTest.java b/core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleIntegrationTest.java similarity index 98% rename from core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleTest.java rename to core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleIntegrationTest.java index 7bb2d4bb70..fc343e4cee 100644 --- a/core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleTest.java +++ b/core-java/src/test/java/com/baeldung/concurrent/countdownlatch/CountdownLatchExampleIntegrationTest.java @@ -12,7 +12,7 @@ import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -public class CountdownLatchExampleTest { +public class CountdownLatchExampleIntegrationTest { @Test public void whenParallelProcessing_thenMainThreadWillBlockUntilCompletion() throws InterruptedException { // Given diff --git a/core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitTest.java b/core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitIntegrationTest.java similarity index 98% rename from core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitTest.java rename to core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitIntegrationTest.java index 2c330c513d..2e38886271 100644 --- a/core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitTest.java +++ b/core-java/src/test/java/org/baeldung/java/shell/JavaProcessUnitIntegrationTest.java @@ -7,7 +7,7 @@ import java.io.*; import java.util.concurrent.Executors; import java.util.function.Consumer; -public class JavaProcessUnitTest { +public class JavaProcessUnitIntegrationTest { private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().startsWith("windows"); private static class StreamGobbler implements Runnable { diff --git a/core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamTest.java b/core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamIntegrationTest.java similarity index 93% rename from core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamTest.java rename to core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamIntegrationTest.java index c2eb1cff5d..42e85fc586 100644 --- a/core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamTest.java +++ b/core-java/src/test/java/org/baeldung/java/streams/ThreadPoolInParallelStreamIntegrationTest.java @@ -1,38 +1,38 @@ -package org.baeldung.java.streams; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; -import java.util.stream.Collectors; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class ThreadPoolInParallelStreamTest { - - @Test - public void giveRangeOfLongs_whenSummedInParallel_shouldBeEqualToExpectedTotal() throws InterruptedException, ExecutionException { - long firstNum = 1; - long lastNum = 1_000_000; - - List aList = LongStream.rangeClosed(firstNum, lastNum).boxed().collect(Collectors.toList()); - - ForkJoinPool customThreadPool = new ForkJoinPool(4); - long actualTotal = customThreadPool.submit(() -> aList.parallelStream().reduce(0L, Long::sum)).get(); - - assertEquals((lastNum + firstNum) * lastNum / 2, actualTotal); - } - - @Test - public void givenList_whenCallingParallelStream_shouldBeParallelStream() { - List aList = new ArrayList<>(); - Stream parallelStream = aList.parallelStream(); - - assertTrue(parallelStream.isParallel()); - } -} +package org.baeldung.java.streams; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ThreadPoolInParallelStreamIntegrationTest { + + @Test + public void giveRangeOfLongs_whenSummedInParallel_shouldBeEqualToExpectedTotal() throws InterruptedException, ExecutionException { + long firstNum = 1; + long lastNum = 1_000_000; + + List aList = LongStream.rangeClosed(firstNum, lastNum).boxed().collect(Collectors.toList()); + + ForkJoinPool customThreadPool = new ForkJoinPool(4); + long actualTotal = customThreadPool.submit(() -> aList.parallelStream().reduce(0L, Long::sum)).get(); + + assertEquals((lastNum + firstNum) * lastNum / 2, actualTotal); + } + + @Test + public void givenList_whenCallingParallelStream_shouldBeParallelStream() { + List aList = new ArrayList<>(); + Stream parallelStream = aList.parallelStream(); + + assertTrue(parallelStream.isParallel()); + } +} diff --git a/spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextTest.java b/spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextIntegrationTest.java similarity index 97% rename from spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextTest.java rename to spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextIntegrationTest.java index 8ee280a2e8..d49f63aea6 100644 --- a/spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextTest.java +++ b/spring-core/src/test/java/com/baeldung/applicationcontext/ClasspathXmlApplicationContextIntegrationTest.java @@ -1,18 +1,18 @@ package com.baeldung.applicationcontext; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.assertThat; - -import java.util.List; -import java.util.Locale; - import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.MessageSource; import org.springframework.context.support.ClassPathXmlApplicationContext; -public class ClasspathXmlApplicationContextTest { +import java.util.List; +import java.util.Locale; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +public class ClasspathXmlApplicationContextIntegrationTest { @Test public void testBasicUsage() { ApplicationContext context = new ClassPathXmlApplicationContext("classpathxmlapplicationcontext-example.xml"); diff --git a/spring-data-rest/pom.xml b/spring-data-rest/pom.xml index 1845d60e94..1e1ec02e96 100644 --- a/spring-data-rest/pom.xml +++ b/spring-data-rest/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.baeldung - intro-spring-data-rest + spring-data-rest 1.0 jar @@ -56,6 +56,17 @@ org.springframework.boot spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LiveTest.java + + +
diff --git a/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java b/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsIntegrationTest.java similarity index 98% rename from spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java rename to spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsIntegrationTest.java index 43b5dd7da6..e3fe60d487 100644 --- a/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java +++ b/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsIntegrationTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertEquals; @RunWith(SpringRunner.class) @SpringBootTest(classes = SpringDataRestApplication.class, webEnvironment = WebEnvironment.DEFINED_PORT) -public class SpringDataRelationshipsTest { +public class SpringDataRelationshipsIntegrationTest { @Autowired private TestRestTemplate template; diff --git a/spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorTest.java b/spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorIntegrationTest.java similarity index 98% rename from spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorTest.java rename to spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorIntegrationTest.java index 300fc081d3..bc321bc686 100644 --- a/spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorTest.java +++ b/spring-data-rest/src/test/java/com/baeldung/validator/SpringDataRestValidatorIntegrationTest.java @@ -1,11 +1,8 @@ package com.baeldung.validator; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; - +import com.baeldung.SpringDataRestApplication; +import com.baeldung.models.WebsiteUser; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -17,14 +14,16 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; -import com.baeldung.SpringDataRestApplication; -import com.baeldung.models.WebsiteUser; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringDataRestApplication.class) @WebAppConfiguration -public class SpringDataRestValidatorTest { +public class SpringDataRestValidatorIntegrationTest { public static final String URL = "http://localhost"; private MockMvc mockMvc; diff --git a/spring-security-cache-control/pom.xml b/spring-security-cache-control/pom.xml index c30b0cd1aa..f25e85012e 100644 --- a/spring-security-cache-control/pom.xml +++ b/spring-security-cache-control/pom.xml @@ -85,4 +85,20 @@ 2.9.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + + + + \ No newline at end of file diff --git a/spring-security-mvc-login/pom.xml b/spring-security-mvc-login/pom.xml index f208c74dc4..b3431da7dc 100644 --- a/spring-security-mvc-login/pom.xml +++ b/spring-security-mvc-login/pom.xml @@ -197,7 +197,6 @@ org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin.version} **/*IntegrationTest.java @@ -266,7 +265,6 @@ 3.6.0 2.6 - 2.19.1 2.7 1.6.1 diff --git a/xmlunit2/pom.xml b/xmlunit2/pom.xml index d4364292d6..3b659c49c1 100644 --- a/xmlunit2/pom.xml +++ b/xmlunit2/pom.xml @@ -17,6 +17,16 @@ 1.8 + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LiveTest.java + + + @@ -55,6 +65,6 @@ 3.6.0 - + From f673acbb47748a7f7aecfd89ebe000d8a20649bd Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Fri, 24 Mar 2017 16:44:28 +0100 Subject: [PATCH 067/149] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7737fd4b08..bcff16f5f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,5 @@ cache: directories: - .autoconf - $HOME/.m2 + From 319dd2653a073f310db05b6121696ebfa7049968 Mon Sep 17 00:00:00 2001 From: Danil Kornishev Date: Fri, 24 Mar 2017 11:55:27 -0400 Subject: [PATCH 068/149] Spring State Machine (#1424) * Neo4j cleanup * Neo4j cleanup * Neo4j cleanup x2 * State Machine Init * cleanup * White background, Java Util Logging * Change to Logging * Static import of asserts. rename test methods --- pom.xml | 5 +- spring-state-machine/bpmn/forkjoin.bpmn | 116 ++++++++++++++++++ spring-state-machine/bpmn/img/forkjoin.png | Bin 0 -> 50788 bytes spring-state-machine/bpmn/img/simple.png | Bin 0 -> 22706 bytes spring-state-machine/bpmn/simple.bpmn | 76 ++++++++++++ spring-state-machine/pom.xml | 31 +++++ .../ApplicationReviewEvents.java | 5 + .../ApplicationReviewStates.java | 5 + .../ForkJoinStateMachineConfiguration.java | 74 +++++++++++ ...HierarchicalStateMachineConfiguration.java | 47 +++++++ .../JunctionStateMachineConfiguration.java | 60 +++++++++ .../SimpleEnumStateMachineConfiguration.java | 53 ++++++++ .../SimpleStateMachineConfiguration.java | 105 ++++++++++++++++ .../config/StateMachineListener.java | 16 +++ .../ForkJoinStateMachineTest.java | 45 +++++++ .../HierarchicalStateMachineTest.java | 37 ++++++ .../JunctionStateMachineTest.java | 24 ++++ .../statemachine/StateEnumMachineTest.java | 33 +++++ .../statemachine/StateMachineBuilderTest.java | 35 ++++++ .../spring/statemachine/StateMachineTest.java | 47 +++++++ 20 files changed, 813 insertions(+), 1 deletion(-) create mode 100644 spring-state-machine/bpmn/forkjoin.bpmn create mode 100644 spring-state-machine/bpmn/img/forkjoin.png create mode 100644 spring-state-machine/bpmn/img/simple.png create mode 100644 spring-state-machine/bpmn/simple.bpmn create mode 100644 spring-state-machine/pom.xml create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java diff --git a/pom.xml b/pom.xml index 15e0e3322e..7c1cacbd33 100644 --- a/pom.xml +++ b/pom.xml @@ -188,6 +188,7 @@ spring-sleuth spring-social-login spring-spel + spring-state-machine spring-thymeleaf spring-userservice spring-zuul @@ -210,7 +211,9 @@ rabbitmq vertx - + + + diff --git a/spring-state-machine/bpmn/forkjoin.bpmn b/spring-state-machine/bpmn/forkjoin.bpmn new file mode 100644 index 0000000000..0cb060f74b --- /dev/null +++ b/spring-state-machine/bpmn/forkjoin.bpmn @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-state-machine/bpmn/img/forkjoin.png b/spring-state-machine/bpmn/img/forkjoin.png new file mode 100644 index 0000000000000000000000000000000000000000..642ab6949d18b49012529d7c5b9b14f909c9d701 GIT binary patch literal 50788 zcmeFZbyQW`_Xi3{cZzgMm!yPnC~2g-l$KJuyAVI#_x{d8Hc_1T6?b9^E2nU9~9&z(2xm{p`f79q$I_ZprBxJp`c(`5aEF<7%bXW zP*A9N=AxnsQlg^d3br;T=9b1#P?8^F;}KM%dmeXfFw*<`KMwYe8BQi|kv<^VGN7V{ z7WqJdsj}Oh_4P>g@mV}>3A7qssxSE_wsrDR%*3NcHGa?+uyQm88s}2-m>ToF!t2+6*Q$%cl0y&ge zWrA{UF~zzOy|%P_7F7$yH54eXQsb)%)atj(M3-Ha1POVl>8PRQpA-lFyWiyv-;+Cu zrbWea;a3XaRlYxRa#6acGCdNLf6VCMn`;<}TZzFc5z(V{=q2BE&!U-_^;6LPb#5Ng zBg#(0z+<)KXFMAH3jDUd>BNuGHIa_QNhN-;u3wvHc_%!(hjt9(N#Xbc1qDYO_lnKv zc|4tH4Au(o_erKq>c>~vubbz-Y(A5JJQzyc7ry@9&OQ4(Zz$0d_BcB{Ar8xwA|+=} zpNM5?(eU+r@h&NctUZ_JDYkCg2!1>EEZ?phvWsa7(A;Msb( z>U)k`s@&sGB)vT$gyAh|DEJBXq~)|*#Tkf&o|YOBL|Dudfh**&&X35xz*1^Nx9dcp zJCi(;dvusy-HB1|jhO?(!bVp0N#dK7*2N(K+);aaL_N7F<)F-HFPzKpo$*%&k0d`_ zDT_2}%0~izDFH<**7Yc)FzKY_S}S4oW6s3Np3Ul6tRq2BKqpj5vYQ5?6N%->&<6*# zHj)0O{w!Z{zA}sOER!Mzmgqb;sSvsQ*8QekSt_pL-p(2BD_IPOFx_V0dr!k`nZS|T z6~TH66{{e(g9KLE18S(#UV|Quj^~Pf{{Cz)*DT66b z9;J&&+qpkDT(h|?nF{>|?QR4|JAkB}#lW;&W(uhoiLGKxgToPzpML#A?4e2}_IJ0C z7_&Za7v~khxYM}Xxb*u;ehXP2g`-ws7AMbAXHn-NJSNN|r7_#Yw8M~oWB8-#$I1zl zacbyL!EFblLtWZ|t4*P7EM8T{buddRsG)_6mP{|8!h{}q3*HRmox@v~okFGT8in|~ zC=s!EVnV@nccf;*S9_DP1sYbupM8OIhSJA`{VL2U301XRobSU_;7Y0i8|O!<05j<~ zkO8v`)g;XP917i!<{eB5^hP`0Fp{+o!aJ)IpLhdIR6i_OVM#=^OscoR+;6-SC?9AezLB@~LNmc>!etqC!`3HH`TU_}9~&C*CGf5X zyBIEOP{#QpRXo*rrWUAbh^B$!4(2JhwuHJ4#(B>-_-$bM3iXTOJMOel)^_j`o-^}8 zaNP>eA*nm&y>tc^C!z_gsR%hmT7-0h1RZ4z)){sf)?hoygv1jX><_3}SR>H;1F&!S z-c!6sd(Z!qydri=eD^7Oq$D+06hTicuKb%wTPg9JlAPO|v7DV8!BK=tCe36^xic!- zsLNFtTLKrdOWaGoOI{bTm%`sc-!*A;0;#G%CaL|TiH{R4*ge>(aK%4jb;e@LN#!%; z$0!rhZO~!R+ZY=gI~b=L<@X&K#rMSZkw&%9%~D-Qeiq}(byYT15+AP_Cw$9zN_xs1 z`jR8ZWfd(NRj{fPYt{wVFJYVCNfZ#BGI zKblM-LXl17P|bjzNwGlXdFeZe*OHN?X1PyF6LRZczEA)0v=d*N`l;ws{ik6{nWNUZ zRnjf?pGqap^QyKOH{CWjHa&X9jH%W@YxZl{Yma(Kdi@xr8EP3S7>-pLROLV0bFc+c zP(?)cXmmaAuOGPJl(LK3px)1!h$f`bO+V3dsQetnpkAYKnz7A1@#?KCmwpXu4cc3| zx8dXO9h6VEwvWe)#)k?qWYyAk*%MzhI3F$doO3ClDThji1`sxJmA*wae`|hafy`O= zHf@4<_>Gy1S+0fEC|~YGnH<=0Y~9>uRIub0K{;Tnr{5@!V>Z!`kB|?Vk1|!4kA#n^ z@nz$)Mz}@<2eK{26XH{)6Yi~=EwpWmouF;j(K2wG4Y}cxS%gV>=aO%`nX?c*c<34rCif8t=ivnnxhFB0tMf z0#*gbt~K`6%!Y2ot=5i}%<6WfuIcUFeG?t>Td4rAG~|CUZQSr2QrC;q6Dc#)J7`I5 z!FQ{>G7!8I>=Yzvm2Q33iq{%`Yk#YByL}6PyLW?sRR@m=SRsM<1?mg&0|yDq!K}XyR#nNBRdVdFlwx*A5mvd zJxl_sK5L5h`1H(1AxtmqGVjI{V+_*B3xQ*`dp**ABq=2wQBH6<7{2vNa?{eBff`Cy zZ+A;2jcBF1^?2$0QtrE%iOzaEldYQ40&QjtX*PV$ksat`Puc~3wqs6z~!N+u{x(6#NYRcxKp^j zo~29`@54W3I@4*kK&qs6k(yO%$qkUXj=N+n(v{attGGFb>GK`+bz=OXR*n1iF$?4F zZO?uF6=ehE>ygx07DlJyuj)V59Exi-TO3ZoZ{&ZjVfRH9t|W6iK|Vtt%45Z`Or=V+N}iDk9L0(ho{ z&utF3lS6pscy$ad3>em9Q?;(M*Lx{@;39#~gpOwWbkjf0X>B&{^`J$AN7Topa;?-H;1F)oPBtl4a) z93tMAuVOx@<~!*slWJQKBzCXg3tou~%!Ze!BgrOXv=$@8=S1hu<#OPm<#Zy!;9Hpd zF>1TZ6SDYLelw*&pwQNKp1!y6t8#Y!@hglPWG#fjoU|>ged1C+l?f4*ZZl7PK^h)`>plyE(fQ#&DT^IaWAL^m^^b13wA5M zuWWlI-s~9Ed7Sr)GY#5!b~|$4K#rBD73*6fqo|#z+h<)WSFvNXFX~!8ts; zTk1eR%2)E>6E@8Kw^9D2oT*0&E6|{Rb|{f0f?Q;^N8KtHjDnsLZcyXjO|rsbQ*ena z%vl(kJ23@6g|$w=dXK|=!C&6o>^>VAVRg%xa)YWikvZ`AA>wa(llu70_O2V8C(}KA zp|mBFjrYD0P$wY0lhm|>f_nTE@()@{iTV%<3OdbPMZ;c0PL|Km#){>&k zz|&Ar0?vHES1V)t*W}JtmezKB&Mzn*p5Oz%LoTyYl0Q6RZ}EasLr#HQ)W+7BoQs8% zg^f}WnVg(lz}CowPf6_M@8!T>FDT9I?cedSvN}0Au{d$C*w~t~KI7%(Wo2V$WoKsw zo?y0fv9^Eh%xrB(^=p$q`-mCa8QPk^vp2V~CWq|%`i+f){R>J;$cz5^?-!lM&gOr= z$=dGsv48`zLhi6WV_{?cYj0qw0OTs4g1NJ?rG}Wfm9e!Q@D9OeY@D0|4-5Wt>+e_o zW2xrfOS!n7|8wO(?)+XUzzR9RKThQq+L1ebQ8!S{WI^^%)r%+!lrWVC5tyyVDLo&edt}+8Wli?`Z(h0CneMypAIMXmCvK!f zJ5J$dl6ynJApY+M7GjP>Idd}QfA6Syd&7{y!eWA;p#S+1tl{k~^37=XzYAb-FHrw? zbKqT|Z!oa9lr)A2|J>tYOY)2_rvJM&7$2_>RC-*WJsH?Sg7+q9jkfI{L5s& zNdxYuIENPlVq$=EAryOZzw4ZMw$7>9X5#Vfy6O?kG9qWcIbA$zv)IcpVtU<1dn8l* zTH)WWhAIMZgw($b?l8*RgyK`HN40Dsr#Si=Nu_F0tg?*Oxg|3%i7jr>IAf!GgGoAJm7PKUh~! zs^sIDiE1TXUpA&Z-xb@k(FCS|+$VEtNXabdyX-9y!C-(i9C_dbMB=$5XbaC zo(@zES3TrE*yR`}x5NDb9tZyRAj2%(cZC8o@=LOpGqAe)vV&*QoeIbdu=a+&|B_^m zFW|aW>icx408uWz_mK(GXNabik8{kgjwV`csL)A7YU19lRBv)WvgKcF9p(9Eq@|VQ zkv`8oJ2;DBZL`5e1g^I8Bwu0$PQ5Zj%G$T6%QB<55<{W@Uiq3JdZe-ZbCQb+JtoDhfWR-oH%=ZC!5=Lw3e zn_be2Y;-g!;V%|2FUof5w_-=lkCtkPL_Hq#R)7K&=u2z;C>A19C1uc9x0l{=l@ZTC z+rGmM$_c0k&TpBUc~?1;UWQJsIb4xABW#N1=ZT3KWr^w`O?^@c%H*8y&weS1O)nt#m3)u2t55Usr3! z5RJ$^Ftv@(!zVRAU%m1;b0f`c5&N`LT zVv=b*j|rl)fa>J}u{~#eZ~YC#)@+b07xmb33|+MvlK90|KKITL7;z2`JZolN5+p*v zAdN}nFzpS-qH5NC{q{kU-k?vw`frFyV-G2YN-?O!gli9J#;NelhQnL6s$>3_Xtl@E zvF`V)dvmoTtp_7(i2VBN$H>n>)nf{-RqQ>s(UwI%&UXyFmDjG4NkWwM$7cu%Ii!;A37k7Dw@rgG3+$PDN>% z#V!<(GYA(}e@E{NI@-;16$w!+uT=E~- z+0^BBrtPK-(p@XRwS@XJ$TdqX#r1(B|4iFJH?Z9b!RaqdUqa+|h=VB0c&?2%(l@WW zJ+s%Aew&!eV>LQe*TyO=%xyVkw0IzR;LJaiQRp`ml>S=*y+QskMs#9`-`D?K_$x;v z7YT$(hxx@s6f;K=O%B{ikJiCSuiO4p$5y+R>Bbv| z*3P|yJH%_+diUd3XC7561gGsHhuI1l#pwfIA05I#Gz`jz8joC*Ldu3IKH#tS{mR}z z;>Z!13XzS(L=UQH=_A<&ceh?*vs)1_)G{~7sQ%e<R2?9*YPgI@@L87hIYC` zeV#{~ON$L|aBq-DU!7;g1k2)!Z#>in*LJhB=QqE6GlWS%6bTf0hadN!bR2{P@FB*# zgW-X^DMYc+ps&j}9kog_dKzywu4}K^vDrB|_O3snE(%#G=gC$aJmMgG%zSoQ7!kKl zboRO{jKs0&5d!+uWv$fgZu6ea%#4ecYx^PQ8HtdRBKaXl%tv?~j$bz14u}mg?<@N+ z4SP_tUmtXe-lzfCE*c*oOj+Xc+1sg|Ww5$j0P~3Co=Bgy^vO=O%z_cVUY)iolDVF4 z8-vx0)CL9@-40&xJ0&9R&u-{8IZm%NB-`!HrRK|{EYe{*QV9M10%RaK;VS)-0eKqC zetT;*Ujj6Z%q%s-&b-Ul5_J{%Ug1BF`@mw8XGDN_LOwq*E7($VuTQqSZ=j0Uv^{x+{pz&qkny@rP90;m{{6NP+E77AP^EDjz?rJZwCwfA`Lc>{Za z`fVtUkcq#9x@h0OfyGIj;7UKbM=|aAsNrw<2@nky5rl~^H~=?xy4P4 zX$h0rbN))lsmo>2R@nfSfu^bR#OXxGwE4L@-IZ*@*>f(UNl0A(t6YM`m&9mI$o-qd zKi@!R(CoM(v*qv)wipQWIzRThQ9dFm)SUp#a_EnUW{;r~q3CU^a1PloEoqkIvU?w1`3wv23N-yGu#ortB?Q1- ztYQN<8un?`{eV|d7eg(=(C(IG|M*R3L>6GXIh&yyl@FTrkEL|sPz1P8l551nk|4r);I>56K|K#NYsr=xrUz&+mxl!==K*w|Vota;=`% z77a^YtP$w>5<+%Vn^GM*&woaJsC+#g$X0h|HM#vwjs0S)9cxc{GSu#TeJo;)W7(#`RHWC|IBu=g)E%SBQ*_w)ma|yVDAhVym6g#1yeueL zQBHTjtD~FvJ05`)0VVn9dWa5TLEm+GqTwmBx7F847B=4<}PnF&HmU z41k7{$nb$1&~V;w*swHMUT{0W+q0vX`_U?l?sWZeL4IdNTFSw0HnuvHKMa38=xOuL zM{`}d*c3zAA)@vko!jbzi1S6S#r>@~@pn(SjdnAm>~hA4K7@=}W3VC2hC!k+J;L?I+SLgg8$Ja4~?u%A^Z?0)rQ>K^Kg7cXOq z>MUc9vl6n5?ww$e*`uk+J?2f#y;hy~+oh=V+VD)g?$()ATpm2-f0}O>X5UuXRK2rU zI(0U-pFrbQw;${2bW+u&cX$J%dATpW_09x=ckgWMqZQc}%z%l;*&zGn-O7P+*DL;2 zg?)-bH!8)M~yg-}_M^`v{rO{Jjs{ zbs!l%Yeu_&scw_>!9vU0j}X>4>-okHXZ)g&sx3;_`+oH4HOcxFU^zZ?D4S$GV12Ka zy_9F;nU9i1{Ks6f<6D28mW8yQd6DfVohILZv07-|vC=&J3R9S(shLDNt73b;);lva zIb#ApU%IQd%a*Ke6(^-G8)aotJH5A4SIAld!Npou@-8?}|=sC)0(1RJA|gXMLJxe3JV(8Mz?CpuXQQu;VKpf#`~_)uv276^XTf!c*##)OEWMXYoA#q5$UIa? z-FBZ5JTG@nr?J+DWPKn?6t-Sb%EcGkVSu`SlLCdI}l$@qz;Dt!@sG`cia@>YhAo)D;J9qYDBWy>?5(rhA)jgGA*_;gL#+2~f)NRm*t zEKu^}H(w6T-$%w)sY_pv&Gu=o3?kPe^@qf2mST4HmJ*YMkiCa3I3?BSKsJI#s}N8 zThrM)OpyuQYx?1+F?tC{s~J_Js%Hc_s zo5EoWy=U<<{{s$f@8(Fh@!9UQ?RH`Q47!zWRbqE|FDW26?{grNa6U*=pn`aNOP}Uu zfn<~G-KwD(`5&AKm?2rv0a1LWOeN?n3b=24_gb~ zz1p!Hm-F>b7F9nut!jE7`Sy>FpPm$}41OlIe6JE`rBge?{eJjMFg#y%$2WziQ9GwX z-$(1>dQI=IWGlCgdWP`>rDKa3?uuMOK_Vz-$838AKZ~mdD-$*OcK1_w?I;Q1XfvS; z1c12R0u*LBZJj#g!1wGg`}0j}h52bwKs`KXLS1iR{~~mU$KZ4a91mo+4XTRC7Hq;H zj)Mizp=L3!(r;C?&aIjE;BmZWRkiU ztlW${i}7r4jJvMO*12GiSTp2W04*|3eqm>Bccj&GX0^f6C?d$pO zJrV6uPul$uWa$C~zJMA-7+SD;Ny3izKfTI<;M1kN-(ZszU zl`3?A>hfWq=z2!Z0%87TGG)s3QiJXGUVd+X1&49Z_|v0yDzad`GSbCZcq^jrb23jG zesxq-?s}^?y!V?Q!Y$b{C!|V@HbX@BB=KDDLKiJ@i4+hO6W^7Az0Tz~9I>s~f1ah< z^zl2j8gk(A-Q{K9(=@*%UxwrC=J&X==RKP;v^oyziKgv~pb*8rYpjQY9FF8yefSOQ z35SX7a-9y}dc&PhquB`M9AM@O3fg)9*Qtn5~nQ8zJs|MSl zeKM;c!B4Vj*;DW(O*G{hs&&vlziiAn9W90dt7JOj;3&{#E^rZ!M}&0gGYy1Um`Jz+ zp|Fy2)?yJ_t6KSM^DWIzroSOxX_Z?7*|OzMo7|IYkL&$9$0D4UDde$5Ni=xYUUg-I z^OfYsqov^eoiO7wFjNSjkvkH-Ii9zWIi%A!!IN;>ki$!nVV+Hs$;QZ{I6uC9Z}C@1W0$*V;|*FJ`GdrGo8 z-9OPQ&77?G7QC}_({U+&Hey@~Ypp$c{qBsn{Mvioa?jKPWvK8}U(l{Ww{*wBcH_f| z>%8Zk1Mj61txR~4o8&^fK%)Dpk!G8b$MVnlv!5wVjqDOYb{ntn>9V|uvCZ5X5_-jW-%%^{HRRkO$j9&yzc64E;V}`OCk5yB7(&C zuOpQ$n(poH9K+C_z)tF3t3Zmqk-SGUHolBjSGAEGU8(e{7Kx~EV~JT_=I-`}4a@UH ztEo*3k*aoTJwy7pmg*Me!hT4+sdgXtvWa5G8?x?y&evLNN?o5eG=y8;zi)Csy;84y zvyME%XU~@(eY4)W*zIK37R~drjUrv(_CxS9!ox0T?rzMj)4L4Ag?6$A39GMYz#^GFx zMaQhI$MvusVZ}3+aH@N7kY&Jl(UnT(fzx!h2YO=Mm&9S)zUQLnK4-y>oe3D`&bT7i}cj!wXfASHr8hE2++y+ zm`-OWPEm>xd0o`{v}wG{)E?l zNI+)-r%d1rtmF3r0;JhmE5@kEcU>e-V&m1BIW~Q%;sP707HTz#h(4lxfjH4+^ToBB zIZspe9Qe^xIL%B+CsCy9uN(HVxylwY8aHz~+t4R=lGjW|1<_9K?{b;IJ8fP&~tWTY~`9mzu@JfWkfqDVVkpM&HL>aeb7yXHk%ax?Z9^FR$$rV{E3 zse7=HfE+hCBowHkeSq;#?YhC*vI)>vIi7jkCY<%tqgOs=S@~4I~$) zP662L-(;-v1jzY?LHA$A5u9(~WoD;cGtG$fXD(frJnSh6C&?JD@@el6wBmCC0JNC;UV{)0{XWaD1Bn1PA|mQS z0mldh)AJOR{|P|OqW9i*r%BcF_ntmHD<%UVoMuwC#Q&h^fi@*c;0A8vvyj~a zE8N*{xaimB?;#*Va{0fJA!t}>05i(OC0BbuBUGB9Jm^%KpPAA%K}~ZQ*BQQgIK%2= z2z+$Fs{X4%@i*b10HCZZz*Iy3`1iG6@3Tc2Uj1tc^(QFvH6**D0=hp(HURLfCBI*i z{O3|E6o5H@rYpp>z2ynAnR|a`(Sy!pIk|WrLzaWskpMo)Iz?OjX_@}v1^G(|eDkMq z62tVR?#t8ba!8}+L3)Qt05+x9L^Y1^=gM!P`oLlW=YzJ!Ru%hI&I{s3-J;W;okqTh zo;nYo`Nf(PU^Sj;U*sP_{@FxQ3&1#~+$plOVEna&5|g|{0EUTM{LgIx zz&?EutYK9D(yiZGMm_=n+>+J&FFhchgc>(P`8#!Up_ILp(Gc~>z^c;2V_6J^uPO18xFhGc=|RJv;cm{<6up?uE92=rEKWfN3~_FQgO--Il4SZ< z8`Osyk|eJ3B=kXmxMrr|Tp4_GM=_4+miKk_LENn1_{Q9{FRXoDktE5Ek^oli?=7-D z?^Oyo7QhOP00I0FbcQb+yx#NSwAVdFGL}^cJpRe`1r#N1@GTe%(wFXA)MDYE+?V`pvGN968zdC2-*Ot;5)6Po zPtQ6P+yujs`6OlVp!zTdf6!|F_9i<9Jpd3@4fQz{cSvys6;*a4~j*;55woada^Z^`zl@V zXf20T96jXZ#_Cm}N{!Oo&PzBY84!o@<2#Q`rkl2asI)W|pyvRz_ID`}fozlID2?Yg zNuL2VDRkXvum(=|#Sn(ie|5Z2ITt7`kJe=LduMrCnfX0)UcrUW=jD`!c`lgfaAdP3 zuo-n@ngg+^!oqs7)uY0g>6dDg;{oO_+1WJ61u-R3yv@b7HusC=piuyC8Cl0*)jzDX zf8OS2PdFt-=y+9w)+AB%ss>xd;P~S}<^Ioa%4a{pJn*XinyLS1+d-ctOlPA{0be z&aXfcp3a+@L8}8ii!nev6p3TiXPNbyW+_@P#!g&mxl+ph_IOzSF;_%}sOVMrI;OMg z3)d6G8Cc49{0k4hggk-)(%@W^w@U?10P;mT*#WSje0l*K<#H_Y&<<84#a8ck~D5Ra{f#e8tz{<`V6oEddH3i9Y9>(W(*SH?^Fu%&oRrOdG_H-o3Nseb@_+ z3Ixi0qr(e{c-|m5Br^kHT1e^w5fX609d1*UqV+C?F{>0fok3-gA z>H|#tdAhOt&x?@VsB<8ROtY2BpP`Mv_WGaZ+Jhv1eE=m7ki@K7Wsm-QuW#yr*Koh( zg_!y;N*~At(lvU(6;ap_#XfYS|G8Hype#RjhNk}$c_aaPkz8gAIRCxZPXPS?f5|a0 z6(qZ7U*`UMui!G^h!uhZs3x7S8p{@>0gWy>Gl4MvoMwdA+kz0cky%Qo6j|?-svur6i=$=m^;%P%zS4|&w9ii zvU{y$1a6|_H%&7C$&&c|eOBs>qZwoN%dn`OvNkgY_u2rg0LExx(v(W*1H`$=2CHyk8SC57wrT1^VjHd-20!NG3WAZEc3}<&1%^ z3Hj=cR8DpeyIGq;PS1UU_tz~N{>;ek<&Nc7vD+WUpUdU^^k`kN*VyJSHQ{w{y3uY} zi_^~W=sM&6k^zl|3{yzCL?-+E`w18OeHj1hYOR%V?PaRjSccDDwwNFCOdZUh2=E{Y zB?AUyz6>bvhOcgbhT z`66*X_Z1W-v+rNyL)v1{N%*@Ho*DXE2E^R7NPP%FN2Td!qv9Vg(wjHBBW6hnE6`S7 zpLa~Iwi)k+$LUs){16V0|1ePU%for&9omD0l?$5Co~wNqI?P$!Ac=!-!#6(KUhBY+>xG5$p~f6G5b@$j@^k!B;6Zb<@tsH% z9!EzZQ)+MK=;vPDY=$p94yW>KAQzR&Xn*?8cm$$@eg%(P-R7?hpTaZ?-izrU%q#Q9 zKu+TEZ~XY2X^8Y~l5T?KhtGSRCUmGz*t|1Di1#1me=7Axp>f~Gmo3oGeyKs*Q>rG% z87-VFT%Q4xi2Qn_0_!jHfyBF|25;c~uIV${==+1Q+q5CoS7iiu6%OuI*doS^Mx0pY zFVv}vg1YZ+ulMRIjTop`51?bYVZ?orN49o2yy}kcCV8z~@E7cO@Mwh8JCh|cHAC3o z+2>>SET0F6!?+WXY{Cgu*A0A8o^x`NaJ4$_)dUQXJyBv879v)880Grq`h2iq1+Wx` zGw~5n1qRL%6>6ego_qBe(fETcoBU-$_C8mm>bDe;Z0slt1Gb9l>kCYd=$L%Xmx-S- z_{tA7MMOnkW}E3FA0)41SWEVxur@0IZCv6|Q9IPXJu6T)dVABtAZH&CK9v!=ddV=s zkxkpYertlx!a?z_lmKr)_Lh-Hx++^}&f2vlpL|Iba9ni#vcQCdttm}CDjW}3HozRL zZ{vq2BR8DocD{d7xHN$Qd-{;|{Z5cXeCiQ!t8u;~g$o!K%^PJ&z6|+hwhkD^y8}ip zTyue+L65>NPbnq1hK#c))fZ0*6n|W3~@hKbb{K$*6;l(Pie72h5N> zJlg#)CJSy%%ODs{{HK85hX-3oNT+Rv)CcIpbs;q zU)vS8_=OLpRP2f zmyjQrur~Sxl!N22OP~MNZUI)zIvR{C=B<+RgnVn|?fa2Wn-Qwjg?_}`jP^&a^ac4A zM@#EU6GrrND^1>R`wd;_^joJgnVvn&OG&)-249K5I?4%&n4cSbSupBi>{09FIq=pO z+^g)_7aMmXqD)gA9+PkdLaC84L&o7N;6na>H)e1dXpS8>&FxbEsPAKm(kUIs9Pq>C zWIogj*Mius#bZ={oAH9kRQ1nB-_1CmMj?7e=9D(NPN^gV6e)|@?`q$JaR+M& zRh-whi;NH3?wK!Sj^CzPpKWztrYF844yT?vJ$~1GeP>i!wAu4q?AX z%dm(#9`k3*{mIFkcUvYZNLG%S17zJQ?>@F(0z(#iA;1{QOoqrvP4lmzr3c6Gg$U+- zk3zx=x7wS-OTNNUc`<%ARJBTmiSiJRQ4iI*7P~r+#c_Me{7z%kDV}FH6+pq|R%`#W z^UY_|ai*WUt{xM7#=+=`=Y#wQ-Ip8b`ajN!Wm%wa<6Q3VZbvgkgX(n>;Bf}oJZS3s zVbN$}CQU_(8MKhif&gi!G;d5Y#1e znn$%113%7qy0Z3O?(cM4iV`#4`|GDVk6ZNdCo0W_er}jftuv)s`H-X)fyo z=0F7Phe2UUI20F7N3u!a8z1PclzSrW`URtwWo{PNa`9c9xx#XriDZwV+@ZLH_{a#^ zT*QavH2%ceBwZo+E8u_ePDJrGMvFiS!s&cPq-~LmLoaKhx6o!8ro0DGJGmN(g7ip z=J2#}3#y{H9Tp!Gu`bQ}T5pX&{^|VwUee z!T@AM>lE{h9yBqIp*3;}%k#a6p-c7GNcj^!FUBN{Uh>)a1tueC(*XRQen6??AKvTeT_boxYwL}WsZ(SAH; z456Fy@q*=@1a2L#+JBfJ`6SFU3_7NJ%Yc^ey}HuqboKp@JT^zABpimNBwU7jj2Y@t zVdCkR*im}x_&Jdh3^uqNZERG)Tgm% zI*4!T1N=xvf-uo$!4D5#V^}t@k~;tql}0Q((a+0(T%*O0`FJtExQr+P!5l5nOd_<9 zJtv3TYb2Ay3FvoR%RON?l4R5SJzM{f#V*nNBybN;*cf2!3es&kh>sFoz#ylM(n%Og zUsLjwgh|G_64<_zN%N4L0k9}e0E{9wH3Exa57!<-M%zA-@N*m0aFu7sQQVmaDiRC2 z*F*Z%S;B#%AJJ$|cIowtH7K|_+IO|PP66U6x4sh|}W$moqpx?0RQ@H$DMoU*+K!V;{?6xXgRZ zRw75SU%wI-;S*rc4hw|WoUC?#ghjWq9J$_V`9~Pb@MI^jb?J^;-QK>XU_rdRge6IK z1ib1Y*a7L0e%M=^P;bk2A`*>K;D}6x~9Xo&gdO_Xi_ zA-Z5WpcU2^$ISTCxmZ>Z+CyO%T&6#Tzs~;nM3_TkBNV8W=xGd=*>R*TtT7#*GB|gP zZWjVStV-Ik1zO5^BJ8O2g`%RsoLYr#C!RVk02C%Dw3Bh8hyR7(U^+n9IOhjUgwbua zYlCAa5qzWHc@WhrKB;D|lrJ3$7!6yw61xC~vc+kwK4wIl@NMGmzn5+z)lFXyJ1g6%Du%dC;j7XKwqL^o>?7bUMvV}E96o#);ePOl-kz$|QSR)jhF*hHC-^o-U1|3#wsD z89X|l<1QTl+1HTRH{Qc{Hj<`b{KYt6$`a`tbH-9DFx?sO?fQA7PPiw3p>m7sX^sp& zhbPU`EjVCSO56|Ws>C5jod0kSBu-tfw09gugYrH73%HzO>x_#C#4Hj+$=JB$o@5^8iPGY5ED&up~)qQgnwi7G)mjVMdgZl!J73&$o4a1&$_pNDE57QJ6AWb z(183_X#@s{m0lY8!;7)o6Gb6sH5mhMIU~xAdax7OjX&D&Ov-VBxJ?I%`Nx-rXzX4` zY-t-ro5L@+p0b*gs=>V}SCn!%(XZ<;dXu7!DXS2_Q`$5LuY_IL6Jerc7T4R6*ANKL zkuR4_10*vw&;!6!=Lbj52B2m2q`(gOFN&dIvuH8LP~q?uy0w+B;D8v#gE7}}`k{Qm z-LP;CNN;M70Rc@laMKT00&{5m2$<5*fBs(p={z=RsZC@vbD%yc+Ek;BSVoI8?_f_V zK$HTUX2V}vop$z!+Q>4uH|nI8`#LN10ss1^4ALc&#$A&T}k&zJOCB)X8Tr+Vnsw{;93@cP1RzrR$pQS9Xl@V%#w zIiHDrn~>ecU`=wvI<Mm4oG#A~TWqGffhk?uUSq`}I%I*vFORtW$fqc{B5Dk~&}SF)z!ook z;jvG&ccVVS4+>tc;_xo};}{Lh)Ua;`;IF1)QRN6Dk#L=Hc4Xe&#VAYjSw=dfCm3|n z3f(CDKUAG%TvS`z#-&R_Qd&T|rMr=CMH&Q#p;J1C5>Zm=Zd5?J8%263=~5|4N$Gcu zp7VI#FMi-B%$~jXTI-Jgb=_75AB0-fOng%yu{5(@3XILy96X*h6A@Mek>ms`E{Kz0NUf^{oxqQv_&D zEzS_Wel}rN^mfsikpiB>KyB-#P2|j%h0IW&uQ(g)RtaZC5+53dNf&q$ep5va&qcQ; zoOfw`H|WINI!27@`+Y_vm@7;J8~Jg5ifql~g>Mi$jYeL(AY=OuE*)eM3w0M-ClTZ7ePo0U2HvrGy8HRHmaOd@Ut(d0m;Xy|C&bt7nFiJJy zk5-2FpC(31KOEvmh?$|iwX>HN2jBZb()a(KK(~-auzD{0>YVp%|xn}b7$rJ-=%d9frZI7`?zD&@mqZ}=e+u!^?TrAPIHSzWlyI7gP=66vv zr}ZBO2a_qIOpwRb={5Tokm*;x+Ouu%HT{vM$n#G=YU|iUuQuv9Cf@w|78?!ec+WS) z9Fg)8Gm}|obTAC3dr(a&$sE=!l~%g{JyCPEzJA^BH;#aV&WR|-zp5@b)3y1#}c)ZOXf1k zi$L^keZaBjSOkl?c3BCzxY$IN&ete;!fEgr&Qh@%0CYt_80_-6 z5i6CN3k?D`b!2`;#H1EC=hQrO(){%c?;o-5pX^x`nRi6%^XJcT(cBHw6jT{BqN<&$ zN-GnLdJq_wW_Fr2ey%$I(Yc954F+?3i#x8c1i0CP3^LWk{0zA@@sxSFuA>TMefhDH zhSQf|=hn4QC%@3cte3o=L62|wG8iUP(Wb(ad+U6VZ-f}v+zt|bQY`%+e5Ksq&xM}sz4;UV?38*@0yBp@@grzDp2l+ z7v{wSPR_E=^8%W}2cH>s4&Jcq&-#@3PQO{}T|uGuGd;p3BTt!K%#p$BHf})S(S#xe zu=tOP5BBDNlXkq$p_YoF6442qZZS}7`x((9?ol$q`nl;R@_Pu=ZU0pg7#5O{T)O1t zDtA!4*HVB!f5-p3>}bS&(+UFxTGd`I>eYP>qPskqsTdcYOVLjlCt0TjHmc@@##Wby zjYl0&Z?1`so`meFib`$~>Pqjqio>upRQX7`-)`H(2z*+u3)(xS{xO@Xxx*Ehs^z~X+6>y9<1>5N`BtVVNCH+L zkRJH{I)BL0^aWL^Mx-~ituXCicZocoYiGXEeVMD}VU8@?B_By3C~{Kh{hI0s8LRQ! z!Ir4F=@j5w8JjXoej{~dm5yZMv-&3C4M_nJNwsbj9`5Fz_ShDX5l+XDgAGXKup}Yp z$zQmPKq#aEjrizABrYY>H(FZmZ0<|rlWUp#TUb&UQ(^a9cI%^4BwV(QxCG!gQH{%Hd@!bZ5CoBbem?mQqtyeKc!OADlnJ-<1v*hg z${=c*2EUj>pZgH$rdK9g40um zk5*iMqup}4=(jV6@=dKBMJjApQzrXSi{$DJhui*08y&NzQ`T5H>tAQw8dpmYxhV`qe3Q(*YcAR0DG|>JXClc3SbO=w68Q;@aV?F zVX;C`lyIBA&#+DF1Vf+8^NFeyOFBt+`Bi1-=Hgt=&$qPkLjaAGdWTLg&1<<1ykGQa z{JJZR>si&*0)dthuc@F9Na}Xx$R>5|AYH4@ZORZ?y+?mAxL|#>nwxYSt_Eou?`0&) zTf#dvJ*YC5H`S(^7VH`kAJ5R zB}yU9du`48`|fLlcXo&yUB7+$SVhVomy?Qdjf4hdpz&~!Com_cNydNA>2e=Ey5_1^V<`kqa%lTyX9_X*xf!gHu zNtVZ$vK2G2u7yksV!0-ztA!7k2Gq_E(K_(RL`LxM++Z>FT;RC}A~J=LC)m)WAA5nP z&Gf|+c%E|BoWE=sYMd5k(a*io?zNLn;U}N;6_R5ka9v;ll7+>>qBfExv3hm7lnl=F z{#;G9!$?XsP|RW2cUi(RY_E|qsM;bjV}5zg=@3oI`C`H5s#l>R76u|0<09FGrf1e= zXZh=HM0&rhN3tGRvq5<*HYQ3@iCVn#ONkJ>zO)*uQ|s~LQoT|gxciw6Cln**dImF% ze8cR>`6;Ir^RIXNfX!*Szg!v&>jLM2T#;rr*Q2+3@t0^tq226i(w+RF`%gt$My;5P zjnz*$POX_Xcv3oHSiYRVQzX#h^-T1XY$gFb2Zje~CuxE0j_EJ?18C(z--c`4rptQP z200bWKj-&8s`Es;U8!jW{cz|BmO?ctsN&IcQPR2SUY&Z!V(r>z-wu(B!*CivP=fbN zk+X1ESbBg?i;cTwAGQ;H@5X5Sa)prZyt{X?UoXy<+-)PHKRkfRIPh#0=ni}(*0cPa zDo1HNM9`2b09Ccp$DCb5Ens&TnR~~I4GKZ-M6FoR{_%(2(i7%Z z$7{7k#wlXxNjWLYq2_KgH25y`$m+DY?V7Mg-_&u9;5~|BSvxu*{~Qb^ori_$IpJ+0 za~Nr!FF9@A)qbPSLpSR5;P^K}TjCOe&_jC)(`-h4)_DFKu}0i)JQkK0b(GDUMd{zu z?8_@{r_U(yRTm}bedNX%g92|)o=r>_C=(g9`BX!-ESme|_qjpuk7T^4&+c4p-)04&RR??EW9bW> zh97U=(T_nvBWg$k)LStP%J1}50<^A2?j8dT^Q-R}=hEdvv;g&Rl(qn!Hbk~M)AaP+ z_jHb`mkexuEADv8v#F4p@UNll@@{YG=V-1??;mDzhzK)k;-%e#k-N)QRaZL_tbh!i zi&qQSB2lQZXf`*}zgfXWb|rL?36E!Z*ne=mwQ5MD=RNDeljaymE0E%{I_FHJ_I$i` zrRMjQM$zR2uwHJN-BiiIT^tNey& zW;p$JhD8fTTzjdR!P;gQP|6wN1KGCz(+;{pib`_iEP{P}w)Z?T;ah5ESooQ)5%ZV3 z&8S4;u93e*Qo*2U&95qRp>tO%(7$wYqK!z901#;ql?9A|C=hW3hrCWo@ILP67)nLn2e>Rw~A&4B z;n);oQ=Zk_OsE%}vZDLm^vJWE{Lap7ZFMOGWUQ44PBsJTupH=W(@n_k^Fh(B8;9Z1Q!Ont_*`Infp}slz?rz`X zACg{QQT$Lj+Ec!w^a5-w;>6KPucShxz%Qj`tyq59+j_!cwl2=MDx$A@4KCQ=$^#&| zbO^Qp*W_ukI4mwwvk~{1{)XX~DJ=o+y-NytcpE{D&>|_P;d68X51O|@VIBFl-wo1* zp?aw>=_>37weUL(iqp=THCXUB`liY}bOz4CQ@>3m2n3L>jI=M7e8Gju(&%1jOgFeP z%wW-lKLG|Gro>7OVD0`09o%2(c1(|8nB$sRz z8#T_+xEb4!u_(1%9M0(|c+7(VQT-4|u~<}-I4U=%odWpR&yjZL8>_)#6DlpsZz1xy z3+rfSZt9B&Pl+@S-HH<<$#0PJd2TS|m&Dc4_{Vb{_xfllIky35I&;R1s)m7(vlRFWwD3Wj|f>wpEOc#Pq%1EJt5C?>Od zxyXcK&N_#p6V}9{0p|4=Dsm_`D$y^iKRE|b8D4Xe3x5qA(6}q?a*vufX|Q(|ut3W* zKtJRdX+l>#msjt{?HtFH_^BlP6bzGsV!eXUZA>^@)@w#M(%1jg`7?2-rW}o8p>HZt(v3 zbaXQWgXRM{j;L%ZgiDJ};~pyt_ODzdcnEy`zVwEhS&@mx4_{Vn}u736c5pe5kS3+|r5u0xp8LXAkc23Vlg)GzF5 z8DDSU6g<3%qq_6Aw2Oo)iv>WL5CyS(F;Gl)gg7pXE;7VHk<;h`0coOgy1TGiJ7pT} z!_&E>dNI?y--WZYmn>MFBaZ-lJtRaZ{u~a!_gzNp>BS#7iu42TjSUu?S4nLx)8@*7 zK1}im>JkDDI_%#GHjp{aZU-Nn#1p88f>N;=fbAJAKfR2y4`xW4`W|k1M{B>!BnJ<( z;JGxSe1gfE?SG*B!k9Pt51s~$M0Q>~bwn}uL-;f`RTI6Onv^x1<{m>ZP)R`cpyly! zk#5-)2{&x>v)}2wdu%c7G$<(8qu`+K<{gHpoJ$htb@yl!D5-^ja{C+MOAWC7>_Ay6 zn?!elRhVz^v@M@G4gU^2f~8x~jCm|_$)mRKjwg3z+GNlbrkix#($#eHsF_-rR^lA*~X-r3XMDQDd>1iw5xKr>K*N^rmGbZ6gnWUELK!e zXSRZBk%n%vN9>JNt%balZbNY1f>d(Xx!#T|-- z29F9-C53bw6fV5)ItDHwMA4uF)D_#DE?d1CJ{1<}CZBQtk?51ctsx8*~hc zFXIv-X2=EXUK_jhF;+eOh>Z{(P;Z@1TgKM5Zjg$2KNm`~6ZF{7B7b1@d_k%Q!tn=k zzn6<@%(HR`lOzI-sUfN^)|tt-QIH2vK1?z_IEa9`f$Shs=>ZW?Jf@`sgH! z5&4$0LVi$nM)8;A7_`3r3@%Q7?c*2B`?VeQOM&*iIBg)!Tpibxs->1$U0dr1l-s>l zztbeCo3jy_ikqGKr6g9}qUp+NO{psnU@~!G4I;8CAWP4gyWpO$wuuFXH6jNG1Kg_M zBr8;iCPkPt{0nOnV=?hNvaMJ#p+eYec&=z$Pn(x!)X=DP4iK&lo#A1qxns%d?3f%s6FAkuOs5Kz69;Kty8w3*S*$anf}p2L~((|;oWP+1eg@;*%msU=LEk@)1_MM6@xbB z8!9=>+U5m=%$54&Z^G_7T?wcv-Ykx{VW_aDMdxQSE;<&yg}k_bm!Qm%J@s%AQlvkq zH=?#e+$woxk{`wlaDH*-I@x=#!YsHY9dj$3&?Bxyg z?{n$W(_U=qYgG=bV3ItCJA``;5^HG5v=fhS-DZY9#Y;U150>o@LcOJg7SdF7WvzSo z@YM|n-eV3f>{CITE5Q9^#wHpa9eE%}CUiT`d5~7gsLlb)*l{VAR!VYX|8=L}_1w}m z6DXt8j!j8R>liefx`?nks5rIDo?wHJN#6g$;|I?uO6L< zt7X6I1I>1xt5Zx!FgBDH<$P7wb``U_(}9m#e>Q^Gw1Pnvx>Fli_$C~OEGf;_XqH#U zS^{XqfR&?-3Xb@%h{hQJ22T-=BBQarDY z1)o$-pmH{O?P+9VleqGUKzNaGpT43NtD8RPKub#n$(vp5liVt_uZ_lAg~vF|B{@^7 z;~1fjROoWFun7RoPJ*;UrL78qK;Wtxnws@PcRf}YTm3?vh<0Tq9s}!#N zDGjPqJGJFz?UEY;&U1C@Cp&XCQ?)>6upzj@HL*s7h({KT#oDk2lQ2((9sY6VY(YE9 z{g%-du(1^zmZ}pamnaon@~gB(X{|x!9RnxL zD7N}#;0x6kvLTQ6sa*jd@M|+gRMpu_tasj#3|CtI5~BUzlYmm!3P9m(xf zz7n8ndrtwi$z$_rgvHs-5Q_(GG=h z)|kl#omo?8b$PgVWKQ*}7ie*cU?#BPn$1wb(Lz+Reg6oU=XH_Xmw=xi$-e4Tg_>al z+!Z)vh*}k#B<5&ulPd1>XTi_A*}}3M&{_#=|=bBE4k+uAM*MKI)7J<*rYqqh`<)6t)BMz_MtNbwAp(kHhSI4 z5tK|aGFJ(F;AmAEtTmT$-J`f!`P!NlqCG)DQ7!KhjPp=PrUpe4e6tCr+#n6`m+;S}o9k1aI+Y=Hl>yYK=zhSq6gn@p9ej(G>c8`l zijK_0BYuOx3vt5ST&_#`N0TQvcPZ=2NTXn-vH83@OF}pbRdtL0cxL}$<>spWhB@f^ zFdMNqGc))qh}mQS?aId9)k|`|?;vrhH&A{>DHOA)Ec}nUPn!?~KrO&83lQ+}8Bc5b zx=fy)a~ie>G;dG0o7tszNSpBeh(|}~SGuEYo0Z8quAVKAGT6POGJYw;M*?Cx#`BL< zXdzYD8#X34itMFF0_-C^iIN$SY)+Z!k+WQe0}DRL>$x#OFAQ9ZQXyfp9UR%>K2_qE z``wosXuJnj(kqCpp)(8-Nyk7g$KScTyRX`hYjA!r1}?x409=vEV*$$ux;_he$G#oV zGHhf)))SJbBHex*TeCr+XBK!?xY4%cfA|V_&DLC#_EPQ*$krnaA6fa7?6B#VJIn=; z&ZPV!kwwKs$Q4i0Blkcf1ND8>kWoN~Xt&jH6M~SHAG7Jg8*luXNE^uFoDyWC?=(wX zHO8q{*S-8LCUI-1u}dOw$8OT<%PXONw42Lfjt84F=X+-`G#u|+*rgf%?0Hugxt*w2 z0qmu~JJxpf9SoO-EhWfk7Vl2^2)=N+nC1X%wKvkWMQl)z5sjE%RZaq7t-=$NWSH(< z)fZay*q+>0H0>uo)5e2cnWK!8QN;dULE^{3OWousinh%^pBSPDI@&70UtR?e~s;Fz8|l!CINcrO3fWMGDv2WCc`7pB}sH` zEh@2l+@CTW~T}QS#ZjSho^FpbeU2?fl)NwgNCQlG!AW}@28ag zXyLXBomL3@Q?Q^-Bw&-HK{L;F>D*t*TOe}murWm=fa$W}}*qAc|XZpyht?vB7^7k`dU3LsDhp_FSBHa5## zC7#Zsy+eQ!^CpGebXx#Ef2mXg}&A zoDviaS3ETzc!YS!gk?u_&n)8pCB1`v(*SFI- zSA<@E*^9J?(O`eSsrOk=Qk=Sc zhZUb{o=4<;tbxk1MXBiNq0@=Ye47n-;l3VOZ#?zO^xK`;YdrODUvJ4yt=JP;IzDU5 z@>}&EKJ?Fufjn%$UveMhZSqfU-sm{`dN*TtzPGVL7vKFNt}tO{j`jPUr#2Ee?n?bT zp4QSb)t|Q_1Bdu4tU;X$F#H&s&h%a9WPTh9L{NbA^6cpi4aLRBTP#%=*0MP_-$Y|G zb-q%}<7)g)nBGpeNWOcu*ZYsv0iq`aM;7IuYzHE1KMrV@A}$AUoqd%vPj2{T>b?xI z1ksUdpDwnd_O%zS(1IOC_EC#C+dn6Gat8Ijuzxoag zJH#W=(#Rr6>c~qW(iek$awtX#8M|{>bK=BY26buYmD*~5cu5XA(N~f8Y&~V-)dR;! zXm2J;=@A%Ndav}--gKp$DJb3skZX^Y-oHq_xeFzb@e|qLp1~EG?-0+sv*}9kZ_#X7 z5@{%WRjSoH$52#)lwNr;g-ne{q&~wO#$^7oP7O05OuOQ&MEQi;?n$V7MKM?NAo{J0 z)3z;F2#bQ}Yu!S7jd0hsrEXLbf5ybqXBL4{T><7_Bo_-Aa*V8EMj1yL(|kAFGpUNY zR{Xm%vOJn4?r6D8)Y`~4d+uz{Wp}+)b{`%+zTtEK6+_Oah={@cgrOQ?2-m?Z?6%&Y ze~kcu@ z%d1%uDCo3b(hY`5(@dL3k96RcwZ&Z*YmJy}s18{Lvrkf%VnO>su;61Uq++lBe8n`f zAv*?TJ8PJam!92AImm#82O9+z@;``G@>K93;Jx^ zfq=cmiSN7Ef`fEV%%vlx_=Q?2kV$Y2QN_3?JaO4tY_j%V&da~L9fVnsPBr)R>I%qd z6_svLA1vR{iNi0{*Mv<7O)`8@-&*E4;&$n!tjL~)&~KI{+`N41eHxNV_>kz6-rIsi zFDiDUA+rq8fczv7uF=d{m=+cnM?)4S?#BcSnI*KXf?7_sb33nrtKZRvh1nL*b71B< zJ4F!&w~P_n6PcE=<*^q&LkRtPZ(+-G-vB-=R|3bBTpzH1WhTLdf5WApwO9$1mv_vP5Z){ZH}p*gshdl8F*G2FKDGi?>jMA_&V{{t zrdA`EM);#`)Hu@eUwAY`kMJ(<(=hRy%R^pf;cv56^m29T#%1R_{-5LI6;y%EzwGfS(injB& zEf#rYO$bB~otP%zNPE!cO-^5rO>a{8%>9R==5`1z)hueoMo zap?hu;>&<*Rx~R1#YJvw&uJ=Gj2#0Qj`=w%2t$yfybDAb^%dEr;}={G!Ipd214 z4h{H{ZBye^#$xKSayI9#SFlZByg+;oOlr_EqV4I6ByRA~eWTb^o8o(#Il zW%Tt!`Q)zGTBn?Y&qV&UbSF?EMaq1(1!C>m#tIK%dyAW^V*o(P%jcht`WtAJqv77a zP5z(-*t{4_a@*WU%YmMlEqc&)>C5z6E+(epAs>CIeS-$=R; zhIq>;7IP1A4oLJsDfbOfHFE*Gc)_>_Kw{?FHiPFaC+Mqj@5>VY4sSu@CXD2&gL*Ua z(0od%=R|3XiYrH}`@U)+6%mWiHeUvJfyr)6&ezug*<`kKfv=TZ4Nitq5vlP~GEE9DP5V+EMJ^lKDsAaF*l z#+&@u`*ZivV{1^wO#L+Zqv?58=0~Jbazy$voQ>MS*qG5AWJVEG>_d>{)oEEk2#ox$5_=FJ~g_kwQx;LaP* zpmIKmmeP@e&^+gz_?*T%{`RE63ySxEq5*D6p40EqiOPB9AkV%;XpQrr&rY=(R0r3? z7twWks#v=bQsi>XWpn&0hthusW*uq6IfU<_?U~s_UoXIswRJee z+f=a51$p%TBZ#JBrJo%w)lE!jp)loQBZXgzb_VGj(DsV`n;3y-@sw~HB6(jEIB2hF zG=R>ZWmCkS@^#-DjM3=!BxUER?{!5*;~K?iD((5n4m+iqX1KjNa(5-<@dxzDw`a5z z`&X{3{e9VLV|x#oH1Qg4Y>4(7CT?FqpB@B06F;*W{dQ_&-M3w9Z$0y+%ngpJ277Zc zET%aYBQ}gq45R+mY!WGAd^{|62aSkdTf~Rh|G4F>=|ej$#p`i88PMFt$Id|t9}nGq zwvLBO6UKgSocjWuVlvWAEjYM~7svFkg$Qv-@bHhEr8GMpx(T7|_{CXe!D8JI`D~zrtD|md{#-kt>b4J_9IoAr_*etdGFiLi_x}#i#Vk4nTYjs z9(i@QQ?C(|*nX2Om#91rYd7xQ`Ki)}_fIF*u^)M?$*2S(MTmbtU8epwGz^x`Kx-S> zX$$H^BO^ZVPjk?SZb$04qbx`Ij8+p?W)$!)?Kuj-U&vX0Lh) z0&HE3_4(?pFK=wP+r_`mV~Gx~u)(nK%=+JdJH8kN(&{)!c6w{`&{rv|d+>`mGPa1! zkRQa*IaA$a&F+cFU|E@a$k=G=)BpDT0B`jW$p%~WCY9uAW4MOI=4+)yPSM`psxK?} zqU_;oHcx5;ns-X$8oiw5Wx`(+VSIcCT0F^ujuZoBhr=RE_xt>6Z8S?E5f;BNIFVbY z%)2H<2Sc*3d?HE@=uO*c(}V@|obv+Ej~J>^&&_uo#yRMlpql6=qHA1*gNDi8Wa~}C z0T~yf$1Xoe9LhSwBrMn_EAve=5mrl``k_<*9xAtxT}j~IpNlq!CUr6Kjav{^w4NBM z^^xKnYU!uvbRb$}q9*eZz*C$eQCG$$1oOyZF~|swNbfErK~C~nAEQpSPGQO0T&%ma zKgK?yGrmomx}L&2G7ZI=YPpdFfmA`_w`K3^zzqU&>Jx&um%ZD!PgcYyPPC?8_O@xA z;??dA@d)WBg33&!2-~qI7#DIpo5B1)tE(64(#_`fS~54hrj2f^vJ3hZ$4O^A+0T1I zL@#9y1?D~H9IL+EPi@)h#$U~(bgpSdr}tsWOwGNpE$%C!4UFq!s(_t%Vm{{A?0z$p zKJaRdO1!M$*xom;CvZrxNkQA4YoI~5$T6&I3^iY-$#FqN#c?R{d+o{y+ z=JJH*thDB92`}ocx3arYWUJ+wK2{kQif$&<@^AR!*nW*t5R};xmg*dUCc@{vKf};| z7fS#Y>>xPKU^iO2fo*g|dlbI2s+A%N)eHv#!2rS$cVNirLVBq%)0PZ$dW!CcR z)rBjFCI-N0HVDyMbh(S-nLE4bSLW-a$0wfy-QDbd%A&p*HLxT+JJm#er1qH(z@S~j z>i>%iCkWuI47EKBIm_t0H>`}#XUix)-;JB4wMq*u3S2M90w<9DJ~vxY?s_i`a3RBMmZURDB^28$S# zp@kzYr6JGt(*NCD~$)(g%My&;yr^p z9i(YtV+-#Xy5JlAy*mUf1;KpZb2IvA;~W(H*jj)N(o>~xlBoo~bUTQpJPW%S9Fg|D zuLf|KtgNgE)RgLId6LCPfZKf6m+y@3VR!*9ZD*%lhLNJsy)c(m{GeZ`o02DTz}BuO9Fws4EhIo>8msa5=7-f5=3{` z2wqY2eqzg!`ZlX;0jh*_+_{Z+exwvCe9p(-eV^7`nz+=o93N5--KU?;YbC5{TZs^b z?viD)9f0;h>r;KKPI|5?c<~!vk58(dO+;6k6XZb?R zWwmSQr*1Ru10qpnb1b(#deNB+KEh!?^mUB}o6>Eoar}KZ32woLN2Ta?C?R4FusK=X zfBA^Y;J|1x-y>*xiEH1Nvax|9z3H=yy*t^oeIc`ablq0-#Wu3{4XKDUL%`6EK89Fv z!jlBQ!8;Us(|1ok_fX{=|FBF_f?*+K!$(IRA^@2R#bZ_()NR-*MxMPNfz_a@AOrZe9BJ@4A%Y$)(e3|Q8>k=^7t4b6Yn|Cw8fO8+HagvjE-eO;p%ax< zmbvU}$Bq>Rh8($DVDgJ?dn3LV#!#tCq=Bj608;gR`VVoAyJBM;Ug?a8it(LtP=+2| zG70zsTCVAQths>R2phAY{om<+3z-)as@0jQbC8@Vwknu-ac?#QyFC?b4hwQ>>LlKXst+h-hAldh$kINT|QFU0NrhQIWhWH&j+** zrRh?Ql5PIpLZuP}+L4)Dq8%wUFX{SpIs<4WKlAjC^3mn0_YQSqil1-fYon1Ixm6G5 zVNnW*eD#CO*d%$Qk{yW>4dzMEmGecPXgsgchlxxunIn?><3e@unLGuYi!`Y&a3Wzk z_uTn=)?@Z?EN7Se{4&3SKYSan&7M;2*9HR&i`U-$sVg=>nK$6M+W5F|8l2ho+-QT{ z2hr$c9;6XaH%{nYXdi_9X%f!RL5Trr3S(NH2vL*g4x0v#3<{P>6SRya9{Y97k#x-8 zuLerP)2JEpuj&D9#j#MM%wMwa%tzY#HZ^IOHH~kj2)&F5mf`(CR1`7XmUIr$yO?(g1x|rt zYi~Dr!rwHi4w6MJ_srJXN8>$wtDPSqVF-$KJcvKPfr{BKzy5Lb|NnVZk|UI%D#y9+ zj}zHt+}R1#o~OPhGNFg($e3Kdl^3l=)IM^Xs)Lza�gf@SIUg-QO`umQ3S)QlWiV3@kS`RLu_ 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 D__yV@ literal 0 HcmV?d00001 diff --git a/spring-state-machine/bpmn/img/simple.png b/spring-state-machine/bpmn/img/simple.png new file mode 100644 index 0000000000000000000000000000000000000000..7a79bf1d895a401b2b59d4b093e770f3546bc3d3 GIT binary patch literal 22706 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 AC;$Ke literal 0 HcmV?d00001 diff --git a/spring-state-machine/bpmn/simple.bpmn b/spring-state-machine/bpmn/simple.bpmn new file mode 100644 index 0000000000..8ed463e9f9 --- /dev/null +++ b/spring-state-machine/bpmn/simple.bpmn @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml new file mode 100644 index 0000000000..5393626083 --- /dev/null +++ b/spring-state-machine/pom.xml @@ -0,0 +1,31 @@ + + + + parent-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 + + baeldung-spring-state-machine + + 1.8 + 1.8 + + + + + org.springframework.statemachine + spring-statemachine-core + 1.2.3.RELEASE + + + junit + junit + 4.11 + test + + + \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java new file mode 100644 index 0000000000..971fc5dde7 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java @@ -0,0 +1,5 @@ +package com.baeldung.spring.stateMachine.applicationReview; + +public enum ApplicationReviewEvents { + APPROVE, REJECT +} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java new file mode 100644 index 0000000000..1df2db1f86 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java @@ -0,0 +1,5 @@ +package com.baeldung.spring.stateMachine.applicationReview; + +public enum ApplicationReviewStates { + PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED +} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java new file mode 100644 index 0000000000..c55104a627 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java @@ -0,0 +1,74 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +@Configuration +@EnableStateMachine +public class ForkJoinStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .fork("SFork") + .join("SJoin") + .end("SF") + .and() + .withStates() + .parent("SFork") + .initial("Sub1-1") + .end("Sub1-2") + .and() + .withStates() + .parent("SFork") + .initial("Sub2-1") + .end("Sub2-2"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SFork").event("E1") + .and().withExternal() + .source("Sub1-1").target("Sub1-2").event("sub1") + .and().withExternal() + .source("Sub2-1").target("Sub2-2").event("sub2") + .and() + .withFork() + .source("SFork") + .target("Sub1-1") + .target("Sub2-1") + .and() + .withJoin() + .source("Sub1-2") + .source("Sub2-2") + .target("SJoin"); + } + + @Bean + public Guard mediumGuard() { + return (ctx) -> false; + } + + @Bean + public Guard highGuard() { + return (ctx) -> false; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java new file mode 100644 index 0000000000..708dbd3077 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java @@ -0,0 +1,47 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +@Configuration +@EnableStateMachine +public class HierarchicalStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .state("SI") + .end("SF") + .and() + .withStates() + .parent("SI") + .initial("SUB1") + .state("SUB2") + .end("SUBEND"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SF").event("end") + .and().withExternal() + .source("SUB1").target("SUB2").event("se1") + .and().withExternal() + .source("SUB2").target("SUBEND").event("s-end"); + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java new file mode 100644 index 0000000000..e1bae10fb7 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java @@ -0,0 +1,60 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +@Configuration +@EnableStateMachine +public class JunctionStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .junction("SJ") + .state("high") + .state("medium") + .state("low") + .end("SF"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SJ").event("E1") + .and() + .withJunction() + .source("SJ") + .first("high", highGuard()) + .then("medium", mediumGuard()) + .last("low") + .and().withExternal() + .source("low").target("SF").event("end"); + } + + @Bean + public Guard mediumGuard() { + return (ctx) -> false; + } + + @Bean + public Guard highGuard() { + return (ctx) -> false; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java new file mode 100644 index 0000000000..4e11851644 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java @@ -0,0 +1,53 @@ +package com.baeldung.spring.stateMachine.config; + +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +import java.util.Arrays; +import java.util.HashSet; + +@Configuration +@EnableStateMachine +public class SimpleEnumStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(ApplicationReviewStates.PEER_REVIEW) + .state(ApplicationReviewStates.PRINCIPAL_REVIEW) + .end(ApplicationReviewStates.APPROVED) + .end(ApplicationReviewStates.REJECTED); + + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.PRINCIPAL_REVIEW).event(ApplicationReviewEvents.APPROVE) + .and().withExternal() + .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.APPROVED).event(ApplicationReviewEvents.APPROVE) + .and().withExternal() + .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT) + .and().withExternal() + .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT); + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java new file mode 100644 index 0000000000..fe4e0f82ce --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java @@ -0,0 +1,105 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.logging.Logger; + +@Configuration +@EnableStateMachine +public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter { + + public static final Logger LOGGER = Logger.getLogger(SimpleStateMachineConfiguration.class.getName()); + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .end("SF") + .states(new HashSet<>(Arrays.asList("S1", "S2"))) + .state("S4", executeAction(), errorAction()) + .stateEntry("S3", entryAction()) + .stateDo("S3", executeAction()) + .stateExit("S3", exitAction()); + + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("S1").event("E1").action(initAction()) + .and().withExternal() + .source("S1").target("S2").event("E2") + .and().withExternal() + .source("SI").target("S3").event("E3") + .and().withExternal() + .source("S3").target("S4").event("E4").guard(simpleGuard()) + .and().withExternal() + .source("S2").target("SF").event("end"); + } + + @Bean + public Guard simpleGuard() { + return (ctx) -> { + int approvalCount = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); + return approvalCount > 0; + }; + } + + @Bean + public Action entryAction() { + return (ctx) -> { + LOGGER.info("Entry " + ctx.getTarget().getId()); + }; + } + + @Bean + public Action executeAction() { + return (ctx) -> { + LOGGER.info("Do " + ctx.getTarget().getId()); + int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); + approvals++; + ctx.getExtendedState().getVariables().put("approvalCount", approvals); + }; + } + + @Bean + public Action exitAction() { + return (ctx) -> { + LOGGER.info("Exit " + ctx.getSource().getId() + " -> " + ctx.getTarget().getId()); + }; + } + + @Bean + public Action errorAction() { + return (ctx) -> { + LOGGER.info("Error " + ctx.getSource().getId() + ctx.getException()); + }; + } + + @Bean + public Action initAction() { + return (ctx) -> { + LOGGER.info(ctx.getTarget().getId()); + }; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java new file mode 100644 index 0000000000..bb7859c683 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java @@ -0,0 +1,16 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; + +import java.util.logging.Logger; + +public class StateMachineListener extends StateMachineListenerAdapter { + + public static final Logger LOGGER = Logger.getLogger(StateMachineListener.class.getName()); + + @Override + public void stateChanged(State from, State to) { + LOGGER.info(String.format("Transitioned from %s to %s%n", from == null ? "none" : from.getId(), to.getId())); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java new file mode 100644 index 0000000000..416da5f0fe --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration; + +public class ForkJoinStateMachineTest { + + @Test + public void whenForkStateEntered_thenMultipleSubStatesEntered() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + boolean success = stateMachine.sendEvent("E1"); + + assertTrue(success); + + assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); + } + + @Test + public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + boolean success = stateMachine.sendEvent("E1"); + + assertTrue(success); + + assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); + + assertTrue(stateMachine.sendEvent("sub1")); + assertTrue(stateMachine.sendEvent("sub2")); + assertEquals("SJoin", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java new file mode 100644 index 0000000000..3557a63211 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration; + +public class HierarchicalStateMachineTest { + + @Test + public void whenTransitionToSubMachine_thenSubStateIsEntered() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + + assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("se1"); + + assertEquals(Arrays.asList("SI", "SUB2"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("s-end"); + + assertEquals(Arrays.asList("SI", "SUBEND"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("end"); + + assertEquals(1, stateMachine.getState().getIds().size()); + assertEquals("SF", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java new file mode 100644 index 0000000000..d0c1225c9b --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java @@ -0,0 +1,24 @@ +package com.baeldung.spring.stateMachine; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration; + +public class JunctionStateMachineTest { + + @Test + public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + stateMachine.sendEvent("E1"); + Assert.assertEquals("low", stateMachine.getState().getId()); + + stateMachine.sendEvent("end"); + Assert.assertEquals("SF", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java new file mode 100644 index 0000000000..1fd7bd85f0 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java @@ -0,0 +1,33 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; +import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration; + +public class StateEnumMachineTest { + + private StateMachine stateMachine; + + @Before + public void setUp() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class); + stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + } + + @Test + public void whenStateMachineConfiguredWithEnums_thenStateMachineAcceptsEnumEvents() { + assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.APPROVE)); + assertEquals(ApplicationReviewStates.PRINCIPAL_REVIEW, stateMachine.getState().getId()); + assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT)); + assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java new file mode 100644 index 0000000000..cdd1e951e0 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java @@ -0,0 +1,35 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; + +public class StateMachineBuilderTest { + + @Test + public void whenUseStateMachineBuilder_thenBuildSuccessAndMachineWorks() throws Exception { + StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); + builder.configureStates().withStates() + .initial("SI") + .state("S1") + .end("SF"); + + builder.configureTransitions() + .withExternal() + .source("SI").target("S1").event("E1") + .and().withExternal() + .source("S1").target("SF").event("E2"); + + StateMachine machine = builder.build(); + + machine.start(); + + machine.sendEvent("E1"); + assertEquals("S1", machine.getState().getId()); + + machine.sendEvent("E2"); + assertEquals("SF", machine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java new file mode 100644 index 0000000000..1b442bf994 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java @@ -0,0 +1,47 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration; + +public class StateMachineTest { + + private StateMachine stateMachine; + + @Before + public void setUp() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class); + stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + } + + @Test + public void whenSimpleStringStateMachineEvents_thenEndState() { + assertEquals("SI", stateMachine.getState().getId()); + + stateMachine.sendEvent("E1"); + assertEquals("S1", stateMachine.getState().getId()); + + stateMachine.sendEvent("E2"); + assertEquals("S2", stateMachine.getState().getId()); + + stateMachine.sendEvent("end"); + assertEquals("SF", stateMachine.getState().getId()); + + } + + @Test + public void whenSimpleStringMachineActionState_thenActionExecuted() { + stateMachine.sendEvent("E3"); + assertEquals("S3", stateMachine.getState().getId()); + + stateMachine.sendEvent("E4"); + assertEquals("S4", stateMachine.getState().getId()); + assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount")); + } +} From 3140ea166d05c59b605eb3ac0b5d55a116344c6e Mon Sep 17 00:00:00 2001 From: bs-santosh Date: Fri, 24 Mar 2017 21:49:32 +0530 Subject: [PATCH 069/149] Bs santosh spring mybatis (#1479) * Spring and MyBatis integration maven project Complete article is available in http://inprogress.baeldung.com/wp-admin/post.php * Spring-MyBatis integration example This code demonstrates how to use MyBatis in Sring environment. Full details could be found in article http://inprogress.baeldung.com/?p=31706&preview=true --- spring-mybatis/pom.xml | 62 +++++++++ .../application/SpringMyBatisApplication.java | 48 +++++++ .../mybatis/controller/StudentController.java | 60 ++++++++ .../spring/mybatis/mappers/StudentMapper.java | 19 +++ .../spring/mybatis/model/Student.java | 55 ++++++++ .../spring/mybatis/model/StudentLogin.java | 18 +++ .../mybatis/service/StudentService.java | 9 ++ .../mybatis/service/StudentServiceImpl.java | 40 ++++++ .../src/main/resources/mybatis-spring.xml | 54 ++++++++ .../webapp/WEB-INF/conf/mybatis-spring.xml | 51 +++++++ .../src/main/webapp/WEB-INF/jsp/failure.jsp | 36 +++++ .../src/main/webapp/WEB-INF/jsp/login.jsp | 81 +++++++++++ .../src/main/webapp/WEB-INF/jsp/signup.jsp | 130 ++++++++++++++++++ .../src/main/webapp/WEB-INF/jsp/success.jsp | 35 +++++ .../lib/mysql-connector-java-5.1.40-bin.jar | Bin 0 -> 990927 bytes .../src/main/webapp/WEB-INF/web.xml | 21 +++ spring-mybatis/src/main/webapp/index.jsp | 34 +++++ 17 files changed, 753 insertions(+) create mode 100644 spring-mybatis/pom.xml create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/application/SpringMyBatisApplication.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/controller/StudentController.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/mappers/StudentMapper.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/Student.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/StudentLogin.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentService.java create mode 100644 spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentServiceImpl.java create mode 100644 spring-mybatis/src/main/resources/mybatis-spring.xml create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/conf/mybatis-spring.xml create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/jsp/failure.jsp create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/jsp/login.jsp create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/jsp/signup.jsp create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/jsp/success.jsp create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/lib/mysql-connector-java-5.1.40-bin.jar create mode 100644 spring-mybatis/src/main/webapp/WEB-INF/web.xml create mode 100644 spring-mybatis/src/main/webapp/index.jsp diff --git a/spring-mybatis/pom.xml b/spring-mybatis/pom.xml new file mode 100644 index 0000000000..b0eaab5b3a --- /dev/null +++ b/spring-mybatis/pom.xml @@ -0,0 +1,62 @@ + + 4.0.0 + com.baeldung + spring-mybatis + jar + 0.0.1-SNAPSHOT + spring-mybatis Maven Webapp + http://maven.apache.org + + + org.mybatis + mybatis + 3.1.1 + + + org.mybatis + mybatis-spring + 1.1.1 + + + org.springframework + spring-context-support + 3.1.1.RELEASE + + + org.springframework + spring-test + 3.1.1.RELEASE + test + + + mysql + mysql-connector-java + 5.1.40 + + + javax.servlet + jstl + 1.2 + + + org.springframework + spring-webmvc + 3.2.4.RELEASE + + + javax.servlet + servlet-api + 2.5 + + + junit + junit + 3.8.1 + test + + + + spring-mybatis + + diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/application/SpringMyBatisApplication.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/application/SpringMyBatisApplication.java new file mode 100644 index 0000000000..acfaff2669 --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/application/SpringMyBatisApplication.java @@ -0,0 +1,48 @@ +package com.baeldung.spring.mybatis.application; + +import java.util.Date; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import com.baeldung.spring.mybatis.model.Student; +import com.baeldung.spring.mybatis.service.StudentService; + +public class SpringMyBatisApplication { + + public static void main(String[] args){ + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("mybatis-spring.xml"); + + StudentService studentService = (StudentService) ctx.getBean("studentService"); + Student student = new Student(); + student.setFirstName("Santosh"); + student.setLastName("B S"); + student.setEmailAddress("santosh.bse@gmail.com"); + student.setPassword("Test123"); + student.setDateOfBirth(new Date()); + student.setUserName("santoshbs1"); + + boolean result = studentService.insertStudent(student); + if(result){ + System.out.println("Student record saved successfully"); + } + else{ + System.out.println("Encountered an error while saving student data"); + } + + final String userName = "santosh"; + Student matchingStudent = studentService.getStudentByUserName(userName); + if(matchingStudent == null){ + System.out.println("No matching student found for User Name - " + userName); + } + else{ + System.out.println("Student Details are as follows : "); + System.out.println("First Name : " + matchingStudent.getFirstName()); + System.out.println("Last Name : " + matchingStudent.getLastName()); + System.out.println("EMail : " + matchingStudent.getEmailAddress()); + System.out.println("DOB : " + matchingStudent.getDateOfBirth()); + System.out.println("User Name : " + matchingStudent.getUserName()); + } + + } + +} diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/controller/StudentController.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/controller/StudentController.java new file mode 100644 index 0000000000..427613f23f --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/controller/StudentController.java @@ -0,0 +1,60 @@ +package com.baeldung.spring.mybatis.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.baeldung.spring.mybatis.model.Student; +import com.baeldung.spring.mybatis.model.StudentLogin; +import com.baeldung.spring.mybatis.service.StudentService; + +@Controller +@SessionAttributes("student") +public class StudentController { + + @Autowired + private StudentService studentService; + + @RequestMapping(value = "/signup", method = RequestMethod.GET) + public String signup(Model model) { + Student student = new Student(); + model.addAttribute("student", student); + return "signup"; + } + + @RequestMapping(value = "/signup", method = RequestMethod.POST) + public String signup(@Validated @ModelAttribute("student") Student student, BindingResult result, ModelMap model) { + if (studentService.getStudentByUserName(student.getUserName())) { + model.addAttribute("message", "User Name exists. Try another user name"); + return "signup"; + } else { + studentService.insertStudent(student); + model.addAttribute("message", "Saved student details"); + return "redirect:login.html"; + } + } + + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String login(Model model) { + StudentLogin studentLogin = new StudentLogin(); + model.addAttribute("studentLogin", studentLogin); + return "login"; + } + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public String login(@ModelAttribute("studentLogin") StudentLogin studentLogin, BindingResult result, ModelMap model) { + boolean found = studentService.getStudentByLogin(studentLogin.getUserName(), studentLogin.getPassword()); + if (found) { + return "success"; + } else { + return "failure"; + } + } +} \ No newline at end of file diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/mappers/StudentMapper.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/mappers/StudentMapper.java new file mode 100644 index 0000000000..cf3584f7b1 --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/mappers/StudentMapper.java @@ -0,0 +1,19 @@ +package com.baeldung.spring.mybatis.mappers; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Select; + +import com.baeldung.spring.mybatis.model.Student; + +public interface StudentMapper { + @Insert("INSERT INTO student(userName, password, firstName,lastName, dateOfBirth, emailAddress) VALUES" + + "(#{userName},#{password}, #{firstName}, #{lastName}, #{dateOfBirth}, #{emailAddress})") + @Options(useGeneratedKeys = true, keyProperty = "id", flushCache = true, keyColumn = "id") + public void insertStudent(Student student); + + @Select("SELECT USERNAME as userName, PASSWORD as password, FIRSTNAME as firstName, LASTNAME as lastName, " + + "DATEOFBIRTH as dateOfBirth, EMAILADDRESS as emailAddress " + "FROM student WHERE userName = #{userName}") + public Student getStudentByUserName(String userName); + +} \ No newline at end of file diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/Student.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/Student.java new file mode 100644 index 0000000000..f33dd44f72 --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/Student.java @@ -0,0 +1,55 @@ +package com.baeldung.spring.mybatis.model; + +import java.util.Date; + +public class Student { + private Long id; + private String userName; + private String firstName; + private String lastName; + private String password; + private String emailAddress; + private Date dateOfBirth; + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getUserName() { + return userName; + } + public void setUserName(String userName) { + this.userName = userName; + } + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getEmailAddress() { + return emailAddress; + } + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + public Date getDateOfBirth() { + return dateOfBirth; + } + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } +} diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/StudentLogin.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/StudentLogin.java new file mode 100644 index 0000000000..867857b510 --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/model/StudentLogin.java @@ -0,0 +1,18 @@ +package com.baeldung.spring.mybatis.model; + +public class StudentLogin { + private String userName; + private String password; + public String getUserName() { + return userName; + } + public void setUserName(String userName) { + this.userName = userName; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } +} diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentService.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentService.java new file mode 100644 index 0000000000..d26115beee --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentService.java @@ -0,0 +1,9 @@ +package com.baeldung.spring.mybatis.service; + +import com.baeldung.spring.mybatis.model.Student; + +public interface StudentService { + public boolean insertStudent(Student student); + public Student getStudentByLogin(String userName, String password); + public Student getStudentByUserName(String userName); +} \ No newline at end of file diff --git a/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentServiceImpl.java b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentServiceImpl.java new file mode 100644 index 0000000000..538e650482 --- /dev/null +++ b/spring-mybatis/src/main/java/com/baeldung/spring/mybatis/service/StudentServiceImpl.java @@ -0,0 +1,40 @@ +package com.baeldung.spring.mybatis.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.baeldung.spring.mybatis.mappers.StudentMapper; +import com.baeldung.spring.mybatis.model.Student; + +@Service("studentService") +public class StudentServiceImpl implements StudentService { + + @Autowired + private StudentMapper studentMapper; + + @Transactional + public boolean insertStudent(Student student) { + boolean result=false; + try{ + studentMapper.insertStudent(student); + result = true; + } + catch(Exception ex){ + ex.printStackTrace(); + result = false; + } + return result; + } + + public Student getStudentByLogin(String userName, String password) { + Student student = studentMapper.getStudentByUserName(userName); + return student; + } + + public Student getStudentByUserName(String userName) { + Student student = studentMapper.getStudentByUserName(userName); + return student; + } + +} diff --git a/spring-mybatis/src/main/resources/mybatis-spring.xml b/spring-mybatis/src/main/resources/mybatis-spring.xml new file mode 100644 index 0000000000..9f5bab3247 --- /dev/null +++ b/spring-mybatis/src/main/resources/mybatis-spring.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/conf/mybatis-spring.xml b/spring-mybatis/src/main/webapp/WEB-INF/conf/mybatis-spring.xml new file mode 100644 index 0000000000..c8b686358c --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/conf/mybatis-spring.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/jsp/failure.jsp b/spring-mybatis/src/main/webapp/WEB-INF/jsp/failure.jsp new file mode 100644 index 0000000000..66f16d4e09 --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/jsp/failure.jsp @@ -0,0 +1,36 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + + +Login Failure + + + + +
+ Login     Signup +
+
+
+

Student Enrollment Login failure

+
+
+
+ + Oh snap! Something is wrong. Change a few things up + and try submitting again. +
+
+
+
+
+ + ">Try + again? + + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/jsp/login.jsp b/spring-mybatis/src/main/webapp/WEB-INF/jsp/login.jsp new file mode 100644 index 0000000000..5a895bb348 --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/jsp/login.jsp @@ -0,0 +1,81 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> + + + + + + + +Student Login + + +
+ Login     Signup +
+ +
+
+
+

Welcome to Online Student Enrollment Application

+

Login to explore the complete features!

+
+
+ +
+
+ +
+
+ Student Enrollment Login Form + + + + + + + + + + + + + + + +
+
+ +      + +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/jsp/signup.jsp b/spring-mybatis/src/main/webapp/WEB-INF/jsp/signup.jsp new file mode 100644 index 0000000000..bc628862f3 --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/jsp/signup.jsp @@ -0,0 +1,130 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + + +Student Signup + + + + + +
+ Login     Signup +
+ +
+
+
+

Welcome to Online Student Enrollment Application

+

Login to explore the complete features!

+
+
+ +
+
+ +
+ +
${message}
+
+
+ +
+ +
+ Student Enrollment Signup Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +     + +
+
+
+
+ + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/jsp/success.jsp b/spring-mybatis/src/main/webapp/WEB-INF/jsp/success.jsp new file mode 100644 index 0000000000..7ae37bc241 --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/jsp/success.jsp @@ -0,0 +1,35 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + +Login Success + + + + +
+ Login     Signup +
+ +
+
+

Student Enrollment Login success

+
+
+
+ + Well done! You successfully logged-into the system. + Now you can explore the complete features! +
+
+
+
+
+ ">Login + as different user? + + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/WEB-INF/lib/mysql-connector-java-5.1.40-bin.jar b/spring-mybatis/src/main/webapp/WEB-INF/lib/mysql-connector-java-5.1.40-bin.jar new file mode 100644 index 0000000000000000000000000000000000000000..60bef5cfbfbecf98b7371ba19389fe792be305e0 GIT binary patch literal 990927 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

p?9?j-;qd_@Iq|IAN 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�|x-gHa$IbyvGKSEgJtrf{vp> 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

;w|$ja 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

R;2v*H!yTle2 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

e9Yy+$?WBotD+=cy;Bf$CS|h@$$g-WwRw$pCp>R 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%0AuT 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%0AZz6}`@}C(k{P*>q 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%0A$oqtWZ(TPN1 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%0ASlEEB^J_* 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 literal 0 HcmV?d00001 diff --git a/spring-mybatis/src/main/webapp/WEB-INF/web.xml b/spring-mybatis/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..569b5ef9b1 --- /dev/null +++ b/spring-mybatis/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + + myBatisSpringServlet + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/conf/mybatis-spring.xml + + + + + myBatisSpringServlet + *.html + + + MyBatis-Spring Integration + \ No newline at end of file diff --git a/spring-mybatis/src/main/webapp/index.jsp b/spring-mybatis/src/main/webapp/index.jsp new file mode 100644 index 0000000000..b60222e7de --- /dev/null +++ b/spring-mybatis/src/main/webapp/index.jsp @@ -0,0 +1,34 @@ + + + + + + + +

+ Login     Signup +
+ +
+
+
+

Welcome to Online Student Enrollment Application

+

Login to explore the complete features!

+
+
+ +
+
+ + + \ No newline at end of file From 44f5742f16c7020de4487670f33a1e6527137308 Mon Sep 17 00:00:00 2001 From: azrairshad Date: Fri, 24 Mar 2017 09:55:02 -0700 Subject: [PATCH 070/149] (BAEL-746) How to Copy an Array in Java (#1474) --- .../com/baeldung/arraycopy/model/Address.java | 61 +++++++ .../baeldung/arraycopy/model/Employee.java | 25 +++ .../baeldung/arraycopy/ArrayCopyUtilTest.java | 163 ++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 core-java/src/main/java/com/baeldung/arraycopy/model/Address.java create mode 100644 core-java/src/main/java/com/baeldung/arraycopy/model/Employee.java create mode 100644 core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java diff --git a/core-java/src/main/java/com/baeldung/arraycopy/model/Address.java b/core-java/src/main/java/com/baeldung/arraycopy/model/Address.java new file mode 100644 index 0000000000..43c6d77fe6 --- /dev/null +++ b/core-java/src/main/java/com/baeldung/arraycopy/model/Address.java @@ -0,0 +1,61 @@ +package com.baeldung.arraycopy.model; + +public class Address implements Cloneable { + private String country; + private String state; + private String city; + private String street; + private String zipcode; + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getZipcode() { + return zipcode; + } + + public void setZipcode(String zipcode) { + this.zipcode = zipcode; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + super.clone(); + Address address = new Address(); + address.setCity(this.city); + address.setCountry(this.country); + address.setState(this.state); + address.setStreet(this.street); + address.setZipcode(this.zipcode); + return address; + } +} diff --git a/core-java/src/main/java/com/baeldung/arraycopy/model/Employee.java b/core-java/src/main/java/com/baeldung/arraycopy/model/Employee.java new file mode 100644 index 0000000000..757a8f8ec1 --- /dev/null +++ b/core-java/src/main/java/com/baeldung/arraycopy/model/Employee.java @@ -0,0 +1,25 @@ +package com.baeldung.arraycopy.model; + +import java.io.Serializable; + +public class Employee implements Serializable { + private static final long serialVersionUID = -2454619097207585825L; + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java new file mode 100644 index 0000000000..2c9a97c496 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java @@ -0,0 +1,163 @@ +package com.baeldung.arraycopy; + +import com.baeldung.arraycopy.model.Address; +import com.baeldung.arraycopy.model.Employee; +import org.apache.commons.lang3.SerializationUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Arrays; + +public class ArrayCopyUtilTest { + private static Employee[] employees; + private static final int MAX = 2; + + @BeforeClass + public static void setup(){ + employees = new Employee[MAX]; + Employee employee; + for(int i = 0; i < MAX; i++) { + employee = new Employee(); + employee.setName("Emp"+i); + employee.setId(i); + employees[i] = employee; + } + } + + @Test + public void givenArrayOfPrimitiveType_whenCopiedViaSystemsArrayCopy_thenSuccessful(){ + int[] array = {23, 43, 55}; + int[] copiedArray = new int[3]; + + System.arraycopy(array, 0, copiedArray, 0, 3); + + Assert.assertTrue(array.length == copiedArray.length); + Assert.assertTrue(copiedArray[0] == array[0]); + Assert.assertTrue(copiedArray[1] == array[1]); + Assert.assertTrue(copiedArray[2] == array[2]); + } + + @Test + public void givenArrayOfPrimitiveType_whenCopiedSubSequenceViaSystemsArrayCopy_thenSuccessful(){ + int[] array = {23, 43, 55, 12, 65, 88, 92}; + int[] copiedArray = new int[3]; + + System.arraycopy(array, 2, copiedArray, 0, 3); + + Assert.assertTrue(3 == copiedArray.length); + Assert.assertTrue(copiedArray[0] == array[2]); + Assert.assertTrue(copiedArray[1] == array[3]); + Assert.assertTrue(copiedArray[2] == array[4]); + } + + @Test + public void givenArrayOfPrimitiveType_whenCopiedSubSequenceViaArraysCopyOfRange_thenSuccessful(){ + int[] array = {23, 43, 55, 12, 65, 88, 92}; + + int[] copiedArray = Arrays.copyOfRange(array, 1, 4); + + Assert.assertTrue(3 == copiedArray.length); + Assert.assertTrue(copiedArray[0] == array[1]); + Assert.assertTrue(copiedArray[1] == array[2]); + Assert.assertTrue(copiedArray[2] == array[3]); + } + + @Test + public void givenArrayOfPrimitiveType_whenCopiedViaArraysCopyOf_thenValueChangeIsSuccessful(){ + int[] array = {23, 43, 55, 12}; + int newLength = array.length; + + int[] copiedArray = Arrays.copyOf(array, newLength); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == array.length); + Assert.assertTrue(copiedArray[0] == array[0]); + Assert.assertTrue(copiedArray[1] == array[1]); + Assert.assertTrue(copiedArray[2] == array[2]); + Assert.assertTrue(copiedArray[3] == array[3]); + array[0] = 9; + Assert.assertTrue(copiedArray[0] != array[0]); + copiedArray[1] = 12; + Assert.assertTrue(copiedArray[1] != array[1]); + } + + @Test + public void givenArrayOfNonPrimitiveType_whenCopiedViaArraysCopyOf_thenDoShallowCopy(){ + Employee[] copiedArray = Arrays.copyOf(employees, employees.length); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == employees.length); + employees[0].setName(employees[0].getName()+"_Changed"); + //change in employees' element caused change in the copied array + Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); + } + + @Test + public void givenArrayOfPrimitiveType_whenCopiedViaArrayClone_thenValueChangeIsSuccessful(){ + int[] array = {23, 43, 55, 12}; + + int[] copiedArray = array.clone(); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == array.length); + Assert.assertTrue(copiedArray[0] == array[0]); + Assert.assertTrue(copiedArray[1] == array[1]); + Assert.assertTrue(copiedArray[2] == array[2]); + Assert.assertTrue(copiedArray[3] == array[3]); + array[0] = 9; + Assert.assertTrue(copiedArray[0] != array[0]); + copiedArray[1] = 12; + Assert.assertTrue(copiedArray[1] != array[1]); + } + + @Test + public void givenArraysOfNonPrimitiveType_whenCopiedViaArrayClone_thenDoShallowCopy(){ + Employee[] copiedArray = employees.clone(); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == employees.length); + employees[0].setName(employees[0].getName()+"_Changed"); + //change in employees' element changed the copied array + Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); + } + + @Test + public void givenArraysOfCloneableNonPrimitiveType_whenCopiedViaArrayClone_thenDoShallowCopy(){ + Address[] addresses = createAddressArray(); + + Address[] copiedArray = addresses.clone(); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == addresses.length); + addresses[0].setCity(addresses[0].getCity()+"_Changed"); + Assert.assertTrue(copiedArray[0].getCity().equals(addresses[0].getCity())); + } + + @Test + public void givenArraysOfSerializableNonPrimitiveType_whenCopiedViaSerializationUtils_thenDoDeepCopy(){ + Employee[] copiedArray = SerializationUtils.clone(employees); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == employees.length); + employees[0].setName(employees[0].getName()+"_Changed"); + //change in employees' element didn't change in the copied array + Assert.assertFalse(copiedArray[0].getName().equals(employees[0].getName())); + } + + private Address[] createAddressArray(){ + Address[] addresses = new Address[1]; + addresses[0] = createAddress(); + return addresses; + } + + private Address createAddress() { + Address address = new Address(); + address.setCountry("USA"); + address.setState("CA"); + address.setCity("San Francisco"); + address.setStreet("Street 1"); + address.setZipcode("59999"); + return address; + } +} From 11cdf67fad28587f3be0c1c4ca498ff541de81e2 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Fri, 24 Mar 2017 20:06:32 +0100 Subject: [PATCH 071/149] Merge modules (#1471) * Merge modules * Update pom.xml --- pom.xml | 1 - spring-security-basic-auth/.gitignore | 13 - spring-security-basic-auth/README.md | 13 - spring-security-basic-auth/pom.xml | 267 ------------------ .../persistence/service/FooService.java | 23 -- .../java/org/baeldung/spring/MvcConfig.java | 39 --- .../baeldung/spring/PersistenceConfig.java | 14 - .../baeldung/spring/SecSecurityConfig.java | 15 - .../java/org/baeldung/spring/WebConfig.java | 17 -- .../web/controller/FooController.java | 74 ----- .../org/baeldung/web/controller/LinkUtil.java | 30 -- .../web/controller/ResourceCreated.java | 35 --- ...esourceCreatedDiscoverabilityListener.java | 35 --- .../controller/SingleResourceRetrieved.java | 29 -- ...ourceRetrievedDiscoverabilityListener.java | 32 --- .../web/controller/TestController.java | 28 -- .../main/java/org/baeldung/web/dto/Foo.java | 11 - .../src/main/resources/logback.xml | 20 -- .../src/main/resources/webSecurityConfig.xml | 23 -- .../src/main/webapp/WEB-INF/mvc-servlet.xml | 5 - .../src/main/webapp/WEB-INF/view/homepage.jsp | 7 - .../src/main/webapp/WEB-INF/web.xml | 43 --- .../src/test/resources/.gitignore | 13 - .../MyBasicAuthenticationEntryPoint.java | 2 +- spring-security-rest-basic-auth/README.md | 3 +- .../MyBasicAuthenticationEntryPoint.java | 15 +- 26 files changed, 10 insertions(+), 797 deletions(-) delete mode 100644 spring-security-basic-auth/.gitignore delete mode 100644 spring-security-basic-auth/README.md delete mode 100644 spring-security-basic-auth/pom.xml delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/persistence/service/FooService.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/spring/MvcConfig.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/spring/PersistenceConfig.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/spring/SecSecurityConfig.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/spring/WebConfig.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/FooController.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/LinkUtil.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreated.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreatedDiscoverabilityListener.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrieved.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrievedDiscoverabilityListener.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/controller/TestController.java delete mode 100644 spring-security-basic-auth/src/main/java/org/baeldung/web/dto/Foo.java delete mode 100644 spring-security-basic-auth/src/main/resources/logback.xml delete mode 100644 spring-security-basic-auth/src/main/resources/webSecurityConfig.xml delete mode 100644 spring-security-basic-auth/src/main/webapp/WEB-INF/mvc-servlet.xml delete mode 100644 spring-security-basic-auth/src/main/webapp/WEB-INF/view/homepage.jsp delete mode 100644 spring-security-basic-auth/src/main/webapp/WEB-INF/web.xml delete mode 100644 spring-security-basic-auth/src/test/resources/.gitignore rename {spring-security-basic-auth/src/main/java/org/baeldung/security => spring-security-mvc-digest-auth/src/main/java/org/baeldung}/basic/MyBasicAuthenticationEntryPoint.java (96%) rename {spring-security-mvc-digest-auth/src/main/java/org/baeldung/security => spring-security-rest-basic-auth/src/main/java/org/baeldung}/basic/MyBasicAuthenticationEntryPoint.java (96%) diff --git a/pom.xml b/pom.xml index 7c1cacbd33..929f98a674 100644 --- a/pom.xml +++ b/pom.xml @@ -161,7 +161,6 @@ spring-rest-angular spring-rest-docs spring-rest - spring-security-basic-auth spring-security-cache-control spring-security-client/spring-security-jsp-authentication spring-security-client/spring-security-jsp-authorize diff --git a/spring-security-basic-auth/.gitignore b/spring-security-basic-auth/.gitignore deleted file mode 100644 index 83c05e60c8..0000000000 --- a/spring-security-basic-auth/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -*.class - -#folders# -/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-security-basic-auth/README.md b/spring-security-basic-auth/README.md deleted file mode 100644 index ebb404063f..0000000000 --- a/spring-security-basic-auth/README.md +++ /dev/null @@ -1,13 +0,0 @@ -========= - -## Spring Security with Basic Authentication Example Project - -###The Course -The "Learn Spring Security" Classes: http://github.learnspringsecurity.com - -### Relevant Article: -- [Spring Security Basic Authentication](http://www.baeldung.com/spring-security-basic-authentication) - - -### Notes -- the project includes both views as well as a REST layer diff --git a/spring-security-basic-auth/pom.xml b/spring-security-basic-auth/pom.xml deleted file mode 100644 index 4cb0efb9e2..0000000000 --- a/spring-security-basic-auth/pom.xml +++ /dev/null @@ -1,267 +0,0 @@ - - 4.0.0 - com.baeldung - spring-security-mvc-basic-auth - 0.1-SNAPSHOT - - spring-security-mvc-basic-auth - war - - - - - - - org.springframework.security - spring-security-web - ${org.springframework.security.version} - - - org.springframework.security - spring-security-config - ${org.springframework.security.version} - - - - - - org.springframework - spring-core - ${org.springframework.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-context - ${org.springframework.version} - - - org.springframework - spring-jdbc - ${org.springframework.version} - - - org.springframework - spring-beans - ${org.springframework.version} - - - org.springframework - spring-aop - ${org.springframework.version} - - - org.springframework - spring-tx - ${org.springframework.version} - - - org.springframework - spring-expression - ${org.springframework.version} - - - - org.springframework - spring-web - ${org.springframework.version} - - - org.springframework - spring-webmvc - ${org.springframework.version} - - - - - - javax.servlet - javax.servlet-api - ${javax.servlet.version} - provided - - - - javax.servlet - jstl - ${jstl.version} - runtime - - - - - - com.google.guava - guava - ${guava.version} - - - - - - org.slf4j - slf4j-api - ${org.slf4j.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - - org.slf4j - jcl-over-slf4j - ${org.slf4j.version} - - - - org.slf4j - log4j-over-slf4j - ${org.slf4j.version} - - - - - - junit - junit - ${junit.version} - test - - - - org.hamcrest - hamcrest-core - ${org.hamcrest.version} - test - - - org.hamcrest - hamcrest-library - ${org.hamcrest.version} - test - - - - org.mockito - mockito-core - ${mockito.version} - test - - - - - - spring-security-mvc-basic-auth - - - src/main/resources - true - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - 1.8 - 1.8 - - - - - org.apache.maven.plugins - maven-war-plugin - ${maven-war-plugin.version} - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - - - - - - - - - - org.codehaus.cargo - cargo-maven2-plugin - ${cargo-maven2-plugin.version} - - true - - jetty8x - embedded - - - - - - - 8082 - - - - - - - - - - - - 4.3.4.RELEASE - 4.2.0.RELEASE - - - 5.2.5.Final - 5.1.40 - - - 1.7.21 - 1.1.7 - - - 5.3.3.Final - 1.2 - 3.1.0 - - - 19.0 - 3.5 - - - 1.3 - 4.12 - 1.10.19 - - 4.4.5 - 4.5.2 - - 2.9.0 - - - 3.6.0 - 2.6 - 2.19.1 - 2.7 - 1.6.1 - - - - \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/persistence/service/FooService.java b/spring-security-basic-auth/src/main/java/org/baeldung/persistence/service/FooService.java deleted file mode 100644 index 02db7a733a..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/persistence/service/FooService.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.baeldung.persistence.service; - -import org.baeldung.web.dto.Foo; -import org.springframework.stereotype.Service; - -@Service -public class FooService { - - public FooService() { - super(); - } - - // API - - public Foo getById(final Long id) { - return null; - } - - public Long create(final Foo resource) { - return null; - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/spring/MvcConfig.java b/spring-security-basic-auth/src/main/java/org/baeldung/spring/MvcConfig.java deleted file mode 100644 index 74c11478ee..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/spring/MvcConfig.java +++ /dev/null @@ -1,39 +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.WebMvcConfigurerAdapter; -import org.springframework.web.servlet.view.InternalResourceViewResolver; -import org.springframework.web.servlet.view.JstlView; - -@Configuration -@EnableWebMvc -public class MvcConfig extends WebMvcConfigurerAdapter { - - public MvcConfig() { - super(); - } - - // API - - @Override - public void addViewControllers(final ViewControllerRegistry registry) { - super.addViewControllers(registry); - - registry.addViewController("/homepage.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-basic-auth/src/main/java/org/baeldung/spring/PersistenceConfig.java b/spring-security-basic-auth/src/main/java/org/baeldung/spring/PersistenceConfig.java deleted file mode 100644 index 4ea0053f48..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/spring/PersistenceConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.baeldung.spring; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ComponentScan("org.baeldung.persistence") -public class PersistenceConfig { - - public PersistenceConfig() { - super(); - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/spring/SecSecurityConfig.java b/spring-security-basic-auth/src/main/java/org/baeldung/spring/SecSecurityConfig.java deleted file mode 100644 index f40c599834..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/spring/SecSecurityConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.baeldung.spring; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ComponentScan("org.baeldung.security") -// @ImportResource({ "classpath:webSecurityConfig.xml" }) -public class SecSecurityConfig { - - public SecSecurityConfig() { - super(); - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/spring/WebConfig.java b/spring-security-basic-auth/src/main/java/org/baeldung/spring/WebConfig.java deleted file mode 100644 index fa6f5f6d56..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/spring/WebConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.baeldung.spring; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -@Configuration -@ComponentScan("org.baeldung.web") -public class WebConfig extends WebMvcConfigurerAdapter { - - public WebConfig() { - super(); - } - - // API - -} \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/FooController.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/FooController.java deleted file mode 100644 index daa797ee36..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/FooController.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.baeldung.web.controller; - -import java.net.URI; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.baeldung.persistence.service.FooService; -import org.baeldung.web.dto.Foo; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriTemplate; - -import com.google.common.base.Preconditions; - -@Controller -@RequestMapping(value = "/foo") -public class FooController { - - @Autowired - private ApplicationEventPublisher eventPublisher; - - @Autowired - private FooService service; - - public FooController() { - super(); - } - - // API - - @RequestMapping(value = "/{id}", method = RequestMethod.GET) - @ResponseBody - public Foo findOne(@PathVariable("id") final Long id, final UriComponentsBuilder uriBuilder, final HttpServletResponse response) { - return new Foo(); - } - - @RequestMapping(value = "admin/foo/{id}", method = RequestMethod.GET) - @ResponseBody - public Foo get(@PathVariable("id") final Long id, final HttpServletRequest request, final HttpServletResponse response) { - final Foo resourceById = Preconditions.checkNotNull(service.getById(id)); - - eventPublisher.publishEvent(new SingleResourceRetrieved(this, request, response)); - return resourceById; - } - - @RequestMapping(value = "admin/foo", method = RequestMethod.POST) - @ResponseStatus(HttpStatus.CREATED) - public void create(@RequestBody final Foo resource, final HttpServletRequest request, final HttpServletResponse response) { - Preconditions.checkNotNull(resource); - final Long idOfCreatedResource = service.create(resource); - - eventPublisher.publishEvent(new ResourceCreated(this, request, response, idOfCreatedResource)); - } - - @RequestMapping(value = "admin", method = RequestMethod.GET) - @ResponseStatus(value = HttpStatus.NO_CONTENT) - public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) { - final String rootUri = request.getRequestURL().toString(); - - final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo"); - final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection"); - response.addHeader("Link", linkToFoo); - } -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/LinkUtil.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/LinkUtil.java deleted file mode 100644 index a41ebb5a5c..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/LinkUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.baeldung.web.controller; - -import javax.servlet.http.HttpServletResponse; - -/** - * Provides some constants and utility methods to build a Link Header to be stored in the {@link HttpServletResponse} object - */ -public final class LinkUtil { - - private LinkUtil() { - throw new AssertionError(); - } - - // - - /** - * Creates a Link Header to be stored in the {@link HttpServletResponse} to provide Discoverability features to the user - * - * @param uri - * the base uri - * @param rel - * the relative path - * - * @return the complete url - */ - public static String createLinkHeader(final String uri, final String rel) { - return "<" + uri + ">; rel=\"" + rel + "\""; - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreated.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreated.java deleted file mode 100644 index a677888101..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreated.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.baeldung.web.controller; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.context.ApplicationEvent; - -public class ResourceCreated extends ApplicationEvent { - private final HttpServletResponse response; - private final HttpServletRequest request; - private final long idOfNewResource; - - public ResourceCreated(final Object source, final HttpServletRequest request, final HttpServletResponse response, final long idOfNewResource) { - super(source); - - this.request = request; - this.response = response; - this.idOfNewResource = idOfNewResource; - } - - // API - - public HttpServletResponse getResponse() { - return response; - } - - public HttpServletRequest getRequest() { - return request; - } - - public long getIdOfNewResource() { - return idOfNewResource; - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreatedDiscoverabilityListener.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreatedDiscoverabilityListener.java deleted file mode 100644 index 8d19ef82fc..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/ResourceCreatedDiscoverabilityListener.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.baeldung.web.controller; - -import java.net.URI; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; -import org.springframework.web.util.UriTemplate; - -import com.google.common.base.Preconditions; -import com.google.common.net.HttpHeaders; - -@Component -class ResourceCreatedDiscoverabilityListener implements ApplicationListener { - - @Override - public void onApplicationEvent(final ResourceCreated resourceCreatedEvent) { - Preconditions.checkNotNull(resourceCreatedEvent); - - final HttpServletRequest request = resourceCreatedEvent.getRequest(); - final HttpServletResponse response = resourceCreatedEvent.getResponse(); - final long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); - - addLinkHeaderOnResourceCreation(request, response, idOfNewResource); - } - - void addLinkHeaderOnResourceCreation(final HttpServletRequest request, final HttpServletResponse response, final long idOfNewResource) { - final String requestUrl = request.getRequestURL().toString(); - final URI uri = new UriTemplate("{requestUrl}/{idOfNewResource}").expand(requestUrl, idOfNewResource); - response.setHeader(HttpHeaders.LOCATION, uri.toASCIIString()); - } - -} \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrieved.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrieved.java deleted file mode 100644 index 3de7918105..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrieved.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.baeldung.web.controller; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.context.ApplicationEvent; - -public class SingleResourceRetrieved extends ApplicationEvent { - private final HttpServletResponse response; - private final HttpServletRequest request; - - public SingleResourceRetrieved(final Object source, final HttpServletRequest request, final HttpServletResponse response) { - super(source); - - this.request = request; - this.response = response; - } - - // API - - public HttpServletResponse getResponse() { - return response; - } - - public HttpServletRequest getRequest() { - return request; - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrievedDiscoverabilityListener.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrievedDiscoverabilityListener.java deleted file mode 100644 index 45cd7c4d13..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/SingleResourceRetrievedDiscoverabilityListener.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.baeldung.web.controller; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -import com.google.common.base.Preconditions; - -@Component -class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener { - - @Override - public void onApplicationEvent(final SingleResourceRetrieved resourceRetrievedEvent) { - Preconditions.checkNotNull(resourceRetrievedEvent); - - final HttpServletRequest request = resourceRetrievedEvent.getRequest(); - final HttpServletResponse response = resourceRetrievedEvent.getResponse(); - addLinkHeaderOnSingleResourceRetrieval(request, response); - } - - void addLinkHeaderOnSingleResourceRetrieval(final HttpServletRequest request, final HttpServletResponse response) { - final StringBuffer requestURL = request.getRequestURL(); - final int positionOfLastSlash = requestURL.lastIndexOf("/"); - final String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash); - - final String linkHeaderValue = LinkUtil.createLinkHeader(uriForResourceCreation, "collection"); - response.addHeader("Link", linkHeaderValue); - } - -} \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/TestController.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/TestController.java deleted file mode 100644 index f68cfb2eb7..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/controller/TestController.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.baeldung.web.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -public class TestController { - - public TestController() { - super(); - } - - // API - - @RequestMapping("/permitAll") - @ResponseBody - public String permitAll() { - return "Permit All"; - } - - @RequestMapping("/securityNone") - @ResponseBody - public String securityNone() { - return "Security None"; - } - -} diff --git a/spring-security-basic-auth/src/main/java/org/baeldung/web/dto/Foo.java b/spring-security-basic-auth/src/main/java/org/baeldung/web/dto/Foo.java deleted file mode 100644 index 352045989d..0000000000 --- a/spring-security-basic-auth/src/main/java/org/baeldung/web/dto/Foo.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.baeldung.web.dto; - -import java.io.Serializable; - -public class Foo implements Serializable { - - public Foo() { - super(); - } - -} diff --git a/spring-security-basic-auth/src/main/resources/logback.xml b/spring-security-basic-auth/src/main/resources/logback.xml deleted file mode 100644 index 1146dade63..0000000000 --- a/spring-security-basic-auth/src/main/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - web - %date [%thread] %-5level %logger{36} - %message%n - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml b/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml deleted file mode 100644 index b0d483768b..0000000000 --- a/spring-security-basic-auth/src/main/resources/webSecurityConfig.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/webapp/WEB-INF/mvc-servlet.xml b/spring-security-basic-auth/src/main/webapp/WEB-INF/mvc-servlet.xml deleted file mode 100644 index eb7ce7b5f8..0000000000 --- a/spring-security-basic-auth/src/main/webapp/WEB-INF/mvc-servlet.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/webapp/WEB-INF/view/homepage.jsp b/spring-security-basic-auth/src/main/webapp/WEB-INF/view/homepage.jsp deleted file mode 100644 index 7cc14b5dcd..0000000000 --- a/spring-security-basic-auth/src/main/webapp/WEB-INF/view/homepage.jsp +++ /dev/null @@ -1,7 +0,0 @@ - - - - -

This is the body of the sample view

- - \ No newline at end of file diff --git a/spring-security-basic-auth/src/main/webapp/WEB-INF/web.xml b/spring-security-basic-auth/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 77a830c6d5..0000000000 --- a/spring-security-basic-auth/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - Spring Security Basic Auth Application - - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - org.baeldung.spring - - - - org.springframework.web.context.ContextLoaderListener - - - - mvc - org.springframework.web.servlet.DispatcherServlet - 1 - - - mvc - / - - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - springSecurityFilterChain - /* - - - - index.html - - - \ No newline at end of file diff --git a/spring-security-basic-auth/src/test/resources/.gitignore b/spring-security-basic-auth/src/test/resources/.gitignore deleted file mode 100644 index 83c05e60c8..0000000000 --- a/spring-security-basic-auth/src/test/resources/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -*.class - -#folders# -/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-security-basic-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java b/spring-security-mvc-digest-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java similarity index 96% rename from spring-security-basic-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java rename to spring-security-mvc-digest-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java index 968237227f..c51c0a0bc8 100644 --- a/spring-security-basic-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java +++ b/spring-security-mvc-digest-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package org.baeldung.security.basic; +package org.baeldung.basic; import java.io.IOException; import java.io.PrintWriter; diff --git a/spring-security-rest-basic-auth/README.md b/spring-security-rest-basic-auth/README.md index 328f46ed46..43ab08b8ca 100644 --- a/spring-security-rest-basic-auth/README.md +++ b/spring-security-rest-basic-auth/README.md @@ -9,4 +9,5 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com - [RestTemplate with Basic Authentication in Spring](http://www.baeldung.com/2012/04/16/how-to-use-resttemplate-with-basic-authentication-in-spring-3-1) - [HttpClient Timeout](http://www.baeldung.com/httpclient-timeout) - [HttpClient with SSL](http://www.baeldung.com/httpclient-ssl) -- [Writing a Custom Filter in Spring Security](http://www.baeldung.com/spring-security-custom-filter) \ No newline at end of file +- [Writing a Custom Filter in Spring Security](http://www.baeldung.com/spring-security-custom-filter) +- [Spring Security Basic Authentication](http://www.baeldung.com/spring-security-basic-authentication) \ No newline at end of file diff --git a/spring-security-mvc-digest-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java b/spring-security-rest-basic-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java similarity index 96% rename from spring-security-mvc-digest-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java rename to spring-security-rest-basic-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java index 968237227f..6e580e7a22 100644 --- a/spring-security-mvc-digest-auth/src/main/java/org/baeldung/security/basic/MyBasicAuthenticationEntryPoint.java +++ b/spring-security-rest-basic-auth/src/main/java/org/baeldung/basic/MyBasicAuthenticationEntryPoint.java @@ -1,16 +1,15 @@ -package org.baeldung.security.basic; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +package org.baeldung.basic; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.stereotype.Component; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + @Component public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { From 092d883dde466c9cbbf210db6569df8b5e5a8bd0 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Fri, 24 Mar 2017 20:12:13 +0100 Subject: [PATCH 072/149] Revert "Spring State Machine" (#1485) * Revert "Merge modules (#1471)" This reverts commit 11cdf67fad28587f3be0c1c4ca498ff541de81e2. * Revert "(BAEL-746) How to Copy an Array in Java (#1474)" This reverts commit 44f5742f16c7020de4487670f33a1e6527137308. * Revert "Bs santosh spring mybatis (#1479)" This reverts commit 3140ea166d05c59b605eb3ac0b5d55a116344c6e. * Revert "Spring State Machine (#1424)" This reverts commit 319dd2653a073f310db05b6121696ebfa7049968. --- pom.xml | 5 +- spring-state-machine/bpmn/forkjoin.bpmn | 116 ------------------ spring-state-machine/bpmn/img/forkjoin.png | Bin 50788 -> 0 bytes spring-state-machine/bpmn/img/simple.png | Bin 22706 -> 0 bytes spring-state-machine/bpmn/simple.bpmn | 76 ------------ spring-state-machine/pom.xml | 31 ----- .../ApplicationReviewEvents.java | 5 - .../ApplicationReviewStates.java | 5 - .../ForkJoinStateMachineConfiguration.java | 74 ----------- ...HierarchicalStateMachineConfiguration.java | 47 ------- .../JunctionStateMachineConfiguration.java | 60 --------- .../SimpleEnumStateMachineConfiguration.java | 53 -------- .../SimpleStateMachineConfiguration.java | 105 ---------------- .../config/StateMachineListener.java | 16 --- .../ForkJoinStateMachineTest.java | 45 ------- .../HierarchicalStateMachineTest.java | 37 ------ .../JunctionStateMachineTest.java | 24 ---- .../statemachine/StateEnumMachineTest.java | 33 ----- .../statemachine/StateMachineBuilderTest.java | 35 ------ .../spring/statemachine/StateMachineTest.java | 47 ------- 20 files changed, 1 insertion(+), 813 deletions(-) delete mode 100644 spring-state-machine/bpmn/forkjoin.bpmn delete mode 100644 spring-state-machine/bpmn/img/forkjoin.png delete mode 100644 spring-state-machine/bpmn/img/simple.png delete mode 100644 spring-state-machine/bpmn/simple.bpmn delete mode 100644 spring-state-machine/pom.xml delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java delete mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java delete mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java diff --git a/pom.xml b/pom.xml index 929f98a674..791430ba0e 100644 --- a/pom.xml +++ b/pom.xml @@ -187,7 +187,6 @@ spring-sleuth spring-social-login spring-spel - spring-state-machine spring-thymeleaf spring-userservice spring-zuul @@ -210,9 +209,7 @@ rabbitmq vertx - - - + diff --git a/spring-state-machine/bpmn/forkjoin.bpmn b/spring-state-machine/bpmn/forkjoin.bpmn deleted file mode 100644 index 0cb060f74b..0000000000 --- a/spring-state-machine/bpmn/forkjoin.bpmn +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-state-machine/bpmn/img/forkjoin.png b/spring-state-machine/bpmn/img/forkjoin.png deleted file mode 100644 index 642ab6949d18b49012529d7c5b9b14f909c9d701..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50788 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�gf@SIUg-QO`umQ3S)QlWiV3@kS`RLu_ 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 D__yV@ diff --git a/spring-state-machine/bpmn/img/simple.png b/spring-state-machine/bpmn/img/simple.png deleted file mode 100644 index 7a79bf1d895a401b2b59d4b093e770f3546bc3d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22706 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 AC;$Ke diff --git a/spring-state-machine/bpmn/simple.bpmn b/spring-state-machine/bpmn/simple.bpmn deleted file mode 100644 index 8ed463e9f9..0000000000 --- a/spring-state-machine/bpmn/simple.bpmn +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml deleted file mode 100644 index 5393626083..0000000000 --- a/spring-state-machine/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - parent-modules - com.baeldung - 1.0.0-SNAPSHOT - - 4.0.0 - - baeldung-spring-state-machine - - 1.8 - 1.8 - - - - - org.springframework.statemachine - spring-statemachine-core - 1.2.3.RELEASE - - - junit - junit - 4.11 - test - - - \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java deleted file mode 100644 index 971fc5dde7..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.spring.stateMachine.applicationReview; - -public enum ApplicationReviewEvents { - APPROVE, REJECT -} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java deleted file mode 100644 index 1df2db1f86..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.spring.stateMachine.applicationReview; - -public enum ApplicationReviewStates { - PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED -} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java deleted file mode 100644 index c55104a627..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.StateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.Guard; - -@Configuration -@EnableStateMachine -public class ForkJoinStateMachineConfiguration extends StateMachineConfigurerAdapter { - - @Override - public void configure(StateMachineConfigurationConfigurer config) - throws Exception { - config - .withConfiguration() - .autoStartup(true) - .listener(new StateMachineListener()); - } - - @Override - public void configure(StateMachineStateConfigurer states) throws Exception { - states - .withStates() - .initial("SI") - .fork("SFork") - .join("SJoin") - .end("SF") - .and() - .withStates() - .parent("SFork") - .initial("Sub1-1") - .end("Sub1-2") - .and() - .withStates() - .parent("SFork") - .initial("Sub2-1") - .end("Sub2-2"); - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) throws Exception { - transitions.withExternal() - .source("SI").target("SFork").event("E1") - .and().withExternal() - .source("Sub1-1").target("Sub1-2").event("sub1") - .and().withExternal() - .source("Sub2-1").target("Sub2-2").event("sub2") - .and() - .withFork() - .source("SFork") - .target("Sub1-1") - .target("Sub2-1") - .and() - .withJoin() - .source("Sub1-2") - .source("Sub2-2") - .target("SJoin"); - } - - @Bean - public Guard mediumGuard() { - return (ctx) -> false; - } - - @Bean - public Guard highGuard() { - return (ctx) -> false; - } -} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java deleted file mode 100644 index 708dbd3077..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.StateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; - -@Configuration -@EnableStateMachine -public class HierarchicalStateMachineConfiguration extends StateMachineConfigurerAdapter { - - @Override - public void configure(StateMachineConfigurationConfigurer config) - throws Exception { - config - .withConfiguration() - .autoStartup(true) - .listener(new StateMachineListener()); - } - - @Override - public void configure(StateMachineStateConfigurer states) throws Exception { - states - .withStates() - .initial("SI") - .state("SI") - .end("SF") - .and() - .withStates() - .parent("SI") - .initial("SUB1") - .state("SUB2") - .end("SUBEND"); - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) throws Exception { - transitions.withExternal() - .source("SI").target("SF").event("end") - .and().withExternal() - .source("SUB1").target("SUB2").event("se1") - .and().withExternal() - .source("SUB2").target("SUBEND").event("s-end"); - } -} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java deleted file mode 100644 index e1bae10fb7..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.StateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.Guard; - -@Configuration -@EnableStateMachine -public class JunctionStateMachineConfiguration extends StateMachineConfigurerAdapter { - - @Override - public void configure(StateMachineConfigurationConfigurer config) - throws Exception { - config - .withConfiguration() - .autoStartup(true) - .listener(new StateMachineListener()); - } - - @Override - public void configure(StateMachineStateConfigurer states) throws Exception { - states - .withStates() - .initial("SI") - .junction("SJ") - .state("high") - .state("medium") - .state("low") - .end("SF"); - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) throws Exception { - transitions.withExternal() - .source("SI").target("SJ").event("E1") - .and() - .withJunction() - .source("SJ") - .first("high", highGuard()) - .then("medium", mediumGuard()) - .last("low") - .and().withExternal() - .source("low").target("SF").event("end"); - } - - @Bean - public Guard mediumGuard() { - return (ctx) -> false; - } - - @Bean - public Guard highGuard() { - return (ctx) -> false; - } -} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java deleted file mode 100644 index 4e11851644..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.action.Action; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.StateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.Guard; - -import java.util.Arrays; -import java.util.HashSet; - -@Configuration -@EnableStateMachine -public class SimpleEnumStateMachineConfiguration extends StateMachineConfigurerAdapter { - - @Override - public void configure(StateMachineConfigurationConfigurer config) - throws Exception { - config - .withConfiguration() - .autoStartup(true) - .listener(new StateMachineListener()); - } - - @Override - public void configure(StateMachineStateConfigurer states) throws Exception { - states - .withStates() - .initial(ApplicationReviewStates.PEER_REVIEW) - .state(ApplicationReviewStates.PRINCIPAL_REVIEW) - .end(ApplicationReviewStates.APPROVED) - .end(ApplicationReviewStates.REJECTED); - - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) throws Exception { - transitions.withExternal() - .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.PRINCIPAL_REVIEW).event(ApplicationReviewEvents.APPROVE) - .and().withExternal() - .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.APPROVED).event(ApplicationReviewEvents.APPROVE) - .and().withExternal() - .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT) - .and().withExternal() - .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT); - } -} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java deleted file mode 100644 index fe4e0f82ce..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.action.Action; -import org.springframework.statemachine.config.EnableStateMachine; -import org.springframework.statemachine.config.StateMachineConfigurerAdapter; -import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; -import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; -import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.Guard; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.logging.Logger; - -@Configuration -@EnableStateMachine -public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter { - - public static final Logger LOGGER = Logger.getLogger(SimpleStateMachineConfiguration.class.getName()); - - @Override - public void configure(StateMachineConfigurationConfigurer config) - throws Exception { - config - .withConfiguration() - .autoStartup(true) - .listener(new StateMachineListener()); - } - - @Override - public void configure(StateMachineStateConfigurer states) throws Exception { - states - .withStates() - .initial("SI") - .end("SF") - .states(new HashSet<>(Arrays.asList("S1", "S2"))) - .state("S4", executeAction(), errorAction()) - .stateEntry("S3", entryAction()) - .stateDo("S3", executeAction()) - .stateExit("S3", exitAction()); - - } - - @Override - public void configure(StateMachineTransitionConfigurer transitions) throws Exception { - transitions.withExternal() - .source("SI").target("S1").event("E1").action(initAction()) - .and().withExternal() - .source("S1").target("S2").event("E2") - .and().withExternal() - .source("SI").target("S3").event("E3") - .and().withExternal() - .source("S3").target("S4").event("E4").guard(simpleGuard()) - .and().withExternal() - .source("S2").target("SF").event("end"); - } - - @Bean - public Guard simpleGuard() { - return (ctx) -> { - int approvalCount = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); - return approvalCount > 0; - }; - } - - @Bean - public Action entryAction() { - return (ctx) -> { - LOGGER.info("Entry " + ctx.getTarget().getId()); - }; - } - - @Bean - public Action executeAction() { - return (ctx) -> { - LOGGER.info("Do " + ctx.getTarget().getId()); - int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); - approvals++; - ctx.getExtendedState().getVariables().put("approvalCount", approvals); - }; - } - - @Bean - public Action exitAction() { - return (ctx) -> { - LOGGER.info("Exit " + ctx.getSource().getId() + " -> " + ctx.getTarget().getId()); - }; - } - - @Bean - public Action errorAction() { - return (ctx) -> { - LOGGER.info("Error " + ctx.getSource().getId() + ctx.getException()); - }; - } - - @Bean - public Action initAction() { - return (ctx) -> { - LOGGER.info(ctx.getTarget().getId()); - }; - } -} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java deleted file mode 100644 index bb7859c683..0000000000 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.baeldung.spring.stateMachine.config; - -import org.springframework.statemachine.listener.StateMachineListenerAdapter; -import org.springframework.statemachine.state.State; - -import java.util.logging.Logger; - -public class StateMachineListener extends StateMachineListenerAdapter { - - public static final Logger LOGGER = Logger.getLogger(StateMachineListener.class.getName()); - - @Override - public void stateChanged(State from, State to) { - LOGGER.info(String.format("Transitioned from %s to %s%n", from == null ? "none" : from.getId(), to.getId())); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java deleted file mode 100644 index 416da5f0fe..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; - -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - -import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration; - -public class ForkJoinStateMachineTest { - - @Test - public void whenForkStateEntered_thenMultipleSubStatesEntered() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - - boolean success = stateMachine.sendEvent("E1"); - - assertTrue(success); - - assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); - } - - @Test - public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - - boolean success = stateMachine.sendEvent("E1"); - - assertTrue(success); - - assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); - - assertTrue(stateMachine.sendEvent("sub1")); - assertTrue(stateMachine.sendEvent("sub2")); - assertEquals("SJoin", stateMachine.getState().getId()); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java deleted file mode 100644 index 3557a63211..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; - -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - -import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration; - -public class HierarchicalStateMachineTest { - - @Test - public void whenTransitionToSubMachine_thenSubStateIsEntered() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - - - assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds()); - - stateMachine.sendEvent("se1"); - - assertEquals(Arrays.asList("SI", "SUB2"), stateMachine.getState().getIds()); - - stateMachine.sendEvent("s-end"); - - assertEquals(Arrays.asList("SI", "SUBEND"), stateMachine.getState().getIds()); - - stateMachine.sendEvent("end"); - - assertEquals(1, stateMachine.getState().getIds().size()); - assertEquals("SF", stateMachine.getState().getId()); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java deleted file mode 100644 index d0c1225c9b..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import org.junit.Assert; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - -import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration; - -public class JunctionStateMachineTest { - - @Test - public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - - stateMachine.sendEvent("E1"); - Assert.assertEquals("low", stateMachine.getState().getId()); - - stateMachine.sendEvent("end"); - Assert.assertEquals("SF", stateMachine.getState().getId()); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java deleted file mode 100644 index 1fd7bd85f0..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; -import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration; - -public class StateEnumMachineTest { - - private StateMachine stateMachine; - - @Before - public void setUp() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class); - stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - } - - @Test - public void whenStateMachineConfiguredWithEnums_thenStateMachineAcceptsEnumEvents() { - assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.APPROVE)); - assertEquals(ApplicationReviewStates.PRINCIPAL_REVIEW, stateMachine.getState().getId()); - assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT)); - assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId()); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java deleted file mode 100644 index cdd1e951e0..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.config.StateMachineBuilder; - -public class StateMachineBuilderTest { - - @Test - public void whenUseStateMachineBuilder_thenBuildSuccessAndMachineWorks() throws Exception { - StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); - builder.configureStates().withStates() - .initial("SI") - .state("S1") - .end("SF"); - - builder.configureTransitions() - .withExternal() - .source("SI").target("S1").event("E1") - .and().withExternal() - .source("S1").target("SF").event("E2"); - - StateMachine machine = builder.build(); - - machine.start(); - - machine.sendEvent("E1"); - assertEquals("S1", machine.getState().getId()); - - machine.sendEvent("E2"); - assertEquals("SF", machine.getState().getId()); - } -} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java deleted file mode 100644 index 1b442bf994..0000000000 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - -import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration; - -public class StateMachineTest { - - private StateMachine stateMachine; - - @Before - public void setUp() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class); - stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - } - - @Test - public void whenSimpleStringStateMachineEvents_thenEndState() { - assertEquals("SI", stateMachine.getState().getId()); - - stateMachine.sendEvent("E1"); - assertEquals("S1", stateMachine.getState().getId()); - - stateMachine.sendEvent("E2"); - assertEquals("S2", stateMachine.getState().getId()); - - stateMachine.sendEvent("end"); - assertEquals("SF", stateMachine.getState().getId()); - - } - - @Test - public void whenSimpleStringMachineActionState_thenActionExecuted() { - stateMachine.sendEvent("E3"); - assertEquals("S3", stateMachine.getState().getId()); - - stateMachine.sendEvent("E4"); - assertEquals("S4", stateMachine.getState().getId()); - assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount")); - } -} From 2aca6e085b1fddf6ba6a6fa621f1475aeaddd1b7 Mon Sep 17 00:00:00 2001 From: Yasin Date: Sat, 25 Mar 2017 01:57:45 +0530 Subject: [PATCH 073/149] BAEL-745 Quick Math.pow example (#1482) * yasin.bhojawala@gmail.com Evaluation article on Different Types of Bean Injection in Spring * Revert "yasin.bhojawala@gmail.com" This reverts commit 963cc51a7a15b75b550108fe4e198cd65a274032. * Fixing compilation error and removing unused import * Introduction to Java9 StackWalking API - yasin.bhojawala@gmail.com Code examples for the article "Introduction to Java9 StackWalking API" * BAEL-608 Introduction to Java9 StackWalking API * BAEL-608 Introduction to Java9 StackWalking API changing the test names to BDD style * BAEL-608 Introduction to Java9 StackWalking API correcting the typo * BAEL-608 Introduction to Java9 StackWalking API improving method names * BAEL-608 Introduction to Java9 StackWalking API test method names improvements * BAEL-718 Quick intro to javatuples * merging pom from master * BAEL-722 Intro to JSONassert * BAEL-722 Intro to JSONassert Updated to 1.5.0 * BAEL-745 Quick Math.pow example --- .../java/com/baeldung/pow/PowerExample.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 core-java/src/main/java/com/baeldung/pow/PowerExample.java diff --git a/core-java/src/main/java/com/baeldung/pow/PowerExample.java b/core-java/src/main/java/com/baeldung/pow/PowerExample.java new file mode 100644 index 0000000000..5be5376e6a --- /dev/null +++ b/core-java/src/main/java/com/baeldung/pow/PowerExample.java @@ -0,0 +1,19 @@ +package com.baeldung.pow; + +import java.text.DecimalFormat; + +public class PowerExample { + + public static void main(String[] args) { + + int intResult = (int) Math.pow(2, 3); + System.out.println("Math.pow(2, 3) = " + intResult); + + double dblResult = Math.pow(4.2, 3); + System.out.println("Math.pow(4.2, 3) = " + Math.pow(4.2, 3)); + + DecimalFormat df = new DecimalFormat(".00"); + System.out.println("Math.pow(4.2, 3) rounded = " + df.format(dblResult)); + + } +} From c3764e3f443e8a47ddfe7932f8e5b5f1c91e7afe Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Sat, 25 Mar 2017 03:32:42 +0530 Subject: [PATCH 074/149] CORS in JAX-RS (#1484) * rest with spark java * 4 * Update Application.java * indentation changes * spring @requestmapping shortcuts * removing spring requestmapping and pushing spring-mvc-java * Joining/Splitting Strings with Java and Stream API * adding more join/split functionality * changing package name * testcase change * adding webutils * adding testcase for WebUtils and ServletRequestUtils * adding testcase * spring-security-stormpath * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial * changing banner format in plain text * Delete banner.txt~ * Delete b.txt~ * CORS in JAX-RS --- .../java/com/baeldung/filter/CorsFilter.java | 22 ++++ .../com/baeldung/server/MovieCrudService.java | 110 ++++++++++-------- resteasy/src/main/webapp/script.js | 16 +++ 3 files changed, 99 insertions(+), 49 deletions(-) create mode 100644 resteasy/src/main/java/com/baeldung/filter/CorsFilter.java create mode 100644 resteasy/src/main/webapp/script.js diff --git a/resteasy/src/main/java/com/baeldung/filter/CorsFilter.java b/resteasy/src/main/java/com/baeldung/filter/CorsFilter.java new file mode 100644 index 0000000000..266707fb9f --- /dev/null +++ b/resteasy/src/main/java/com/baeldung/filter/CorsFilter.java @@ -0,0 +1,22 @@ +package com.baeldung.filter; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +public class CorsFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); + responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true"); + responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); + } + +} diff --git a/resteasy/src/main/java/com/baeldung/server/MovieCrudService.java b/resteasy/src/main/java/com/baeldung/server/MovieCrudService.java index b7f3215f3f..5a623a2dc6 100644 --- a/resteasy/src/main/java/com/baeldung/server/MovieCrudService.java +++ b/resteasy/src/main/java/com/baeldung/server/MovieCrudService.java @@ -13,71 +13,83 @@ import java.util.stream.Collectors; @Path("/movies") public class MovieCrudService { - private Map inventory = new HashMap(); + private Map inventory = new HashMap(); - @GET - @Path("/getinfo") - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Movie movieByImdbId(@QueryParam("imdbId") String imdbId) { + @GET + @Path("/") + @Produces({ MediaType.TEXT_PLAIN }) + public Response index() { + return Response.status(200).header("Access-Control-Allow-Origin", "*") + .header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization") + .header("Access-Control-Allow-Credentials", "true") + .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD").entity("").build(); + } - System.out.println("*** Calling getinfo for a given ImdbID***"); + @GET + @Path("/getinfo") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Movie movieByImdbId(@QueryParam("imdbId") String imdbId) { - if (inventory.containsKey(imdbId)) { - return inventory.get(imdbId); - } else - return null; - } + System.out.println("*** Calling getinfo for a given ImdbID***"); - @POST - @Path("/addmovie") - @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response addMovie(Movie movie) { + if (inventory.containsKey(imdbId)) { + return inventory.get(imdbId); + } else + return null; + } - System.out.println("*** Calling addMovie ***"); + @POST + @Path("/addmovie") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response addMovie(Movie movie) { - if (null != inventory.get(movie.getImdbId())) { - return Response.status(Response.Status.NOT_MODIFIED).entity("Movie is Already in the database.").build(); - } + System.out.println("*** Calling addMovie ***"); - inventory.put(movie.getImdbId(), movie); - return Response.status(Response.Status.CREATED).build(); - } + if (null != inventory.get(movie.getImdbId())) { + return Response.status(Response.Status.NOT_MODIFIED).entity("Movie is Already in the database.").build(); + } - @PUT - @Path("/updatemovie") - @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response updateMovie(Movie movie) { + inventory.put(movie.getImdbId(), movie); + return Response.status(Response.Status.CREATED).build(); + } - System.out.println("*** Calling updateMovie ***"); + @PUT + @Path("/updatemovie") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response updateMovie(Movie movie) { - if (null == inventory.get(movie.getImdbId())) { - return Response.status(Response.Status.NOT_MODIFIED).entity("Movie is not in the database.\nUnable to Update").build(); - } + System.out.println("*** Calling updateMovie ***"); - inventory.put(movie.getImdbId(), movie); - return Response.status(Response.Status.OK).build(); - } + if (null == inventory.get(movie.getImdbId())) { + return Response.status(Response.Status.NOT_MODIFIED) + .entity("Movie is not in the database.\nUnable to Update").build(); + } - @DELETE - @Path("/deletemovie") - public Response deleteMovie(@QueryParam("imdbId") String imdbId) { + inventory.put(movie.getImdbId(), movie); + return Response.status(Response.Status.OK).build(); + } - System.out.println("*** Calling deleteMovie ***"); + @DELETE + @Path("/deletemovie") + public Response deleteMovie(@QueryParam("imdbId") String imdbId) { - if (null == inventory.get(imdbId)) { - return Response.status(Response.Status.NOT_FOUND).entity("Movie is not in the database.\nUnable to Delete").build(); - } + System.out.println("*** Calling deleteMovie ***"); - inventory.remove(imdbId); - return Response.status(Response.Status.OK).build(); - } + if (null == inventory.get(imdbId)) { + return Response.status(Response.Status.NOT_FOUND).entity("Movie is not in the database.\nUnable to Delete") + .build(); + } - @GET - @Path("/listmovies") - @Produces({ "application/json" }) - public List listMovies() { + inventory.remove(imdbId); + return Response.status(Response.Status.OK).build(); + } - return inventory.values().stream().collect(Collectors.toCollection(ArrayList::new)); - } + @GET + @Path("/listmovies") + @Produces({ "application/json" }) + public List listMovies() { + + return inventory.values().stream().collect(Collectors.toCollection(ArrayList::new)); + } } diff --git a/resteasy/src/main/webapp/script.js b/resteasy/src/main/webapp/script.js new file mode 100644 index 0000000000..88198887b0 --- /dev/null +++ b/resteasy/src/main/webapp/script.js @@ -0,0 +1,16 @@ +function call(url, type, data) { + var request = $.ajax({ + url : url, + method : "GET", + data : (data) ? JSON.stringify(data) : "", + dataType : type + }); + + request.done(function(resp) { + console.log(resp); + }); + + request.fail(function(jqXHR, textStatus) { + console.log("Request failed: " + textStatus); + }); +}; \ No newline at end of file From 9472f0dd65885d49f365256e88c594009a460f0a Mon Sep 17 00:00:00 2001 From: mujahid Date: Sat, 25 Mar 2017 15:18:20 +0800 Subject: [PATCH 075/149] BAEL-347 - change solr-fulltext-search to solr (#1462) * solr-fulltext-search module created * solr-fulltext-search modue created * solr-fulltext-search change s * pom changes merged from upstream * removed integration tests from mvn build * Refactoring test class * removed test profile * module solr-fulltext-search changed to solr * module solr added in parent pom * delete file * removed unused file, changed test to default solr port --- pom.xml | 2 +- .../solr/fulltexh/search/model/Product.java | 5 - .../search/service/SolrSearchService.java | 5 - .../ItemSearchServiceIntegrationTest.java | 374 ------------------ {solr-fulltext-search => solr}/pom.xml | 4 +- .../solr/fulltext/search/model/Item.java | 0 .../search/service/ItemSearchService.java | 0 .../search/service/ItemSearchServiceImpl.java | 0 .../service/ItemSearchServiceLiveTest.java | 2 +- 9 files changed, 4 insertions(+), 388 deletions(-) delete mode 100644 solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/model/Product.java delete mode 100644 solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/service/SolrSearchService.java delete mode 100644 solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.java rename {solr-fulltext-search => solr}/pom.xml (94%) rename {solr-fulltext-search => solr}/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java (100%) rename {solr-fulltext-search => solr}/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java (100%) rename {solr-fulltext-search => solr}/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java (100%) rename {solr-fulltext-search => solr}/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java (99%) diff --git a/pom.xml b/pom.xml index 791430ba0e..4796a11ac4 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ rxjava selenium-junit-testng - solr-fulltext-search + solr spark-java spring-akka diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/model/Product.java b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/model/Product.java deleted file mode 100644 index b5fb334282..0000000000 --- a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/model/Product.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.solr.fulltexh.search.model; - -public class Product { - -} diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/service/SolrSearchService.java b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/service/SolrSearchService.java deleted file mode 100644 index aa511f0e1b..0000000000 --- a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltexh/search/service/SolrSearchService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.solr.fulltexh.search.service; - -public interface SolrSearchService { - -} diff --git a/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.java b/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.java deleted file mode 100644 index 94661ffc2e..0000000000 --- a/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.java +++ /dev/null @@ -1,374 +0,0 @@ -package com.baeldung.solr.fulltext.search.service; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.List; -import java.util.Map; - -import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.impl.HttpSolrClient; -import org.apache.solr.client.solrj.response.FacetField.Count; -import org.apache.solr.client.solrj.response.QueryResponse; -import org.apache.solr.client.solrj.response.RangeFacet; -import org.apache.solr.client.solrj.response.SpellCheckResponse; -import org.apache.solr.client.solrj.response.SpellCheckResponse.Suggestion; -import org.apache.solr.client.solrj.response.SuggesterResponse; -import org.apache.solr.common.SolrDocument; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.baeldung.solr.fulltext.search.model.Item; - -public class ItemSearchServiceIntegrationTest { - - private static SolrClient solrClient; - private static ItemSearchService itemSearchService; - private static final String solrUrl = "http://localhost:8987/solr/item"; - - @BeforeClass - public static void initBeans() throws Exception { - solrClient = new HttpSolrClient.Builder(solrUrl).build(); - itemSearchService = new ItemSearchServiceImpl(solrClient); - - solrClient.commit(); - } - - @Before - public void clearSolrData() throws Exception { - solrClient.deleteByQuery("*:*"); - } - - @Test - public void whenIndexing_thenAvailableOnRetrieval() throws Exception { - itemSearchService.index("hm0001", "Washing Machine", "Home Appliances", 450f); - final SolrDocument indexedDoc = solrClient.getById("hm0001"); - assertEquals("hm0001", indexedDoc.get("id")); - } - - @Test - public void whenIndexingBean_thenAvailableOnRetrieval() throws Exception { - Item item = new Item(); - item.setId("hm0002"); - item.setCategory("Televisions"); - item.setDescription("LED TV 32"); - item.setPrice(500); - itemSearchService.indexBean(item); - } - - @Test - public void whenSearchingByBasicQuery_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "LED TV 32", "Brand1 Home Appliances", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("brand1"); - query.setStart(0); - query.setRows(10); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(3, items.size()); - - } - - @Test - public void whenSearchingWithWildCard_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "LED TV 32", "Brand1 Home Appliances", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("*rand?"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(3, items.size()); - } - - @Test - public void whenSearchingWithLogicalOperators_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "Brand2 LED TV 32", "Washing Appliances", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("brand1 AND (Washing OR Refrigerator)"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(2, items.size()); - } - - @Test - public void whenSearchingWithFields_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("0003", "Brand2 LED TV 32", "Brand1 Washing Home Appliances", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("description:Brand* AND category:*Washing*"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(1, items.size()); - } - - @Test - public void whenSearchingWithPhrase_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("washing MachIne"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(2, items.size()); - } - - @Test - public void whenSearchingWithRealPhrase_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("\"washing machine\""); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(1, items.size()); - } - - @Test - public void whenSearchingPhraseWithProximity_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 450f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 450f); - itemSearchService.index("hm0003", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("\"Washing equipment\"~2"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(1, items.size()); - } - - @Test - public void whenSearchingWithPriceRange_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Dishwasher", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("price:[100 TO 300]"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(3, items.size()); - } - - @Test - public void whenSearchingWithPriceRangeInclusiveExclusive_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Dishwasher", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 450f); - - SolrQuery query = new SolrQuery(); - query.setQuery("price:{100 TO 300]"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(2, items.size()); - } - - @Test - public void whenSearchingWithFilterQuery_thenAllMatchingItemsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing tools and equipment ", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("price:[100 TO 300]"); - query.addFilterQuery("description:Brand1", "category:Home Appliances"); - - QueryResponse response = solrClient.query(query); - List items = response.getBeans(Item.class); - - assertEquals(2, items.size()); - } - - @Test - public void whenSearchingWithFacetFields_thenAllMatchingFacetsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - query.addFacetField("category"); - - QueryResponse response = solrClient.query(query); - List facetResults = response.getFacetField("category").getValues(); - - assertEquals(2, facetResults.size()); - - for (Count count : facetResults) { - if ("categorya".equalsIgnoreCase(count.getName())) { - assertEquals(2, count.getCount()); - } else if ("categoryb".equalsIgnoreCase(count.getName())) { - assertEquals(2, count.getCount()); - } else { - fail("unexpected category"); - } - } - } - - @Test - public void whenSearchingWithFacetQuery_thenAllMatchingFacetsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - - query.addFacetQuery("Washing OR Refrigerator"); - query.addFacetQuery("Brand2"); - - QueryResponse response = solrClient.query(query); - Map facetQueryMap = response.getFacetQuery(); - - assertEquals(2, facetQueryMap.size()); - - for (Map.Entry entry : facetQueryMap.entrySet()) { - String facetQuery = entry.getKey(); - - if ("Washing OR Refrigerator".equals(facetQuery)) { - assertEquals(Integer.valueOf(2), entry.getValue()); - } else if ("Brand2".equals(facetQuery)) { - assertEquals(Integer.valueOf(2), entry.getValue()); - } else { - fail("unexpected query"); - } - - } - } - - @Test - public void whenSearchingWithFacetRange_thenAllMatchingFacetsShouldAvialble() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "CategoryA", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "CategoryA", 125f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "CategoryB", 150f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "CategoryB", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - - query.addNumericRangeFacet("price", 100, 275, 25); - - QueryResponse response = solrClient.query(query); - List rangeFacets = response.getFacetRanges().get(0).getCounts(); - - assertEquals(7, rangeFacets.size()); - } - - @Test - public void whenSearchingWithHitHighlighting_thenKeywordsShouldBeHighlighted() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("Appliances"); - query.setHighlight(true); - query.addHighlightField("category"); - query.setHighlightSimplePre(""); - query.setHighlightSimplePost(""); - QueryResponse response = solrClient.query(query); - - Map>> hitHighlightedMap = response.getHighlighting(); - Map> highlightedFieldMap = hitHighlightedMap.get("hm0001"); - List highlightedList = highlightedFieldMap.get("category"); - String highLightedText = highlightedList.get(0); - - assertEquals("Home Appliances", highLightedText); - - } - - @Test - public void whenSearchingWithKeywordWithMistake_thenSpellingSuggestionsShouldBeReturned() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Washing equipments", 250f); - - SolrQuery query = new SolrQuery(); - query.setQuery("hme"); - query.set("spellcheck", "on"); - QueryResponse response = solrClient.query(query); - - SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse(); - - assertEquals(false, spellCheckResponse.isCorrectlySpelled()); - - Suggestion suggestion = spellCheckResponse.getSuggestions().get(0); - - assertEquals("hme", suggestion.getToken()); - - List alternatives = suggestion.getAlternatives(); - String alternative = alternatives.get(0); - - assertEquals("home", alternative); - } - - @Test - public void whenSearchingWithIncompleteKeyword_thenKeywordSuggestionsShouldBeReturned() throws Exception { - itemSearchService.index("hm0001", "Brand1 Washing Machine", "Home Appliances", 100f); - itemSearchService.index("hm0002", "Brand1 Refrigerator", "Home Appliances", 300f); - itemSearchService.index("hm0003", "Brand2 Ceiling Fan", "Home Appliances", 200f); - itemSearchService.index("hm0004", "Brand2 Dishwasher", "Home washing equipments", 250f); - - SolrQuery query = new SolrQuery(); - query.setRequestHandler("/suggest"); - query.set("suggest", "true"); - query.set("suggest.build", "true"); - query.set("suggest.dictionary", "mySuggester"); - query.set("suggest.q", "Hom"); - QueryResponse response = solrClient.query(query); - - SuggesterResponse suggesterResponse = response.getSuggesterResponse(); - Map> suggestedTerms = suggesterResponse.getSuggestedTerms(); - List suggestions = suggestedTerms.get("mySuggester"); - - assertEquals(2, suggestions.size()); - - for (String term : suggestions) { - if (!"Home Appliances".equals(term) && !"Home washing equipments".equals(term)) { - fail("Unexpected suggestions"); - } - } - - } - -} diff --git a/solr-fulltext-search/pom.xml b/solr/pom.xml similarity index 94% rename from solr-fulltext-search/pom.xml rename to solr/pom.xml index 5b2950d64c..e784d87157 100644 --- a/solr-fulltext-search/pom.xml +++ b/solr/pom.xml @@ -3,10 +3,10 @@ 4.0.0 com.baeldung - solr-fulltext-search + solr 0.0.1-SNAPSHOT jar - solr-fulltext-search + solr diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java b/solr/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java similarity index 100% rename from solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java rename to solr/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java b/solr/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java similarity index 100% rename from solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java rename to solr/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java b/solr/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java similarity index 100% rename from solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java rename to solr/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java diff --git a/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java b/solr/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java similarity index 99% rename from solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java rename to solr/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java index 1489d40787..3704f4c34c 100644 --- a/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java +++ b/solr/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceLiveTest.java @@ -26,7 +26,7 @@ public class ItemSearchServiceLiveTest { private static SolrClient solrClient; private static ItemSearchService itemSearchService; - private static final String solrUrl = "http://localhost:8987/solr/item"; + private static final String solrUrl = "http://localhost:8983/solr/item"; @BeforeClass public static void initBeans() throws Exception { From b6e271f1f0f111e02ae6141918fb32cae265067f Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 25 Mar 2017 09:25:51 +0100 Subject: [PATCH 076/149] spring-custom-aop -> spring-aop (#1489) --- pom.xml | 1 + {spring-custom-aop => spring-aop}/pom.xml | 0 .../src/main/java/org/baeldung/Application.java | 0 .../src/main/java/org/baeldung/ExampleAspect.java | 0 .../src/main/java/org/baeldung/LogExecutionTime.java | 0 .../src/main/java/org/baeldung/Service.java | 0 .../src/test/java/org/baeldung/CustomAnnotationTest.java | 0 7 files changed, 1 insertion(+) rename {spring-custom-aop => spring-aop}/pom.xml (100%) rename {spring-custom-aop => spring-aop}/src/main/java/org/baeldung/Application.java (100%) rename {spring-custom-aop => spring-aop}/src/main/java/org/baeldung/ExampleAspect.java (100%) rename {spring-custom-aop => spring-aop}/src/main/java/org/baeldung/LogExecutionTime.java (100%) rename {spring-custom-aop => spring-aop}/src/main/java/org/baeldung/Service.java (100%) rename {spring-custom-aop => spring-aop}/src/test/java/org/baeldung/CustomAnnotationTest.java (100%) diff --git a/pom.xml b/pom.xml index 4796a11ac4..187ff60dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,7 @@ spring-cloud spring-core spring-cucumber + spring-aop spring-data-cassandra spring-data-couchbase-2 spring-data-dynamodb diff --git a/spring-custom-aop/pom.xml b/spring-aop/pom.xml similarity index 100% rename from spring-custom-aop/pom.xml rename to spring-aop/pom.xml diff --git a/spring-custom-aop/src/main/java/org/baeldung/Application.java b/spring-aop/src/main/java/org/baeldung/Application.java similarity index 100% rename from spring-custom-aop/src/main/java/org/baeldung/Application.java rename to spring-aop/src/main/java/org/baeldung/Application.java diff --git a/spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java b/spring-aop/src/main/java/org/baeldung/ExampleAspect.java similarity index 100% rename from spring-custom-aop/src/main/java/org/baeldung/ExampleAspect.java rename to spring-aop/src/main/java/org/baeldung/ExampleAspect.java diff --git a/spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java b/spring-aop/src/main/java/org/baeldung/LogExecutionTime.java similarity index 100% rename from spring-custom-aop/src/main/java/org/baeldung/LogExecutionTime.java rename to spring-aop/src/main/java/org/baeldung/LogExecutionTime.java diff --git a/spring-custom-aop/src/main/java/org/baeldung/Service.java b/spring-aop/src/main/java/org/baeldung/Service.java similarity index 100% rename from spring-custom-aop/src/main/java/org/baeldung/Service.java rename to spring-aop/src/main/java/org/baeldung/Service.java diff --git a/spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java b/spring-aop/src/test/java/org/baeldung/CustomAnnotationTest.java similarity index 100% rename from spring-custom-aop/src/test/java/org/baeldung/CustomAnnotationTest.java rename to spring-aop/src/test/java/org/baeldung/CustomAnnotationTest.java From efc2043e5285cfa3067a86b2cd1c19c7bacc73d3 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 25 Mar 2017 10:37:00 +0100 Subject: [PATCH 077/149] KafkaProducerConfig refactor (#1488) --- pom.xml | 1 + .../spring/kafka/KafkaProducerConfig.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 187ff60dd2..5c85e94546 100644 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,7 @@ spring-jms spring-jooq spring-jpa + spring-kafka spring-katharsis spring-ldap spring-mockito diff --git a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java index 84d57c9e92..7e2527b36e 100644 --- a/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java +++ b/spring-kafka/src/main/java/com/baeldung/spring/kafka/KafkaProducerConfig.java @@ -1,8 +1,5 @@ package com.baeldung.spring.kafka; -import java.util.HashMap; -import java.util.Map; - import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; @@ -13,6 +10,9 @@ import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; import org.springframework.kafka.support.serializer.JsonSerializer; +import java.util.HashMap; +import java.util.Map; + @Configuration public class KafkaProducerConfig { @@ -21,30 +21,30 @@ public class KafkaProducerConfig { @Bean public ProducerFactory producerFactory() { - Map configProps = new HashMap(); + Map configProps = new HashMap<>(); configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - return new DefaultKafkaProducerFactory(configProps); + return new DefaultKafkaProducerFactory<>(configProps); } @Bean public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate(producerFactory()); + return new KafkaTemplate<>(producerFactory()); } @Bean public ProducerFactory greetingProducerFactory() { - Map configProps = new HashMap(); + Map configProps = new HashMap<>(); configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); - return new DefaultKafkaProducerFactory(configProps); + return new DefaultKafkaProducerFactory<>(configProps); } @Bean public KafkaTemplate greetingKafkaTemplate() { - return new KafkaTemplate(greetingProducerFactory()); + return new KafkaTemplate<>(greetingProducerFactory()); } } From 669f4d6dce4d528dd6c8ad7382ebd33c2b6a71db Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 25 Mar 2017 11:00:16 +0100 Subject: [PATCH 078/149] Further build improvements (#1486) --- javaslang/pom.xml | 12 ++++++++++++ ...st.java => PropertyBasedLongRunningUnitTest.java} | 2 +- metrics/pom.xml | 12 ++++++++++++ ...{MetricsTest.java => MetricsIntegrationTest.java} | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) rename javaslang/src/test/java/com/baeldung/javaslang/{PropertyBasedTest.java => PropertyBasedLongRunningUnitTest.java} (97%) rename metrics/src/test/java/com/baeldung/metrics/core/{MetricsTest.java => MetricsIntegrationTest.java} (99%) diff --git a/javaslang/pom.xml b/javaslang/pom.xml index 941aac0802..3770cce548 100644 --- a/javaslang/pom.xml +++ b/javaslang/pom.xml @@ -44,6 +44,18 @@ 1.8 + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LongRunningUnitTest.java + **/*ManualTest.java + + true + + \ No newline at end of file diff --git a/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedLongRunningUnitTest.java similarity index 97% rename from javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java rename to javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedLongRunningUnitTest.java index 43f3d6e6a0..2bbc92563e 100644 --- a/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedTest.java +++ b/javaslang/src/test/java/com/baeldung/javaslang/PropertyBasedLongRunningUnitTest.java @@ -11,7 +11,7 @@ import java.util.function.Predicate; import static javaslang.API.*; -public class PropertyBasedTest { +public class PropertyBasedLongRunningUnitTest { private static Predicate divisibleByTwo = i -> i % 2 == 0; private static Predicate divisibleByFive = i -> i % 5 == 0; diff --git a/metrics/pom.xml b/metrics/pom.xml index 0f5cb2f135..6111ebf30b 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -69,6 +69,18 @@ ${maven.compiler.target}
+ + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LongRunningUnitTest.java + **/*ManualTest.java + + true + +
diff --git a/metrics/src/test/java/com/baeldung/metrics/core/MetricsTest.java b/metrics/src/test/java/com/baeldung/metrics/core/MetricsIntegrationTest.java similarity index 99% rename from metrics/src/test/java/com/baeldung/metrics/core/MetricsTest.java rename to metrics/src/test/java/com/baeldung/metrics/core/MetricsIntegrationTest.java index e876de6e65..352911f1e1 100644 --- a/metrics/src/test/java/com/baeldung/metrics/core/MetricsTest.java +++ b/metrics/src/test/java/com/baeldung/metrics/core/MetricsIntegrationTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -public class MetricsTest { +public class MetricsIntegrationTest { @Test public void whenMarkMeter_thenCorrectRates() throws InterruptedException { Meter meter = new Meter(); From 21f9df63304a695cdcbb714fa1138b2b610d6751 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Sat, 25 Mar 2017 11:14:09 +0100 Subject: [PATCH 079/149] Bael 518 protobuffer (#1400) * BEEL-518 code for protobuf article * BEEL-518 add generated protobuf class * BEEL-550 use newest version of protobuff * BAEL-518 Small refactoring in protobuffer module * BEEL-518 simpler protobuf example * BEEL-518 proper package --- book | 0 ...638124c-9a1b-4d25-8fce-cc223d472e77.events | Bin 0 -> 663 bytes ...2ba9cbe-1a44-428e-a710-13b1bdc67c4b.events | Bin 0 -> 675 bytes ...f420ffc-0c3b-403e-bb8c-66cf499c773e.events | Bin 0 -> 714 bytes ...72a057b-adea-4c69-83a0-0431318823e7.events | Bin 0 -> 714 bytes .../baeldung/protobuf/AddressBookProtos.java | 1488 +++-------------- .../src/main/resources/addressbook.proto | 13 +- .../com/baeldung/protobuf/ProtobufTest.java | 21 +- .../cache/2d9345a30d2cc31bb3091d70a8ef6c18.0 | 24 + .../cache/2d9345a30d2cc31bb3091d70a8ef6c18.1 | 39 + .../cache/4b217e04ba52215f3a6b64d28f6729c6.0 | 13 + .../cache/4b217e04ba52215f3a6b64d28f6729c6.1 | 7 + spring-rest/src/test/resources/cache/journal | 12 + 13 files changed, 334 insertions(+), 1283 deletions(-) create mode 100644 book create mode 100644 events/MessagesAggregate/0638124c-9a1b-4d25-8fce-cc223d472e77.events create mode 100644 events/MessagesAggregate/d2ba9cbe-1a44-428e-a710-13b1bdc67c4b.events create mode 100644 events/ToDoItem/bf420ffc-0c3b-403e-bb8c-66cf499c773e.events create mode 100644 events/ToDoItem/e72a057b-adea-4c69-83a0-0431318823e7.events create mode 100644 spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.0 create mode 100644 spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.1 create mode 100644 spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.0 create mode 100644 spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.1 create mode 100644 spring-rest/src/test/resources/cache/journal diff --git a/book b/book new file mode 100644 index 0000000000..e69de29bb2 diff --git a/events/MessagesAggregate/0638124c-9a1b-4d25-8fce-cc223d472e77.events b/events/MessagesAggregate/0638124c-9a1b-4d25-8fce-cc223d472e77.events new file mode 100644 index 0000000000000000000000000000000000000000..d3ce8b9cea0dd432ac369a37c2281c78d9b98a7e GIT binary patch literal 663 zcmbV|O-jT-5QW>lR}iwuMzoUZpQL+2la094g?NEVcV!%K5-_dI0X&Q6bPV`|2s5#k zkM~|ZJ|S`wRy{gK;D8;Ns1!I899XF=>B?F{Db5s13#b9>d#M{$Hxi55sSA`1qR6q< zPAU^%MRG2w!1aM41f_H|R^De`pnD?D4^6&f`hoWKAlfhgLaqndeHZ@*_YUaJ B!ZiQ@ literal 0 HcmV?d00001 diff --git a/events/MessagesAggregate/d2ba9cbe-1a44-428e-a710-13b1bdc67c4b.events b/events/MessagesAggregate/d2ba9cbe-1a44-428e-a710-13b1bdc67c4b.events new file mode 100644 index 0000000000000000000000000000000000000000..2ab0ec469f90efe0f09ac43380e620738125a0ce GIT binary patch literal 675 zcmbV|%}T>S6otpVuOMX6O|dtbJ2TECVTcP6+_;gxz@3@fT4)<+Qf>D>kxytW_=5Zfq;`tB*0x5>wYHZMYbnJj zsX$c>3`oX+Q6&R8lz=*5NYT1zg7~;eY*%?8UZ(AOp3|;f=lsO$zRT~q>uAn58>XJq z?R$6_z4}uojbEhZFGRi=ioWMv-`w$X*-o;@_BmU0*}mOwvPApqtcI~K4h>(N#4vE5 z`xW;DiFzs;Ax2t87g9)2&YT7lm4b>Z1IcKL<)Ar*@VjYa^gm3WiSaSy2c~bDUyf@2 P?4RiPsJpMnKgGQPZ1>2E literal 0 HcmV?d00001 diff --git a/events/ToDoItem/bf420ffc-0c3b-403e-bb8c-66cf499c773e.events b/events/ToDoItem/bf420ffc-0c3b-403e-bb8c-66cf499c773e.events new file mode 100644 index 0000000000000000000000000000000000000000..d805fc253e397456a33c8b6b1d5de5b42af14043 GIT binary patch literal 714 zcmbV|zfQw25QojqD0Xq?%h7JV7VWa4wZIZDKbZ8x1G`)uw4yLanf^6zW@SR)}or zljN9E01U!|L_y$v7eJ|yq%lEjLF~66b z`0zFTPJP(3GQBN1=5_2(;j;D5v5j;H`(amxJoS%6aZ&Oj=0$lP4z~WgwDoy7^AFc{ zq~ajD;_9BUWowLYNlS==&&Fbh(3#YL);uXqE^5>LuU(_KK>HOtfjN)6Eggw}KSBIa O>O%GB!p#bgm3{(Qui1M5 literal 0 HcmV?d00001 diff --git a/events/ToDoItem/e72a057b-adea-4c69-83a0-0431318823e7.events b/events/ToDoItem/e72a057b-adea-4c69-83a0-0431318823e7.events new file mode 100644 index 0000000000000000000000000000000000000000..3d67b74ced4494b9f3af4f38b6bef9622c565329 GIT binary patch literal 714 zcmbWzJx&8L5CveCo(s|90)j+6E}nP~`%%_5>nj*HK~+N8vP_jRk>3B#IU(=vG1IB*R{7Y%t8QQRn$EuaQ$R#GmsTG+NS5^qs15XGEZ zMQGa|C^3PFjR&U*0gaYgI;S+Z;&&kCq3VgZ>1BMX=q-;Gzw%fP)hgfTW#Qp&&r~=) ze7%0BKEhdrepeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - java.util.List - getPhonesList(); + java.util.List + getNumbersList(); /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - AddressBookProtos.Person.PhoneNumber getPhones(int index); + int getNumbersCount(); /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - int getPhonesCount(); + java.lang.String getNumbers(int index); /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - java.util.List - getPhonesOrBuilderList(); - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - AddressBookProtos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( - int index); + com.google.protobuf.ByteString + getNumbersBytes(int index); } /** @@ -108,7 +102,7 @@ public final class AddressBookProtos { name_ = ""; id_ = 0; email_ = ""; - phones_ = java.util.Collections.emptyList(); + numbers_ = com.google.protobuf.LazyStringArrayList.EMPTY; } @java.lang.Override @@ -158,12 +152,12 @@ public final class AddressBookProtos { break; } case 34: { + com.google.protobuf.ByteString bs = input.readBytes(); if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - phones_ = new java.util.ArrayList(); + numbers_ = new com.google.protobuf.LazyStringArrayList(); mutable_bitField0_ |= 0x00000008; } - phones_.add( - input.readMessage(AddressBookProtos.Person.PhoneNumber.PARSER, extensionRegistry)); + numbers_.add(bs); break; } } @@ -175,7 +169,7 @@ public final class AddressBookProtos { e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - phones_ = java.util.Collections.unmodifiableList(phones_); + numbers_ = numbers_.getUnmodifiableView(); } this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); @@ -184,819 +178,14 @@ public final class AddressBookProtos { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return AddressBookProtos.internal_static_protobuf_Person_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_Person_descriptor; } protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_Person_fieldAccessorTable + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_Person_fieldAccessorTable .ensureFieldAccessorsInitialized( - AddressBookProtos.Person.class, AddressBookProtos.Person.Builder.class); - } - - /** - * Protobuf enum {@code protobuf.Person.PhoneType} - */ - public enum PhoneType - implements com.google.protobuf.ProtocolMessageEnum { - /** - * MOBILE = 0; - */ - MOBILE(0), - /** - * HOME = 1; - */ - HOME(1), - /** - * WORK = 2; - */ - WORK(2),; - - /** - * MOBILE = 0; - */ - public static final int MOBILE_VALUE = 0; - /** - * HOME = 1; - */ - public static final int HOME_VALUE = 1; - /** - * WORK = 2; - */ - public static final int WORK_VALUE = 2; - - - public final int getNumber() { - return value; - } - - /** - * @deprecated Use {@link #forNumber(int)} instead. - */ - @java.lang.Deprecated - public static PhoneType valueOf(int value) { - return forNumber(value); - } - - public static PhoneType forNumber(int value) { - switch (value) { - case 0: - return MOBILE; - case 1: - return HOME; - case 2: - return WORK; - default: - return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - - private static final com.google.protobuf.Internal.EnumLiteMap< - PhoneType> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public PhoneType findValueByNumber(int number) { - return PhoneType.forNumber(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(ordinal()); - } - - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return AddressBookProtos.Person.getDescriptor().getEnumTypes().get(0); - } - - private static final PhoneType[] VALUES = values(); - - public static PhoneType valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int value; - - private PhoneType(int value) { - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:protobuf.Person.PhoneType) - } - - public interface PhoneNumberOrBuilder extends - // @@protoc_insertion_point(interface_extends:protobuf.Person.PhoneNumber) - com.google.protobuf.MessageOrBuilder { - - /** - * required string number = 1; - */ - boolean hasNumber(); - - /** - * required string number = 1; - */ - java.lang.String getNumber(); - - /** - * required string number = 1; - */ - com.google.protobuf.ByteString - getNumberBytes(); - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - boolean hasType(); - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - AddressBookProtos.Person.PhoneType getType(); - } - - /** - * Protobuf type {@code protobuf.Person.PhoneNumber} - */ - public static final class PhoneNumber extends - com.google.protobuf.GeneratedMessageV3 implements - // @@protoc_insertion_point(message_implements:protobuf.Person.PhoneNumber) - PhoneNumberOrBuilder { - // Use PhoneNumber.newBuilder() to construct. - private PhoneNumber(com.google.protobuf.GeneratedMessageV3.Builder builder) { - super(builder); - } - - private PhoneNumber() { - number_ = ""; - type_ = 1; - } - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - - private PhoneNumber( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - this(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - number_ = bs; - break; - } - case 16: { - int rawValue = input.readEnum(); - AddressBookProtos.Person.PhoneType value = AddressBookProtos.Person.PhoneType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(2, rawValue); - } else { - bitField0_ |= 0x00000002; - type_ = rawValue; - } - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return AddressBookProtos.internal_static_protobuf_Person_PhoneNumber_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_Person_PhoneNumber_fieldAccessorTable - .ensureFieldAccessorsInitialized( - AddressBookProtos.Person.PhoneNumber.class, AddressBookProtos.Person.PhoneNumber.Builder.class); - } - - private int bitField0_; - public static final int NUMBER_FIELD_NUMBER = 1; - private volatile java.lang.Object number_; - - /** - * required string number = 1; - */ - public boolean hasNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * required string number = 1; - */ - public java.lang.String getNumber() { - java.lang.Object ref = number_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - number_ = s; - } - return s; - } - } - - /** - * required string number = 1; - */ - public com.google.protobuf.ByteString - getNumberBytes() { - java.lang.Object ref = number_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - number_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int TYPE_FIELD_NUMBER = 2; - private int type_; - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public boolean hasType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public AddressBookProtos.Person.PhoneType getType() { - AddressBookProtos.Person.PhoneType result = AddressBookProtos.Person.PhoneType.valueOf(type_); - return result == null ? AddressBookProtos.Person.PhoneType.HOME : result; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - if (!hasNumber()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 1, number_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, type_); - } - unknownFields.writeTo(output); - } - - public int getSerializedSize() { - int size = memoizedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, number_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, type_); - } - size += unknownFields.getSerializedSize(); - memoizedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - public boolean equals(final java.lang.Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof AddressBookProtos.Person.PhoneNumber)) { - return super.equals(obj); - } - AddressBookProtos.Person.PhoneNumber other = (AddressBookProtos.Person.PhoneNumber) obj; - - boolean result = true; - result = result && (hasNumber() == other.hasNumber()); - if (hasNumber()) { - result = result && getNumber() - .equals(other.getNumber()); - } - result = result && (hasType() == other.hasType()); - if (hasType()) { - result = result && type_ == other.type_; - } - result = result && unknownFields.equals(other.unknownFields); - return result; - } - - @java.lang.Override - public int hashCode() { - if (memoizedHashCode != 0) { - return memoizedHashCode; - } - int hash = 41; - hash = (19 * hash) + getDescriptorForType().hashCode(); - if (hasNumber()) { - hash = (37 * hash) + NUMBER_FIELD_NUMBER; - hash = (53 * hash) + getNumber().hashCode(); - } - if (hasType()) { - hash = (37 * hash) + TYPE_FIELD_NUMBER; - hash = (53 * hash) + type_; - } - hash = (29 * hash) + unknownFields.hashCode(); - memoizedHashCode = hash; - return hash; - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public static AddressBookProtos.Person.PhoneNumber parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input); - } - - public static AddressBookProtos.Person.PhoneNumber parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseDelimitedWithIOException(PARSER, input, extensionRegistry); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input); - } - - public static AddressBookProtos.Person.PhoneNumber parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return com.google.protobuf.GeneratedMessageV3 - .parseWithIOException(PARSER, input, extensionRegistry); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder() { - return DEFAULT_INSTANCE.toBuilder(); - } - - public static Builder newBuilder(AddressBookProtos.Person.PhoneNumber prototype) { - return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return this == DEFAULT_INSTANCE - ? new Builder() : new Builder().mergeFrom(this); - } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code protobuf.Person.PhoneNumber} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessageV3.Builder implements - // @@protoc_insertion_point(builder_implements:protobuf.Person.PhoneNumber) - AddressBookProtos.Person.PhoneNumberOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return AddressBookProtos.internal_static_protobuf_Person_PhoneNumber_descriptor; - } - - protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_Person_PhoneNumber_fieldAccessorTable - .ensureFieldAccessorsInitialized( - AddressBookProtos.Person.PhoneNumber.class, AddressBookProtos.Person.PhoneNumber.Builder.class); - } - - // Construct using AddressBookProtos.Person.PhoneNumber.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessageV3 - .alwaysUseFieldBuilders) { - } - } - - public Builder clear() { - super.clear(); - number_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - type_ = 1; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return AddressBookProtos.internal_static_protobuf_Person_PhoneNumber_descriptor; - } - - public AddressBookProtos.Person.PhoneNumber getDefaultInstanceForType() { - return AddressBookProtos.Person.PhoneNumber.getDefaultInstance(); - } - - public AddressBookProtos.Person.PhoneNumber build() { - AddressBookProtos.Person.PhoneNumber result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public AddressBookProtos.Person.PhoneNumber buildPartial() { - AddressBookProtos.Person.PhoneNumber result = new AddressBookProtos.Person.PhoneNumber(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.number_ = number_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.type_ = type_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder clone() { - return (Builder) super.clone(); - } - - public Builder setField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.setField(field, value); - } - - public Builder clearField( - com.google.protobuf.Descriptors.FieldDescriptor field) { - return (Builder) super.clearField(field); - } - - public Builder clearOneof( - com.google.protobuf.Descriptors.OneofDescriptor oneof) { - return (Builder) super.clearOneof(oneof); - } - - public Builder setRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - int index, Object value) { - return (Builder) super.setRepeatedField(field, index, value); - } - - public Builder addRepeatedField( - com.google.protobuf.Descriptors.FieldDescriptor field, - Object value) { - return (Builder) super.addRepeatedField(field, value); - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof AddressBookProtos.Person.PhoneNumber) { - return mergeFrom((AddressBookProtos.Person.PhoneNumber) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(AddressBookProtos.Person.PhoneNumber other) { - if (other == AddressBookProtos.Person.PhoneNumber.getDefaultInstance()) - return this; - if (other.hasNumber()) { - bitField0_ |= 0x00000001; - number_ = other.number_; - onChanged(); - } - if (other.hasType()) { - setType(other.getType()); - } - this.mergeUnknownFields(other.unknownFields); - onChanged(); - return this; - } - - public final boolean isInitialized() { - if (!hasNumber()) { - return false; - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - AddressBookProtos.Person.PhoneNumber parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (AddressBookProtos.Person.PhoneNumber) e.getUnfinishedMessage(); - throw e.unwrapIOException(); - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object number_ = ""; - - /** - * required string number = 1; - */ - public boolean hasNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * required string number = 1; - */ - public java.lang.String getNumber() { - java.lang.Object ref = number_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - number_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * required string number = 1; - */ - public com.google.protobuf.ByteString - getNumberBytes() { - java.lang.Object ref = number_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - number_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * required string number = 1; - */ - public Builder setNumber( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - number_ = value; - onChanged(); - return this; - } - - /** - * required string number = 1; - */ - public Builder clearNumber() { - bitField0_ = (bitField0_ & ~0x00000001); - number_ = getDefaultInstance().getNumber(); - onChanged(); - return this; - } - - /** - * required string number = 1; - */ - public Builder setNumberBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - number_ = value; - onChanged(); - return this; - } - - private int type_ = 1; - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public boolean hasType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public AddressBookProtos.Person.PhoneType getType() { - AddressBookProtos.Person.PhoneType result = AddressBookProtos.Person.PhoneType.valueOf(type_); - return result == null ? AddressBookProtos.Person.PhoneType.HOME : result; - } - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public Builder setType(AddressBookProtos.Person.PhoneType value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - type_ = value.getNumber(); - onChanged(); - return this; - } - - /** - * optional .protobuf.Person.PhoneType type = 2 [default = HOME]; - */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000002); - type_ = 1; - onChanged(); - return this; - } - - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.mergeUnknownFields(unknownFields); - } - - - // @@protoc_insertion_point(builder_scope:protobuf.Person.PhoneNumber) - } - - // @@protoc_insertion_point(class_scope:protobuf.Person.PhoneNumber) - private static final AddressBookProtos.Person.PhoneNumber DEFAULT_INSTANCE; - - static { - DEFAULT_INSTANCE = new AddressBookProtos.Person.PhoneNumber(); - } - - public static AddressBookProtos.Person.PhoneNumber getDefaultInstance() { - return DEFAULT_INSTANCE; - } - - @java.lang.Deprecated - public static final com.google.protobuf.Parser - PARSER = new com.google.protobuf.AbstractParser() { - public PhoneNumber parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new PhoneNumber(input, extensionRegistry); - } - }; - - public static com.google.protobuf.Parser parser() { - return PARSER; - } - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public AddressBookProtos.Person.PhoneNumber getDefaultInstanceForType() { - return DEFAULT_INSTANCE; - } - + com.baeldung.protobuf.AddressBookProtos.Person.class, com.baeldung.protobuf.AddressBookProtos.Person.Builder.class); } private int bitField0_; @@ -1107,44 +296,37 @@ public final class AddressBookProtos { } } - public static final int PHONES_FIELD_NUMBER = 4; - private java.util.List phones_; + public static final int NUMBERS_FIELD_NUMBER = 4; + private com.google.protobuf.LazyStringList numbers_; /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public java.util.List getPhonesList() { - return phones_; + public com.google.protobuf.ProtocolStringList + getNumbersList() { + return numbers_; } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public java.util.List - getPhonesOrBuilderList() { - return phones_; + public int getNumbersCount() { + return numbers_.size(); } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public int getPhonesCount() { - return phones_.size(); + public java.lang.String getNumbers(int index) { + return numbers_.get(index); } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public AddressBookProtos.Person.PhoneNumber getPhones(int index) { - return phones_.get(index); - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public AddressBookProtos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( - int index) { - return phones_.get(index); + public com.google.protobuf.ByteString + getNumbersBytes(int index) { + return numbers_.getByteString(index); } private byte memoizedIsInitialized = -1; @@ -1162,12 +344,6 @@ public final class AddressBookProtos { memoizedIsInitialized = 0; return false; } - for (int i = 0; i < getPhonesCount(); i++) { - if (!getPhones(i).isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } memoizedIsInitialized = 1; return true; } @@ -1183,8 +359,8 @@ public final class AddressBookProtos { if (((bitField0_ & 0x00000004) == 0x00000004)) { com.google.protobuf.GeneratedMessageV3.writeString(output, 3, email_); } - for (int i = 0; i < phones_.size(); i++) { - output.writeMessage(4, phones_.get(i)); + for (int i = 0; i < numbers_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 4, numbers_.getRaw(i)); } unknownFields.writeTo(output); } @@ -1204,9 +380,13 @@ public final class AddressBookProtos { if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, email_); } - for (int i = 0; i < phones_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(4, phones_.get(i)); + { + int dataSize = 0; + for (int i = 0; i < numbers_.size(); i++) { + dataSize += computeStringSizeNoTag(numbers_.getRaw(i)); + } + size += dataSize; + size += 1 * getNumbersList().size(); } size += unknownFields.getSerializedSize(); memoizedSize = size; @@ -1220,10 +400,10 @@ public final class AddressBookProtos { if (obj == this) { return true; } - if (!(obj instanceof AddressBookProtos.Person)) { + if (!(obj instanceof com.baeldung.protobuf.AddressBookProtos.Person)) { return super.equals(obj); } - AddressBookProtos.Person other = (AddressBookProtos.Person) obj; + com.baeldung.protobuf.AddressBookProtos.Person other = (com.baeldung.protobuf.AddressBookProtos.Person) obj; boolean result = true; result = result && (hasName() == other.hasName()); @@ -1241,8 +421,8 @@ public final class AddressBookProtos { result = result && getEmail() .equals(other.getEmail()); } - result = result && getPhonesList() - .equals(other.getPhonesList()); + result = result && getNumbersList() + .equals(other.getNumbersList()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -1266,47 +446,47 @@ public final class AddressBookProtos { hash = (37 * hash) + EMAIL_FIELD_NUMBER; hash = (53 * hash) + getEmail().hashCode(); } - if (getPhonesCount() > 0) { - hash = (37 * hash) + PHONES_FIELD_NUMBER; - hash = (53 * hash) + getPhonesList().hashCode(); + if (getNumbersCount() > 0) { + hash = (37 * hash) + NUMBERS_FIELD_NUMBER; + hash = (53 * hash) + getNumbersList().hashCode(); } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static AddressBookProtos.Person parseFrom(byte[] data) + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static AddressBookProtos.Person parseFrom(java.io.InputStream input) + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -1314,13 +494,13 @@ public final class AddressBookProtos { .parseWithIOException(PARSER, input, extensionRegistry); } - public static AddressBookProtos.Person parseDelimitedFrom(java.io.InputStream input) + public static com.baeldung.protobuf.AddressBookProtos.Person parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input); } - public static AddressBookProtos.Person parseDelimitedFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -1328,14 +508,14 @@ public final class AddressBookProtos { .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } - public static AddressBookProtos.Person parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.Person parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -1351,7 +531,7 @@ public final class AddressBookProtos { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(AddressBookProtos.Person prototype) { + public static Builder newBuilder(com.baeldung.protobuf.AddressBookProtos.Person prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @@ -1373,20 +553,20 @@ public final class AddressBookProtos { public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:protobuf.Person) - AddressBookProtos.PersonOrBuilder { + com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return AddressBookProtos.internal_static_protobuf_Person_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_Person_descriptor; } protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_Person_fieldAccessorTable + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_Person_fieldAccessorTable .ensureFieldAccessorsInitialized( - AddressBookProtos.Person.class, AddressBookProtos.Person.Builder.class); + com.baeldung.protobuf.AddressBookProtos.Person.class, com.baeldung.protobuf.AddressBookProtos.Person.Builder.class); } - // Construct using AddressBookProtos.Person.newBuilder() + // Construct using com.baeldung.protobuf.AddressBookProtos.Person.newBuilder() private Builder() { maybeForceBuilderInitialization(); } @@ -1400,7 +580,6 @@ public final class AddressBookProtos { private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessageV3 .alwaysUseFieldBuilders) { - getPhonesFieldBuilder(); } } @@ -1412,34 +591,30 @@ public final class AddressBookProtos { bitField0_ = (bitField0_ & ~0x00000002); email_ = ""; bitField0_ = (bitField0_ & ~0x00000004); - if (phonesBuilder_ == null) { - phones_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); - } else { - phonesBuilder_.clear(); - } + numbers_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); return this; } public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return AddressBookProtos.internal_static_protobuf_Person_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_Person_descriptor; } - public AddressBookProtos.Person getDefaultInstanceForType() { - return AddressBookProtos.Person.getDefaultInstance(); + public com.baeldung.protobuf.AddressBookProtos.Person getDefaultInstanceForType() { + return com.baeldung.protobuf.AddressBookProtos.Person.getDefaultInstance(); } - public AddressBookProtos.Person build() { - AddressBookProtos.Person result = buildPartial(); + public com.baeldung.protobuf.AddressBookProtos.Person build() { + com.baeldung.protobuf.AddressBookProtos.Person result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public AddressBookProtos.Person buildPartial() { - AddressBookProtos.Person result = new AddressBookProtos.Person(this); + public com.baeldung.protobuf.AddressBookProtos.Person buildPartial() { + com.baeldung.protobuf.AddressBookProtos.Person result = new com.baeldung.protobuf.AddressBookProtos.Person(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -1454,15 +629,11 @@ public final class AddressBookProtos { to_bitField0_ |= 0x00000004; } result.email_ = email_; - if (phonesBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008)) { - phones_ = java.util.Collections.unmodifiableList(phones_); - bitField0_ = (bitField0_ & ~0x00000008); - } - result.phones_ = phones_; - } else { - result.phones_ = phonesBuilder_.build(); + if (((bitField0_ & 0x00000008) == 0x00000008)) { + numbers_ = numbers_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000008); } + result.numbers_ = numbers_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -1501,16 +672,16 @@ public final class AddressBookProtos { } public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof AddressBookProtos.Person) { - return mergeFrom((AddressBookProtos.Person) other); + if (other instanceof com.baeldung.protobuf.AddressBookProtos.Person) { + return mergeFrom((com.baeldung.protobuf.AddressBookProtos.Person) other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(AddressBookProtos.Person other) { - if (other == AddressBookProtos.Person.getDefaultInstance()) return this; + public Builder mergeFrom(com.baeldung.protobuf.AddressBookProtos.Person other) { + if (other == com.baeldung.protobuf.AddressBookProtos.Person.getDefaultInstance()) return this; if (other.hasName()) { bitField0_ |= 0x00000001; name_ = other.name_; @@ -1524,31 +695,15 @@ public final class AddressBookProtos { email_ = other.email_; onChanged(); } - if (phonesBuilder_ == null) { - if (!other.phones_.isEmpty()) { - if (phones_.isEmpty()) { - phones_ = other.phones_; - bitField0_ = (bitField0_ & ~0x00000008); - } else { - ensurePhonesIsMutable(); - phones_.addAll(other.phones_); - } - onChanged(); - } - } else { - if (!other.phones_.isEmpty()) { - if (phonesBuilder_.isEmpty()) { - phonesBuilder_.dispose(); - phonesBuilder_ = null; - phones_ = other.phones_; - bitField0_ = (bitField0_ & ~0x00000008); - phonesBuilder_ = - com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? - getPhonesFieldBuilder() : null; - } else { - phonesBuilder_.addAllMessages(other.phones_); - } + if (!other.numbers_.isEmpty()) { + if (numbers_.isEmpty()) { + numbers_ = other.numbers_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureNumbersIsMutable(); + numbers_.addAll(other.numbers_); } + onChanged(); } this.mergeUnknownFields(other.unknownFields); onChanged(); @@ -1562,11 +717,6 @@ public final class AddressBookProtos { if (!hasId()) { return false; } - for (int i = 0; i < getPhonesCount(); i++) { - if (!getPhones(i).isInitialized()) { - return false; - } - } return true; } @@ -1574,11 +724,11 @@ public final class AddressBookProtos { com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - AddressBookProtos.Person parsedMessage = null; + com.baeldung.protobuf.AddressBookProtos.Person parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (AddressBookProtos.Person) e.getUnfinishedMessage(); + parsedMessage = (com.baeldung.protobuf.AddressBookProtos.Person) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { if (parsedMessage != null) { @@ -1790,266 +940,109 @@ public final class AddressBookProtos { return this; } - private java.util.List phones_ = - java.util.Collections.emptyList(); + private com.google.protobuf.LazyStringList numbers_ = com.google.protobuf.LazyStringArrayList.EMPTY; - private void ensurePhonesIsMutable() { + private void ensureNumbersIsMutable() { if (!((bitField0_ & 0x00000008) == 0x00000008)) { - phones_ = new java.util.ArrayList(phones_); + numbers_ = new com.google.protobuf.LazyStringArrayList(numbers_); bitField0_ |= 0x00000008; } } - private com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person.PhoneNumber, AddressBookProtos.Person.PhoneNumber.Builder, AddressBookProtos.Person.PhoneNumberOrBuilder> phonesBuilder_; - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public java.util.List getPhonesList() { - if (phonesBuilder_ == null) { - return java.util.Collections.unmodifiableList(phones_); - } else { - return phonesBuilder_.getMessageList(); - } + public com.google.protobuf.ProtocolStringList + getNumbersList() { + return numbers_.getUnmodifiableView(); } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public int getPhonesCount() { - if (phonesBuilder_ == null) { - return phones_.size(); - } else { - return phonesBuilder_.getCount(); - } + public int getNumbersCount() { + return numbers_.size(); } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public AddressBookProtos.Person.PhoneNumber getPhones(int index) { - if (phonesBuilder_ == null) { - return phones_.get(index); - } else { - return phonesBuilder_.getMessage(index); - } + public java.lang.String getNumbers(int index) { + return numbers_.get(index); } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public Builder setPhones( - int index, AddressBookProtos.Person.PhoneNumber value) { - if (phonesBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePhonesIsMutable(); - phones_.set(index, value); - onChanged(); - } else { - phonesBuilder_.setMessage(index, value); + public com.google.protobuf.ByteString + getNumbersBytes(int index) { + return numbers_.getByteString(index); + } + + /** + * repeated string numbers = 4; + */ + public Builder setNumbers( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); } + ensureNumbersIsMutable(); + numbers_.set(index, value); + onChanged(); return this; } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public Builder setPhones( - int index, AddressBookProtos.Person.PhoneNumber.Builder builderForValue) { - if (phonesBuilder_ == null) { - ensurePhonesIsMutable(); - phones_.set(index, builderForValue.build()); - onChanged(); - } else { - phonesBuilder_.setMessage(index, builderForValue.build()); + public Builder addNumbers( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); } + ensureNumbersIsMutable(); + numbers_.add(value); + onChanged(); return this; } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public Builder addPhones(AddressBookProtos.Person.PhoneNumber value) { - if (phonesBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePhonesIsMutable(); - phones_.add(value); - onChanged(); - } else { - phonesBuilder_.addMessage(value); - } + public Builder addAllNumbers( + java.lang.Iterable values) { + ensureNumbersIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, numbers_); + onChanged(); return this; } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public Builder addPhones( - int index, AddressBookProtos.Person.PhoneNumber value) { - if (phonesBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePhonesIsMutable(); - phones_.add(index, value); - onChanged(); - } else { - phonesBuilder_.addMessage(index, value); - } + public Builder clearNumbers() { + numbers_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); return this; } /** - * repeated .protobuf.Person.PhoneNumber phones = 4; + * repeated string numbers = 4; */ - public Builder addPhones( - AddressBookProtos.Person.PhoneNumber.Builder builderForValue) { - if (phonesBuilder_ == null) { - ensurePhonesIsMutable(); - phones_.add(builderForValue.build()); - onChanged(); - } else { - phonesBuilder_.addMessage(builderForValue.build()); + public Builder addNumbersBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); } + ensureNumbersIsMutable(); + numbers_.add(value); + onChanged(); return this; } - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public Builder addPhones( - int index, AddressBookProtos.Person.PhoneNumber.Builder builderForValue) { - if (phonesBuilder_ == null) { - ensurePhonesIsMutable(); - phones_.add(index, builderForValue.build()); - onChanged(); - } else { - phonesBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public Builder addAllPhones( - java.lang.Iterable values) { - if (phonesBuilder_ == null) { - ensurePhonesIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll( - values, phones_); - onChanged(); - } else { - phonesBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public Builder clearPhones() { - if (phonesBuilder_ == null) { - phones_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); - } else { - phonesBuilder_.clear(); - } - return this; - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public Builder removePhones(int index) { - if (phonesBuilder_ == null) { - ensurePhonesIsMutable(); - phones_.remove(index); - onChanged(); - } else { - phonesBuilder_.remove(index); - } - return this; - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public AddressBookProtos.Person.PhoneNumber.Builder getPhonesBuilder( - int index) { - return getPhonesFieldBuilder().getBuilder(index); - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public AddressBookProtos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( - int index) { - if (phonesBuilder_ == null) { - return phones_.get(index); - } else { - return phonesBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public java.util.List - getPhonesOrBuilderList() { - if (phonesBuilder_ != null) { - return phonesBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(phones_); - } - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public AddressBookProtos.Person.PhoneNumber.Builder addPhonesBuilder() { - return getPhonesFieldBuilder().addBuilder( - AddressBookProtos.Person.PhoneNumber.getDefaultInstance()); - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public AddressBookProtos.Person.PhoneNumber.Builder addPhonesBuilder( - int index) { - return getPhonesFieldBuilder().addBuilder( - index, AddressBookProtos.Person.PhoneNumber.getDefaultInstance()); - } - - /** - * repeated .protobuf.Person.PhoneNumber phones = 4; - */ - public java.util.List - getPhonesBuilderList() { - return getPhonesFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person.PhoneNumber, AddressBookProtos.Person.PhoneNumber.Builder, AddressBookProtos.Person.PhoneNumberOrBuilder> - getPhonesFieldBuilder() { - if (phonesBuilder_ == null) { - phonesBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person.PhoneNumber, AddressBookProtos.Person.PhoneNumber.Builder, AddressBookProtos.Person.PhoneNumberOrBuilder>( - phones_, - ((bitField0_ & 0x00000008) == 0x00000008), - getParentForChildren(), - isClean()); - phones_ = null; - } - return phonesBuilder_; - } - public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { return super.setUnknownFields(unknownFields); @@ -2065,13 +1058,13 @@ public final class AddressBookProtos { } // @@protoc_insertion_point(class_scope:protobuf.Person) - private static final AddressBookProtos.Person DEFAULT_INSTANCE; + private static final com.baeldung.protobuf.AddressBookProtos.Person DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new AddressBookProtos.Person(); + DEFAULT_INSTANCE = new com.baeldung.protobuf.AddressBookProtos.Person(); } - public static AddressBookProtos.Person getDefaultInstance() { + public static com.baeldung.protobuf.AddressBookProtos.Person getDefaultInstance() { return DEFAULT_INSTANCE; } @@ -2095,7 +1088,7 @@ public final class AddressBookProtos { return PARSER; } - public AddressBookProtos.Person getDefaultInstanceForType() { + public com.baeldung.protobuf.AddressBookProtos.Person getDefaultInstanceForType() { return DEFAULT_INSTANCE; } @@ -2108,13 +1101,13 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - java.util.List + java.util.List getPeopleList(); /** * repeated .protobuf.Person people = 1; */ - AddressBookProtos.Person getPeople(int index); + com.baeldung.protobuf.AddressBookProtos.Person getPeople(int index); /** * repeated .protobuf.Person people = 1; @@ -2124,13 +1117,13 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - java.util.List + java.util.List getPeopleOrBuilderList(); /** * repeated .protobuf.Person people = 1; */ - AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( + com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( int index); } @@ -2181,11 +1174,11 @@ public final class AddressBookProtos { } case 10: { if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) { - people_ = new java.util.ArrayList(); + people_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000001; } people_.add( - input.readMessage(AddressBookProtos.Person.PARSER, extensionRegistry)); + input.readMessage(com.baeldung.protobuf.AddressBookProtos.Person.PARSER, extensionRegistry)); break; } } @@ -2206,30 +1199,30 @@ public final class AddressBookProtos { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; } protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_AddressBook_fieldAccessorTable + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_AddressBook_fieldAccessorTable .ensureFieldAccessorsInitialized( - AddressBookProtos.AddressBook.class, AddressBookProtos.AddressBook.Builder.class); + com.baeldung.protobuf.AddressBookProtos.AddressBook.class, com.baeldung.protobuf.AddressBookProtos.AddressBook.Builder.class); } public static final int PEOPLE_FIELD_NUMBER = 1; - private java.util.List people_; + private java.util.List people_; /** * repeated .protobuf.Person people = 1; */ - public java.util.List getPeopleList() { + public java.util.List getPeopleList() { return people_; } /** * repeated .protobuf.Person people = 1; */ - public java.util.List + public java.util.List getPeopleOrBuilderList() { return people_; } @@ -2244,14 +1237,14 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.Person getPeople(int index) { + public com.baeldung.protobuf.AddressBookProtos.Person getPeople(int index) { return people_.get(index); } /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( + public com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( int index) { return people_.get(index); } @@ -2302,10 +1295,10 @@ public final class AddressBookProtos { if (obj == this) { return true; } - if (!(obj instanceof AddressBookProtos.AddressBook)) { + if (!(obj instanceof com.baeldung.protobuf.AddressBookProtos.AddressBook)) { return super.equals(obj); } - AddressBookProtos.AddressBook other = (AddressBookProtos.AddressBook) obj; + com.baeldung.protobuf.AddressBookProtos.AddressBook other = (com.baeldung.protobuf.AddressBookProtos.AddressBook) obj; boolean result = true; result = result && getPeopleList() @@ -2330,38 +1323,38 @@ public final class AddressBookProtos { return hash; } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static AddressBookProtos.AddressBook parseFrom(byte[] data) + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static AddressBookProtos.AddressBook parseFrom(java.io.InputStream input) + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -2369,13 +1362,13 @@ public final class AddressBookProtos { .parseWithIOException(PARSER, input, extensionRegistry); } - public static AddressBookProtos.AddressBook parseDelimitedFrom(java.io.InputStream input) + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseDelimitedWithIOException(PARSER, input); } - public static AddressBookProtos.AddressBook parseDelimitedFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -2383,14 +1376,14 @@ public final class AddressBookProtos { .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { return com.google.protobuf.GeneratedMessageV3 .parseWithIOException(PARSER, input); } - public static AddressBookProtos.AddressBook parseFrom( + public static com.baeldung.protobuf.AddressBookProtos.AddressBook parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { @@ -2406,7 +1399,7 @@ public final class AddressBookProtos { return DEFAULT_INSTANCE.toBuilder(); } - public static Builder newBuilder(AddressBookProtos.AddressBook prototype) { + public static Builder newBuilder(com.baeldung.protobuf.AddressBookProtos.AddressBook prototype) { return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); } @@ -2428,20 +1421,20 @@ public final class AddressBookProtos { public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:protobuf.AddressBook) - AddressBookProtos.AddressBookOrBuilder { + com.baeldung.protobuf.AddressBookProtos.AddressBookOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; } protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return AddressBookProtos.internal_static_protobuf_AddressBook_fieldAccessorTable + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_AddressBook_fieldAccessorTable .ensureFieldAccessorsInitialized( - AddressBookProtos.AddressBook.class, AddressBookProtos.AddressBook.Builder.class); + com.baeldung.protobuf.AddressBookProtos.AddressBook.class, com.baeldung.protobuf.AddressBookProtos.AddressBook.Builder.class); } - // Construct using AddressBookProtos.AddressBook.newBuilder() + // Construct using com.baeldung.protobuf.AddressBookProtos.AddressBook.newBuilder() private Builder() { maybeForceBuilderInitialization(); } @@ -2472,23 +1465,23 @@ public final class AddressBookProtos { public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; + return com.baeldung.protobuf.AddressBookProtos.internal_static_protobuf_AddressBook_descriptor; } - public AddressBookProtos.AddressBook getDefaultInstanceForType() { - return AddressBookProtos.AddressBook.getDefaultInstance(); + public com.baeldung.protobuf.AddressBookProtos.AddressBook getDefaultInstanceForType() { + return com.baeldung.protobuf.AddressBookProtos.AddressBook.getDefaultInstance(); } - public AddressBookProtos.AddressBook build() { - AddressBookProtos.AddressBook result = buildPartial(); + public com.baeldung.protobuf.AddressBookProtos.AddressBook build() { + com.baeldung.protobuf.AddressBookProtos.AddressBook result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public AddressBookProtos.AddressBook buildPartial() { - AddressBookProtos.AddressBook result = new AddressBookProtos.AddressBook(this); + public com.baeldung.protobuf.AddressBookProtos.AddressBook buildPartial() { + com.baeldung.protobuf.AddressBookProtos.AddressBook result = new com.baeldung.protobuf.AddressBookProtos.AddressBook(this); int from_bitField0_ = bitField0_; if (peopleBuilder_ == null) { if (((bitField0_ & 0x00000001) == 0x00000001)) { @@ -2536,16 +1529,16 @@ public final class AddressBookProtos { } public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof AddressBookProtos.AddressBook) { - return mergeFrom((AddressBookProtos.AddressBook) other); + if (other instanceof com.baeldung.protobuf.AddressBookProtos.AddressBook) { + return mergeFrom((com.baeldung.protobuf.AddressBookProtos.AddressBook) other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(AddressBookProtos.AddressBook other) { - if (other == AddressBookProtos.AddressBook.getDefaultInstance()) return this; + public Builder mergeFrom(com.baeldung.protobuf.AddressBookProtos.AddressBook other) { + if (other == com.baeldung.protobuf.AddressBookProtos.AddressBook.getDefaultInstance()) return this; if (peopleBuilder_ == null) { if (!other.people_.isEmpty()) { if (people_.isEmpty()) { @@ -2590,11 +1583,11 @@ public final class AddressBookProtos { com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - AddressBookProtos.AddressBook parsedMessage = null; + com.baeldung.protobuf.AddressBookProtos.AddressBook parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (AddressBookProtos.AddressBook) e.getUnfinishedMessage(); + parsedMessage = (com.baeldung.protobuf.AddressBookProtos.AddressBook) e.getUnfinishedMessage(); throw e.unwrapIOException(); } finally { if (parsedMessage != null) { @@ -2606,23 +1599,23 @@ public final class AddressBookProtos { private int bitField0_; - private java.util.List people_ = + private java.util.List people_ = java.util.Collections.emptyList(); private void ensurePeopleIsMutable() { if (!((bitField0_ & 0x00000001) == 0x00000001)) { - people_ = new java.util.ArrayList(people_); + people_ = new java.util.ArrayList(people_); bitField0_ |= 0x00000001; } } private com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person, AddressBookProtos.Person.Builder, AddressBookProtos.PersonOrBuilder> peopleBuilder_; + com.baeldung.protobuf.AddressBookProtos.Person, com.baeldung.protobuf.AddressBookProtos.Person.Builder, com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder> peopleBuilder_; /** * repeated .protobuf.Person people = 1; */ - public java.util.List getPeopleList() { + public java.util.List getPeopleList() { if (peopleBuilder_ == null) { return java.util.Collections.unmodifiableList(people_); } else { @@ -2644,7 +1637,7 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.Person getPeople(int index) { + public com.baeldung.protobuf.AddressBookProtos.Person getPeople(int index) { if (peopleBuilder_ == null) { return people_.get(index); } else { @@ -2656,7 +1649,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder setPeople( - int index, AddressBookProtos.Person value) { + int index, com.baeldung.protobuf.AddressBookProtos.Person value) { if (peopleBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -2674,7 +1667,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder setPeople( - int index, AddressBookProtos.Person.Builder builderForValue) { + int index, com.baeldung.protobuf.AddressBookProtos.Person.Builder builderForValue) { if (peopleBuilder_ == null) { ensurePeopleIsMutable(); people_.set(index, builderForValue.build()); @@ -2688,7 +1681,7 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public Builder addPeople(AddressBookProtos.Person value) { + public Builder addPeople(com.baeldung.protobuf.AddressBookProtos.Person value) { if (peopleBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -2706,7 +1699,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder addPeople( - int index, AddressBookProtos.Person value) { + int index, com.baeldung.protobuf.AddressBookProtos.Person value) { if (peopleBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -2724,7 +1717,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder addPeople( - AddressBookProtos.Person.Builder builderForValue) { + com.baeldung.protobuf.AddressBookProtos.Person.Builder builderForValue) { if (peopleBuilder_ == null) { ensurePeopleIsMutable(); people_.add(builderForValue.build()); @@ -2739,7 +1732,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder addPeople( - int index, AddressBookProtos.Person.Builder builderForValue) { + int index, com.baeldung.protobuf.AddressBookProtos.Person.Builder builderForValue) { if (peopleBuilder_ == null) { ensurePeopleIsMutable(); people_.add(index, builderForValue.build()); @@ -2754,7 +1747,7 @@ public final class AddressBookProtos { * repeated .protobuf.Person people = 1; */ public Builder addAllPeople( - java.lang.Iterable values) { + java.lang.Iterable values) { if (peopleBuilder_ == null) { ensurePeopleIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -2797,7 +1790,7 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.Person.Builder getPeopleBuilder( + public com.baeldung.protobuf.AddressBookProtos.Person.Builder getPeopleBuilder( int index) { return getPeopleFieldBuilder().getBuilder(index); } @@ -2805,7 +1798,7 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( + public com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder getPeopleOrBuilder( int index) { if (peopleBuilder_ == null) { return people_.get(index); @@ -2817,7 +1810,7 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public java.util.List + public java.util.List getPeopleOrBuilderList() { if (peopleBuilder_ != null) { return peopleBuilder_.getMessageOrBuilderList(); @@ -2829,34 +1822,34 @@ public final class AddressBookProtos { /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.Person.Builder addPeopleBuilder() { + public com.baeldung.protobuf.AddressBookProtos.Person.Builder addPeopleBuilder() { return getPeopleFieldBuilder().addBuilder( - AddressBookProtos.Person.getDefaultInstance()); + com.baeldung.protobuf.AddressBookProtos.Person.getDefaultInstance()); } /** * repeated .protobuf.Person people = 1; */ - public AddressBookProtos.Person.Builder addPeopleBuilder( + public com.baeldung.protobuf.AddressBookProtos.Person.Builder addPeopleBuilder( int index) { return getPeopleFieldBuilder().addBuilder( - index, AddressBookProtos.Person.getDefaultInstance()); + index, com.baeldung.protobuf.AddressBookProtos.Person.getDefaultInstance()); } /** * repeated .protobuf.Person people = 1; */ - public java.util.List + public java.util.List getPeopleBuilderList() { return getPeopleFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person, AddressBookProtos.Person.Builder, AddressBookProtos.PersonOrBuilder> + com.baeldung.protobuf.AddressBookProtos.Person, com.baeldung.protobuf.AddressBookProtos.Person.Builder, com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder> getPeopleFieldBuilder() { if (peopleBuilder_ == null) { peopleBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< - AddressBookProtos.Person, AddressBookProtos.Person.Builder, AddressBookProtos.PersonOrBuilder>( + com.baeldung.protobuf.AddressBookProtos.Person, com.baeldung.protobuf.AddressBookProtos.Person.Builder, com.baeldung.protobuf.AddressBookProtos.PersonOrBuilder>( people_, ((bitField0_ & 0x00000001) == 0x00000001), getParentForChildren(), @@ -2881,13 +1874,13 @@ public final class AddressBookProtos { } // @@protoc_insertion_point(class_scope:protobuf.AddressBook) - private static final AddressBookProtos.AddressBook DEFAULT_INSTANCE; + private static final com.baeldung.protobuf.AddressBookProtos.AddressBook DEFAULT_INSTANCE; static { - DEFAULT_INSTANCE = new AddressBookProtos.AddressBook(); + DEFAULT_INSTANCE = new com.baeldung.protobuf.AddressBookProtos.AddressBook(); } - public static AddressBookProtos.AddressBook getDefaultInstance() { + public static com.baeldung.protobuf.AddressBookProtos.AddressBook getDefaultInstance() { return DEFAULT_INSTANCE; } @@ -2911,7 +1904,7 @@ public final class AddressBookProtos { return PARSER; } - public AddressBookProtos.AddressBook getDefaultInstanceForType() { + public com.baeldung.protobuf.AddressBookProtos.AddressBook getDefaultInstanceForType() { return DEFAULT_INSTANCE; } @@ -2922,11 +1915,6 @@ public final class AddressBookProtos { private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_protobuf_Person_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_protobuf_Person_PhoneNumber_descriptor; - private static final - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable - internal_static_protobuf_Person_PhoneNumber_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_protobuf_AddressBook_descriptor; private static final @@ -2943,15 +1931,11 @@ public final class AddressBookProtos { static { java.lang.String[] descriptorData = { - "\n\020routeguide.proto\022\010protobuf\"\333\001\n\006Person\022" + - "\014\n\004name\030\001 \002(\t\022\n\n\002id\030\002 \002(\005\022\r\n\005email\030\003 \001(\t" + - "\022,\n\006phones\030\004 \003(\0132\034.protobuf.Person.Phone" + - "Number\032M\n\013PhoneNumber\022\016\n\006number\030\001 \002(\t\022.\n" + - "\004type\030\002 \001(\0162\032.protobuf.Person.PhoneType:" + - "\004HOME\"+\n\tPhoneType\022\n\n\006MOBILE\020\000\022\010\n\004HOME\020\001" + - "\022\010\n\004WORK\020\002\"/\n\013AddressBook\022 \n\006people\030\001 \003(" + - "\0132\020.protobuf.PersonB*\n\025com.baeldung.prot" + - "obufB\021AddressBookProtos" + "\n\020routeguide.proto\022\010protobuf\"B\n\006Person\022\014" + + "\n\004name\030\001 \002(\t\022\n\n\002id\030\002 \002(\005\022\r\n\005email\030\003 \001(\t\022" + + "\017\n\007numbers\030\004 \003(\t\"/\n\013AddressBook\022 \n\006peopl" + + "e\030\001 \003(\0132\020.protobuf.PersonB*\n\025com.baeldun" + + "g.protobufB\021AddressBookProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -2970,13 +1954,7 @@ public final class AddressBookProtos { internal_static_protobuf_Person_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_protobuf_Person_descriptor, - new java.lang.String[]{"Name", "Id", "Email", "Phones",}); - internal_static_protobuf_Person_PhoneNumber_descriptor = - internal_static_protobuf_Person_descriptor.getNestedTypes().get(0); - internal_static_protobuf_Person_PhoneNumber_fieldAccessorTable = new - com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( - internal_static_protobuf_Person_PhoneNumber_descriptor, - new java.lang.String[]{"Number", "Type",}); + new java.lang.String[]{"Name", "Id", "Email", "Numbers",}); internal_static_protobuf_AddressBook_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_protobuf_AddressBook_fieldAccessorTable = new @@ -2985,5 +1963,5 @@ public final class AddressBookProtos { new java.lang.String[]{"People",}); } - // @@protoc_insertion_point(outer_class_scope) +// @@protoc_insertion_point(outer_class_scope) } \ No newline at end of file diff --git a/protobuffer/src/main/resources/addressbook.proto b/protobuffer/src/main/resources/addressbook.proto index 1c0946b7f7..fe3f9c4174 100644 --- a/protobuffer/src/main/resources/addressbook.proto +++ b/protobuffer/src/main/resources/addressbook.proto @@ -8,18 +8,7 @@ message Person { required int32 id = 2; optional string email = 3; - enum PhoneType { - MOBILE = 0; - HOME = 1; - WORK = 2; - } - - message PhoneNumber { - required string number = 1; - optional PhoneType type = 2 [default = HOME]; - } - - repeated PhoneNumber phones = 4; + repeated string numbers = 4; } message AddressBook { diff --git a/protobuffer/src/test/java/com/baeldung/protobuf/ProtobufTest.java b/protobuffer/src/test/java/com/baeldung/protobuf/ProtobufTest.java index 74447eb4a4..f0d64092f0 100644 --- a/protobuffer/src/test/java/com/baeldung/protobuf/ProtobufTest.java +++ b/protobuffer/src/test/java/com/baeldung/protobuf/ProtobufTest.java @@ -28,24 +28,18 @@ public class ProtobufTest { int id = new Random().nextInt(); String name = "Michael Program"; String number = "01234567890"; - AddressBookProtos.Person.PhoneType type = AddressBookProtos.Person.PhoneType.HOME; AddressBookProtos.Person person = AddressBookProtos.Person.newBuilder() .setId(id) .setName(name) .setEmail(email) - .addPhones( - AddressBookProtos.Person.PhoneNumber.newBuilder() - .setNumber(number) - .setType(type)) + .addNumbers(number) .build(); //then assertEquals(person.getEmail(), email); assertEquals(person.getId(), id); assertEquals(person.getName(), name); - assertEquals(person.getPhones(0).getNumber(), number); - assertEquals(person.getPhones(0).getType(), type); - assertEquals(person.getPhonesList().size(), 1); + assertEquals(person.getNumbers(0), number); } @@ -56,16 +50,12 @@ public class ProtobufTest { int id = new Random().nextInt(); String name = "Michael Program"; String number = "01234567890"; - AddressBookProtos.Person.PhoneType type = AddressBookProtos.Person.PhoneType.HOME; AddressBookProtos.Person person = AddressBookProtos.Person.newBuilder() .setId(id) .setName(name) .setEmail(email) - .addPhones( - AddressBookProtos.Person.PhoneNumber.newBuilder() - .setNumber(number) - .setType(type)) + .addNumbers(number) .build(); //when @@ -82,9 +72,8 @@ public class ProtobufTest { assertEquals(deserialized.getPeople(0).getEmail(), email); assertEquals(deserialized.getPeople(0).getId(), id); assertEquals(deserialized.getPeople(0).getName(), name); - assertEquals(deserialized.getPeople(0).getPhones(0).getNumber(), number); - assertEquals(deserialized.getPeople(0).getPhones(0).getType(), type); - assertEquals(deserialized.getPeople(0).getPhonesList().size(), 1); + assertEquals(deserialized.getPeople(0).getNumbers(0), number); + } } diff --git a/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.0 b/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.0 new file mode 100644 index 0000000000..78d5d3fcf3 --- /dev/null +++ b/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.0 @@ -0,0 +1,24 @@ +https://publicobject.com/helloworld.txt +GET +0 +HTTP/1.1 200 OK +10 +Server: nginx/1.10.0 (Ubuntu) +Date: Thu, 09 Mar 2017 10:17:25 GMT +Content-Type: text/plain +Content-Length: 1759 +Last-Modified: Tue, 27 May 2014 02:35:47 GMT +Connection: keep-alive +ETag: "5383fa03-6df" +Accept-Ranges: bytes +OkHttp-Sent-Millis: 1489054646765 +OkHttp-Received-Millis: 1489054646966 + +TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +4 +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 +0 +TLSv1.2 diff --git a/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.1 b/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.1 new file mode 100644 index 0000000000..83db2312f0 --- /dev/null +++ b/spring-rest/src/test/resources/cache/2d9345a30d2cc31bb3091d70a8ef6c18.1 @@ -0,0 +1,39 @@ + + \\ // + \\ .ooo. // + .@@@@@@@@@. + :@@@@@@@@@@@@@: + :@@. '@@@@@' .@@: + @@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@ + + :@@ :@@@@@@@@@@@@@@@@@. @@: + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@ '@@@@@@@@@@@@@@@@@, @@@ + @@@@@@@@@@@@@@@@@ + '@@@@@@@@@@@@@@@' + @@@@ @@@@ + @@@@ @@@@ + @@@@ @@@@ + '@@' '@@' + + :@@@. + .@@@@@@@: +@@ `@@ @@` @@ @@ + .@@@@'@@@@: +@@ `@@ @@` @@ @@ + @@@ @@@ +@@ `@@ @@` @@ @@ + .@@ @@: +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@;@@@@@ + @@@ @@@ +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@@@@@@@@ + @@@ @@@ +@@ @@@ `@@@@@@@@@@` @@ @@ @@@ :@@ + @@@ @@@ +@@@@@ `@@@@@@@@@@` @@ @@ @@# @@+ + @@@ @@@ +@@@@@+ `@@ @@` @@ @@ @@: @@# + @@: .@@` +@@@+@@ `@@ @@` @@ @@ @@# @@+ + @@@. .@@@ +@@ @@@ `@@ @@` @@ @@ @@@ ,@@ + @@@@@@@@@ +@@ @@@ `@@ @@` @@@@ @@@@ @@@@#@@@@ + @@@@@@@ +@@ #@@ `@@ @@` @@@@: @@@@: @@'@@@@@ + @@: + @@: + @@: diff --git a/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.0 b/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.0 new file mode 100644 index 0000000000..82c93f0a86 --- /dev/null +++ b/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.0 @@ -0,0 +1,13 @@ +http://publicobject.com/helloworld.txt +GET +0 +HTTP/1.1 301 Moved Permanently +8 +Server: nginx/1.10.0 (Ubuntu) +Date: Thu, 09 Mar 2017 10:17:25 GMT +Content-Type: text/html +Content-Length: 194 +Connection: keep-alive +Location: https://publicobject.com/helloworld.txt +OkHttp-Sent-Millis: 1489054646977 +OkHttp-Received-Millis: 1489054647185 diff --git a/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.1 b/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.1 new file mode 100644 index 0000000000..acf72eabe3 --- /dev/null +++ b/spring-rest/src/test/resources/cache/4b217e04ba52215f3a6b64d28f6729c6.1 @@ -0,0 +1,7 @@ + +301 Moved Permanently + +

301 Moved Permanently

+
nginx/1.10.0 (Ubuntu)
+ + diff --git a/spring-rest/src/test/resources/cache/journal b/spring-rest/src/test/resources/cache/journal new file mode 100644 index 0000000000..44b709c179 --- /dev/null +++ b/spring-rest/src/test/resources/cache/journal @@ -0,0 +1,12 @@ +libcore.io.DiskLruCache +1 +201105 +2 + +DIRTY 4b217e04ba52215f3a6b64d28f6729c6 +CLEAN 4b217e04ba52215f3a6b64d28f6729c6 333 194 +DIRTY 2d9345a30d2cc31bb3091d70a8ef6c18 +CLEAN 2d9345a30d2cc31bb3091d70a8ef6c18 7618 1759 +READ 4b217e04ba52215f3a6b64d28f6729c6 +DIRTY 4b217e04ba52215f3a6b64d28f6729c6 +CLEAN 4b217e04ba52215f3a6b64d28f6729c6 333 194 From 9b48f77c8d1f48aef50f6936b24d0b94f607b550 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 25 Mar 2017 11:47:14 +0100 Subject: [PATCH 080/149] ContactNumberValidator refactor (#1491) * ContactNumberValidator refactor * Refactor controllers --- .../customvalidator/ContactNumberValidator.java | 5 +---- .../com/baeldung/web/controller/UserController.java | 7 ++++--- .../web/controller/ValidatedPhoneController.java | 13 ++++++------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java index 713b7429cf..dea6b9099b 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java +++ b/spring-mvc-java/src/main/java/com/baeldung/customvalidator/ContactNumberValidator.java @@ -11,10 +11,7 @@ public class ContactNumberValidator implements ConstraintValidator 8) && (contactField.length() < 14); + return contactField != null && contactField.matches("[0-9]+") && (contactField.length() > 8) && (contactField.length() < 14); } } diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/UserController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/UserController.java index fda159f204..e72ec06830 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/web/controller/UserController.java +++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/UserController.java @@ -3,15 +3,16 @@ package com.baeldung.web.controller; import com.baeldung.model.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/") public class UserController { - @RequestMapping(value = "/", method = RequestMethod.GET) + @GetMapping("/") public String showForm(final Model model) { final User user = new User(); user.setFirstname("John"); @@ -21,7 +22,7 @@ public class UserController { return "index"; } - @RequestMapping(value = "/processForm", method = RequestMethod.POST) + @PostMapping("/processForm") public String processForm(@ModelAttribute(value = "user") final User user, final Model model) { // Insert User into DB model.addAttribute("name", user.getFirstname() + " " + user.getLastname()); diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java index 54b0e19e60..8c6cfcd3be 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java +++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/ValidatedPhoneController.java @@ -1,27 +1,26 @@ package com.baeldung.web.controller; -import javax.validation.Valid; - +import com.baeldung.model.ValidatedPhone; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import com.baeldung.model.ValidatedPhone; +import javax.validation.Valid; @Controller @EnableWebMvc public class ValidatedPhoneController { - @RequestMapping(value = "/validatePhone", method = RequestMethod.GET) + @GetMapping("/validatePhone") public String loadFormPage(Model m) { m.addAttribute("validatedPhone", new ValidatedPhone()); return "phoneHome"; } - @RequestMapping(value = "/addValidatePhone", method = RequestMethod.POST) + @PostMapping("/addValidatePhone") public String submitForm(@Valid ValidatedPhone validatedPhone, BindingResult result, Model m) { if (result.hasErrors()) { return "phoneHome"; From e62f8fa3d8ce4e8671c59065401cccc58384f016 Mon Sep 17 00:00:00 2001 From: buddhini81 Date: Sun, 26 Mar 2017 00:20:46 +0530 Subject: [PATCH 081/149] Add javaEE annotation sample project (#1481) * Delete AccountServlet.java * Delete BankAppServletContextListener.java * Delete LogInFilter.java * Delete UploadCustomerDocumentsServlet.java * Delete index.jsp * Delete login.jsp * Delete upload.jsp * Delete web.xml * Create javaeeannotations * Delete javaeeannotations * commit javaEE annotations project --- .../JavaEEAnnotationsSample/pom.xml | 57 ++++++++++++++++++ .../javaeeannotations}/AccountServlet.java | 13 ++--- .../BankAppServletContextListener.java | 34 +++++------ .../DepositRequestListener.java | 20 +++++++ .../javaeeannotations}/LogInFilter.java | 8 +-- .../UploadCustomerDocumentsServlet.java | 58 +++++++++---------- .../src/main}/webapp/index.jsp | 30 +++++----- .../src/main}/webapp/login.jsp | 22 +++---- .../src/main}/webapp/upload.jsp | 30 +++++----- .../src/main/webapp}/web.xml | 20 +++---- 10 files changed, 183 insertions(+), 109 deletions(-) create mode 100644 jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/pom.xml rename jee7/src/main/java/com/baeldung/javaeeannotations/{ => JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations}/AccountServlet.java (85%) rename jee7/src/main/java/com/baeldung/javaeeannotations/{ => JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations}/BankAppServletContextListener.java (96%) create mode 100644 jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java rename jee7/src/main/java/com/baeldung/javaeeannotations/{ => JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations}/LogInFilter.java (96%) rename jee7/src/main/java/com/baeldung/javaeeannotations/{ => JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations}/UploadCustomerDocumentsServlet.java (96%) rename jee7/src/main/{ => java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main}/webapp/index.jsp (78%) rename jee7/src/main/{ => java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main}/webapp/login.jsp (95%) rename jee7/src/main/{ => java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main}/webapp/upload.jsp (95%) rename jee7/src/main/{webapp/WEB-INF => java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp}/web.xml (95%) diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/pom.xml b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/pom.xml new file mode 100644 index 0000000000..b4bb243559 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + com.baeldung.javaeeannotations + JavaEEAnnotationsSample + 0.0.1-SNAPSHOT + war + JavaEEAnnotationsSample + JavaEEAnnotationsSample + + + + + javax.annotation + javax.annotation-api + 1.3 + + + + javax.servlet + javax.servlet-api + 3.1.0 + + + + javax.servlet.jsp + jsp-api + 2.1 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + src/main/webapp + SpringFieldConstructorInjection + false + + + + + JavaEEAnnotationsSample + + \ No newline at end of file diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java similarity index 85% rename from jee7/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java index e3f1667595..e24eb307bb 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java @@ -5,9 +5,6 @@ import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; -import javax.servlet.annotation.HttpConstraint; -import javax.servlet.annotation.HttpMethodConstraint; -import javax.servlet.annotation.ServletSecurity; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; @@ -19,20 +16,20 @@ import javax.servlet.http.HttpServletResponse; urlPatterns = {"/account", "/bankAccount" }, initParams = { @WebInitParam(name = "type", value = "savings") } ) -@ServletSecurity( +/*@ServletSecurity( value = @HttpConstraint(rolesAllowed = {"admin"}), httpMethodConstraints = {@HttpMethodConstraint(value = "POST", rolesAllowed = {"admin"})} - ) + )*/ public class AccountServlet extends javax.servlet.http.HttpServlet { String accountType = null; - @Override public void init(ServletConfig config) throws ServletException { accountType = config.getInitParameter("type"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + PrintWriter writer = response.getWriter(); writer.println("Hello, I am an AccountServlet!"); writer.flush(); @@ -49,8 +46,8 @@ public class AccountServlet extends javax.servlet.http.HttpServlet { PrintWriter writer = response.getWriter(); writer.println(" Balance of " + accountType + " account is: " + - accountBalance + "
This account bares an interest rate of " + interestRate + - " % "); + accountBalance + "
This account bares an interest rate of " + interestRate + + " % "); writer.flush(); } diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java similarity index 96% rename from jee7/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java index 6b43dd8a84..ee1b624cd1 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/BankAppServletContextListener.java @@ -1,17 +1,17 @@ -package com.baeldung.javaeeannotations; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.annotation.WebListener; - -@WebListener -public class BankAppServletContextListener implements ServletContextListener { - - public void contextInitialized(ServletContextEvent sce) { - sce.getServletContext().setAttribute("ATTR_DEFAULT_LANGUAGE", "english"); - } - - public void contextDestroyed(ServletContextEvent sce) { - System.out.println("CONTEXT DESTROYED"); - } -} +package com.baeldung.javaeeannotations; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +public class BankAppServletContextListener implements ServletContextListener { + + public void contextInitialized(ServletContextEvent sce) { + sce.getServletContext().setAttribute("ATTR_DEFAULT_LANGUAGE", "english"); + } + + public void contextDestroyed(ServletContextEvent sce) { + System.out.println("CONTEXT DESTROYED"); + } +} diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java new file mode 100644 index 0000000000..f361c84b97 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java @@ -0,0 +1,20 @@ +package com.baeldung.javaeeannotations; + +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpServletRequest; + +@WebListener +public class DepositRequestListener implements ServletRequestListener { + + public void requestDestroyed(ServletRequestEvent event) { + + } + + public void requestInitialized(ServletRequestEvent evet) { + HttpServletRequest req = (HttpServletRequest)evet.getServletRequest(); + req.setAttribute("interest", new Double(10)); + } + +} diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java similarity index 96% rename from jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java index 4e4aef2672..5ee420f6a2 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/LogInFilter.java @@ -11,13 +11,13 @@ import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -@WebFilter( +/*@WebFilter( urlPatterns = "/bankAccount/*", filterName = "LogInFilter", description = "Filter all account transaction URLs" - ) + )*/ public class LogInFilter implements javax.servlet.Filter { - @Override + public void init(FilterConfig filterConfig) throws ServletException { } @@ -29,8 +29,8 @@ public class LogInFilter implements javax.servlet.Filter { chain.doFilter(request, response); } - @Override public void destroy() { + } } diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java similarity index 96% rename from jee7/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java index 8a6c709b81..3a139ad7cc 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java @@ -1,29 +1,29 @@ -package com.baeldung.javaeeannotations; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.annotation.MultipartConfig; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Part; - -@WebServlet(urlPatterns = { "/uploadCustDocs" }) -@MultipartConfig( - fileSizeThreshold = 1024 * 1024 * 20, - maxFileSize = 1024 * 1024 * 20, - maxRequestSize = 1024 * 1024 * 25, - location = "D:/custDocs" - ) -public class UploadCustomerDocumentsServlet extends HttpServlet { - - protected void doPost( - HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - for (Part part : request.getParts()) { - part.write("myFile"); - } - } - -} +package com.baeldung.javaeeannotations; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; + +@WebServlet(urlPatterns = { "/uploadCustDocs" }) +@MultipartConfig( + fileSizeThreshold = 1024 * 1024 * 20, + maxFileSize = 1024 * 1024 * 20, + maxRequestSize = 1024 * 1024 * 25, + location = "D:/custDocs" + ) +public class UploadCustomerDocumentsServlet extends HttpServlet { + + protected void doPost( + HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + for (Part part : request.getParts()) { + part.write("myFile"); + } + } + +} diff --git a/jee7/src/main/webapp/index.jsp b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/index.jsp similarity index 78% rename from jee7/src/main/webapp/index.jsp rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/index.jsp index 0c389ef5b1..c49dec859e 100644 --- a/jee7/src/main/webapp/index.jsp +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/index.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" - pageEncoding="ISO-8859-1"%> - - - - -My Account - - -
- Width: -    - -
- +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + + + + +My Account + + +
+ Amount: +    + +
+ \ No newline at end of file diff --git a/jee7/src/main/webapp/login.jsp b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/login.jsp similarity index 95% rename from jee7/src/main/webapp/login.jsp rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/login.jsp index 885df0c3d9..6892cb0420 100644 --- a/jee7/src/main/webapp/login.jsp +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/login.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" - pageEncoding="ISO-8859-1"%> - - - - -Login - - -Login Here... - +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + + + + +Login + + +Login Here... + \ No newline at end of file diff --git a/jee7/src/main/webapp/upload.jsp b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/upload.jsp similarity index 95% rename from jee7/src/main/webapp/upload.jsp rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/upload.jsp index 020483b99f..3601322ef0 100644 --- a/jee7/src/main/webapp/upload.jsp +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/upload.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" - pageEncoding="ISO-8859-1"%> - - - - -Insert title here - - -
- -
- -
- +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" + pageEncoding="ISO-8859-1"%> + + + + +Insert title here + + +
+ +
+ +
+ \ No newline at end of file diff --git a/jee7/src/main/webapp/WEB-INF/web.xml b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/web.xml similarity index 95% rename from jee7/src/main/webapp/WEB-INF/web.xml rename to jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/web.xml index 0a3d84d2d4..f01eb341e4 100644 --- a/jee7/src/main/webapp/WEB-INF/web.xml +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/webapp/web.xml @@ -1,11 +1,11 @@ - - - - BASIC - default - - + + + + BASIC + default + + \ No newline at end of file From de29cd56327f0b532d587053f1c997e07d6c88c6 Mon Sep 17 00:00:00 2001 From: baljeet20 Date: Sun, 26 Mar 2017 01:53:04 +0530 Subject: [PATCH 082/149] BAEL-750 Added Java configuration (#1494) * BAEL-750 A guide to gemfire with spring data * BAEL-750 A guide to gemfire with spring data * BAEL-750 A guide to gemfire with spring data --- pom.xml | 3 +- spring-data-gemfire/pom.xml | 97 ++++++++++++++++++ .../gemfire/function/FunctionExecution.java | 16 +++ .../data/gemfire/function/FunctionImpl.java | 18 ++++ .../function/GemfireConfiguration.java | 65 ++++++++++++ .../spring/data/gemfire/model/Employee.java | 41 ++++++++ .../repository/EmployeeRepository.java | 17 ++++ .../main/resources/application-context.xml | 20 ++++ .../repository/EmployeeRepositoryTest.java | 98 +++++++++++++++++++ 9 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 spring-data-gemfire/pom.xml create mode 100644 spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionExecution.java create mode 100644 spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionImpl.java create mode 100644 spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/GemfireConfiguration.java create mode 100644 spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/model/Employee.java create mode 100644 spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepository.java create mode 100644 spring-data-gemfire/src/main/resources/application-context.xml create mode 100644 spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java diff --git a/pom.xml b/pom.xml index 5c85e94546..e0295e5c72 100644 --- a/pom.xml +++ b/pom.xml @@ -211,7 +211,8 @@ rabbitmq vertx - + spring-data-gemfire + diff --git a/spring-data-gemfire/pom.xml b/spring-data-gemfire/pom.xml new file mode 100644 index 0000000000..f387b04651 --- /dev/null +++ b/spring-data-gemfire/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + com.baeldung + spring-data-gemfire + 1.0.0-SNAPSHOT + jar + + 2.19.1 + 1.7.4.RELEASE + 7.0.1 + 1.0 + 4.12 + 1.3 + 4.3.0.RELEASE + 4.3.0.RELEASE + + + + + org.springframework.data + spring-data-gemfire + ${spring-data-gemfire-version} + + + + com.gemstone.gemfire + gemfire + ${gemfire-version} + + + + com.google.collections + google-collections + ${google-collections-version} + + + + junit + junit + ${junit-version} + test + + + org.hamcrest + hamcrest-core + + + + + org.hamcrest + hamcrest-library + ${hamcrest-library-version} + test + + + org.springframework + spring-context + ${spring-context-version} + + + org.springframework + spring-test + ${spring-test-version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot + + true + + + + gemstone + http://dist.gemstone.com.s3.amazonaws.com/maven/release/ + + + + \ No newline at end of file diff --git a/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionExecution.java b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionExecution.java new file mode 100644 index 0000000000..a3ddf32414 --- /dev/null +++ b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionExecution.java @@ -0,0 +1,16 @@ +package com.baeldung.spring.data.gemfire.function; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.gemfire.function.annotation.FunctionId; +import org.springframework.data.gemfire.function.annotation.OnRegion; +import org.springframework.data.gemfire.function.annotation.RegionData; + +import java.util.Map; +import java.util.Set; + +@OnRegion(region="employee") +public interface FunctionExecution { + @FunctionId("greeting") + public void execute(String message); + +} diff --git a/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionImpl.java b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionImpl.java new file mode 100644 index 0000000000..9fc6a2155d --- /dev/null +++ b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/FunctionImpl.java @@ -0,0 +1,18 @@ +package com.baeldung.spring.data.gemfire.function; + +import org.springframework.data.gemfire.function.annotation.GemfireFunction; +import org.springframework.stereotype.Component; + +@Component +public class FunctionImpl { + + @GemfireFunction + public void greeting(String message){ + System.out.println("Message "+message); + } + + @GemfireFunction + public String sayHello(String message){ + return "Hello "+message; + } +} diff --git a/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/GemfireConfiguration.java b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/GemfireConfiguration.java new file mode 100644 index 0000000000..0049f82ddc --- /dev/null +++ b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/function/GemfireConfiguration.java @@ -0,0 +1,65 @@ +package com.baeldung.spring.data.gemfire.function; + +import com.baeldung.spring.data.gemfire.model.Employee; +import com.baeldung.spring.data.gemfire.repository.EmployeeRepository; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.GemFireCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.function.config.EnableGemfireFunctionExecutions; +import org.springframework.data.gemfire.function.config.EnableGemfireFunctions; +import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories; +import java.util.Properties; + +@Configuration +@ComponentScan +@EnableGemfireRepositories(basePackages = "com.baeldung.spring.data.gemfire.repository") +@EnableGemfireFunctions +@EnableGemfireFunctionExecutions(basePackages = "com.baeldung.spring.data.gemfire.function") +public class GemfireConfiguration { + + @Autowired + EmployeeRepository employeeRepository; + + @Autowired + FunctionExecution functionExecution; + + + @Bean + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + gemfireProperties.setProperty("name", "SpringDataGemFireApplication"); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", "config"); + return gemfireProperties; + } + + @Bean + @Autowired + CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + gemfireCache.setClose(true); + gemfireCache.setProperties(gemfireProperties()); + return gemfireCache; + } + + + @Bean(name="employee") + @Autowired + LocalRegionFactoryBean getEmployee(final GemFireCache cache) { + LocalRegionFactoryBean employeeRegion = new LocalRegionFactoryBean(); + employeeRegion.setCache(cache); + employeeRegion.setClose(false); + employeeRegion.setName("employee"); + employeeRegion.setPersistent(false); + employeeRegion.setDataPolicy(DataPolicy.PRELOADED); + return employeeRegion; + } + + +} + diff --git a/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/model/Employee.java b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/model/Employee.java new file mode 100644 index 0000000000..9c29b28dca --- /dev/null +++ b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/model/Employee.java @@ -0,0 +1,41 @@ +package com.baeldung.spring.data.gemfire.model; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.gemfire.mapping.Region; + + +@Region("employee") +public class Employee { + + @Id + public String name; + public double salary; + + @PersistenceConstructor + public Employee(String name, double salary) { + this.name = name; + this.salary = salary; + } + + @Override + public String toString() { + return name + " is " + salary + " years old."; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getSalary() { + return salary; + } + + public void setSalary(int salary) { + this.salary = salary; + } +} \ No newline at end of file diff --git a/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepository.java b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepository.java new file mode 100644 index 0000000000..30799a2b99 --- /dev/null +++ b/spring-data-gemfire/src/main/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepository.java @@ -0,0 +1,17 @@ +package com.baeldung.spring.data.gemfire.repository; + +import com.baeldung.spring.data.gemfire.model.Employee; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EmployeeRepository extends CrudRepository { + + Employee findByName(String name); + + Iterable findBySalaryGreaterThan(double salary); + + Iterable findBySalaryLessThan(double salary); + + Iterable findBySalaryGreaterThanAndSalaryLessThan(double salary1, double salary2); +} diff --git a/spring-data-gemfire/src/main/resources/application-context.xml b/spring-data-gemfire/src/main/resources/application-context.xml new file mode 100644 index 0000000000..e618719b30 --- /dev/null +++ b/spring-data-gemfire/src/main/resources/application-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java b/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java new file mode 100644 index 0000000000..8714ad2d81 --- /dev/null +++ b/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java @@ -0,0 +1,98 @@ +package com.baeldung.spring.data.gemfire.repository; + +import com.baeldung.spring.data.gemfire.function.FunctionExecution; +import com.baeldung.spring.data.gemfire.function.GemfireConfiguration; +import com.baeldung.spring.data.gemfire.model.Employee; +import com.google.common.collect.Lists; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import java.util.List; + +import static junit.framework.Assert.assertEquals; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes=GemfireConfiguration.class, loader=AnnotationConfigContextLoader.class) +public class EmployeeRepositoryTest { + + @Autowired + EmployeeRepository employeeRepository; + + @Autowired + FunctionExecution execution; + + @Test + public void whenEmployeeIsSaved_ThenAbleToRead(){ + Employee employee=new Employee("John Davidson",4550.00); + employeeRepository.save(employee); + + List employees= Lists.newArrayList(employeeRepository.findAll()); + + assertEquals(1, employees.size()); + } + + @Test + public void whenSalaryGreaterThan_ThenEmployeeFound(){ + + Employee employee=new Employee("John Davidson",4550.00); + Employee employee1=new Employee("Adam Davidson",3500.00); + Employee employee2=new Employee("Chris Davidson",5600.00); + + employeeRepository.save(employee); + employeeRepository.save(employee1); + employeeRepository.save(employee2); + + List employees= Lists.newArrayList(employeeRepository.findBySalaryGreaterThan(4000.00)); + + assertEquals(2,employees.size()); + + } + + @Test + public void whenSalaryLessThan_ThenEmployeeFound(){ + + Employee employee=new Employee("John Davidson",4550.00); + Employee employee1=new Employee("Adam Davidson",3500.00); + Employee employee2=new Employee("Chris Davidson",5600.00); + + employeeRepository.save(employee); + employeeRepository.save(employee1); + employeeRepository.save(employee2); + + List employees= Lists.newArrayList(employeeRepository.findBySalaryLessThan(4000)); + + assertEquals(1,employees.size()); + + } + @Test + public void whenSalaryBetween_ThenEmployeeFound(){ + + Employee employee=new Employee("John Davidson",4550.00); + Employee employee1=new Employee("Adam Davidson",3500.00); + Employee employee2=new Employee("Chris Davidson",5600.00); + + employeeRepository.save(employee); + employeeRepository.save(employee1); + employeeRepository.save(employee2); + + List employees= Lists.newArrayList(employeeRepository.findBySalaryGreaterThanAndSalaryLessThan(3500,5000)); + + assertEquals(1,employees.size()); + + } + + @Test + public void whenFunctionExecutedFromClient_ThenFunctionExecutedOnServer(){ + execution.execute("Hello World"); + } + + @After + public void cleanup(){ + employeeRepository.deleteAll(); + } +} From 052c149b0edb156eba9244d708d5b3a958d15e4f Mon Sep 17 00:00:00 2001 From: lor6 Date: Sat, 25 Mar 2017 23:12:17 +0200 Subject: [PATCH 083/149] in memory test (#1476) * in memory test * update dependencies * rename db * remove create db * update version --- spring-jpa/pom.xml | 8 +-- .../org/baeldung/config/StudentJpaConfig.java | 68 +++++++++++++++++++ .../persistence/dao/StudentRepository.java | 8 +++ .../baeldung/persistence/model/Student.java | 38 +++++++++++ .../resources/persistence-student.properties | 11 +++ .../repository/InMemoryDBTest.java | 38 +++++++++++ .../resources/persistence-student.properties | 9 +++ 7 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 spring-jpa/src/main/java/org/baeldung/config/StudentJpaConfig.java create mode 100644 spring-jpa/src/main/java/org/baeldung/persistence/dao/StudentRepository.java create mode 100644 spring-jpa/src/main/java/org/baeldung/persistence/model/Student.java create mode 100644 spring-jpa/src/main/resources/persistence-student.properties create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/repository/InMemoryDBTest.java create mode 100644 spring-jpa/src/test/resources/persistence-student.properties diff --git a/spring-jpa/pom.xml b/spring-jpa/pom.xml index 2229d64abe..3ca373acea 100644 --- a/spring-jpa/pom.xml +++ b/spring-jpa/pom.xml @@ -196,14 +196,14 @@ - 4.3.4.RELEASE + 4.3.7.RELEASE 3.21.0-GA - 5.2.5.Final + 5.2.9.Final 5.1.40 - 1.10.5.RELEASE - 1.4.193 + 1.11.1.RELEASE + 1.4.194 1.2 diff --git a/spring-jpa/src/main/java/org/baeldung/config/StudentJpaConfig.java b/spring-jpa/src/main/java/org/baeldung/config/StudentJpaConfig.java new file mode 100644 index 0000000000..a40f180a62 --- /dev/null +++ b/spring-jpa/src/main/java/org/baeldung/config/StudentJpaConfig.java @@ -0,0 +1,68 @@ +package org.baeldung.config; + +import java.util.Properties; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao") +@PropertySource("persistence-student.properties") +@EnableTransactionManagement +public class StudentJpaConfig { + + @Autowired + private Environment env; + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); + dataSource.setUrl(env.getProperty("jdbc.url")); + dataSource.setUsername(env.getProperty("jdbc.user")); + dataSource.setPassword(env.getProperty("jdbc.pass")); + + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource()); + em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" }); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + em.setJpaProperties(additionalProperties()); + return em; + } + + @Bean + JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } + + final Properties additionalProperties() { + final Properties hibernateProperties = new Properties(); + + hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); + hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect")); + hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql")); + hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", env.getProperty("hibernate.cache.use_second_level_cache")); + hibernateProperties.setProperty("hibernate.cache.use_query_cache", env.getProperty("hibernate.cache.use_query_cache")); + + return hibernateProperties; + } +} diff --git a/spring-jpa/src/main/java/org/baeldung/persistence/dao/StudentRepository.java b/spring-jpa/src/main/java/org/baeldung/persistence/dao/StudentRepository.java new file mode 100644 index 0000000000..af484b442c --- /dev/null +++ b/spring-jpa/src/main/java/org/baeldung/persistence/dao/StudentRepository.java @@ -0,0 +1,8 @@ +package org.baeldung.persistence.dao; + +import org.springframework.data.jpa.repository.JpaRepository; + +import org.baeldung.persistence.model.Student; + +public interface StudentRepository extends JpaRepository { +} diff --git a/spring-jpa/src/main/java/org/baeldung/persistence/model/Student.java b/spring-jpa/src/main/java/org/baeldung/persistence/model/Student.java new file mode 100644 index 0000000000..437eeac5bb --- /dev/null +++ b/spring-jpa/src/main/java/org/baeldung/persistence/model/Student.java @@ -0,0 +1,38 @@ +package org.baeldung.persistence.model; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class Student { + + @Id + private long id; + private String name; + + public Student() { + } + + public Student(long id, String name) { + super(); + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-jpa/src/main/resources/persistence-student.properties b/spring-jpa/src/main/resources/persistence-student.properties new file mode 100644 index 0000000000..4e80b836d2 --- /dev/null +++ b/spring-jpa/src/main/resources/persistence-student.properties @@ -0,0 +1,11 @@ +jdbc.driverClassName=com.mysql.jdbc.Driver +jdbc.url=jdbc:mysql://localhost:3306/myDb +jdbc.user=tutorialuser +jdbc.pass=tutorialpass + +hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=create-drop + +hibernate.cache.use_second_level_cache=false +hibernate.cache.use_query_cache=false \ No newline at end of file diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/repository/InMemoryDBTest.java b/spring-jpa/src/test/java/org/baeldung/persistence/repository/InMemoryDBTest.java new file mode 100644 index 0000000000..2c40c5b117 --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/repository/InMemoryDBTest.java @@ -0,0 +1,38 @@ +package org.baeldung.persistence.repository; + +import javax.annotation.Resource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.transaction.annotation.Transactional; + +import org.baeldung.config.StudentJpaConfig; +import org.baeldung.persistence.model.Student; +import org.baeldung.persistence.dao.StudentRepository; + +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { StudentJpaConfig.class }, loader = AnnotationConfigContextLoader.class) +@Transactional +public class InMemoryDBTest { + + @Resource + private StudentRepository studentRepository; + + private static final long ID = 1; + private static final String NAME="john"; + + @Test + public void givenStudent_whenSave_thenGetOk(){ + Student student = new Student(ID, NAME); + studentRepository.save(student); + + Student student2 = studentRepository.findOne(ID); + assertEquals("name incorrect", NAME, student2.getName()); + } + +} diff --git a/spring-jpa/src/test/resources/persistence-student.properties b/spring-jpa/src/test/resources/persistence-student.properties new file mode 100644 index 0000000000..21dcd5b4a0 --- /dev/null +++ b/spring-jpa/src/test/resources/persistence-student.properties @@ -0,0 +1,9 @@ +jdbc.driverClassName=org.h2.Driver +jdbc.url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1 + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=create-drop + +hibernate.cache.use_second_level_cache=false +hibernate.cache.use_query_cache=false \ No newline at end of file From 52eb7c2bc42081b589b9a566f594fe3817acfaa5 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Sun, 26 Mar 2017 15:56:20 +0200 Subject: [PATCH 084/149] Bael 738 (#1504) * BAEL-724 code for put/patch article * BAEL-724 fix typo * BAEL-728 more generic patch approach * BAEL-738 change url of PUT method * fix route confict --- .../repository/HeavyResourceRepository.java | 8 ++++++++ .../web/controller/HeavyResourceController.java | 14 +++++++------- .../controller/HeavyResourceControllerTest.java | 6 +++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java index 6de9e75450..5556d85f65 100644 --- a/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java +++ b/spring-rest/src/main/java/org/baeldung/repository/HeavyResourceRepository.java @@ -2,6 +2,7 @@ package org.baeldung.repository; import org.baeldung.web.dto.HeavyResource; import org.baeldung.web.dto.HeavyResourceAddressOnly; +import org.baeldung.web.dto.HeavyResourceAddressPartialUpdate; import java.util.Map; @@ -17,4 +18,11 @@ public class HeavyResourceRepository { public void save(Map updates, String id) { } + + public void save(HeavyResource heavyResource, String id) { + + } + public void save(HeavyResourceAddressOnly partialUpdate, String id) { + + } } diff --git a/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java index f2c4ffaa51..55616a6446 100644 --- a/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java +++ b/spring-rest/src/main/java/org/baeldung/web/controller/HeavyResourceController.java @@ -15,19 +15,19 @@ public class HeavyResourceController { private HeavyResourceRepository heavyResourceRepository = new HeavyResourceRepository(); - @RequestMapping(value = "/heavy", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity saveResource(@RequestBody HeavyResource heavyResource) { - heavyResourceRepository.save(heavyResource); + @RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { + heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved"); } - @RequestMapping(value = "/heavy", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity partialUpdateName(@RequestBody HeavyResourceAddressOnly partialUpdate) { - heavyResourceRepository.save(partialUpdate); + @RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity partialUpdateName(@RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { + heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated"); } - @RequestMapping(value = "/heavy/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/heavyresource2/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity partialUpdateGeneric(@RequestBody Map updates, @PathVariable("id") String id) { heavyResourceRepository.save(updates, id); diff --git a/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java index e283c5f5f6..a1f9e71bec 100644 --- a/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java +++ b/spring-rest/src/test/java/org/baeldung/web/controller/HeavyResourceControllerTest.java @@ -42,7 +42,7 @@ public class HeavyResourceControllerTest { @Test public void givenHeavyResource_whenSendPutRequest_thenCreateResource() throws Exception { - mockMvc.perform(put("/heavy") + mockMvc.perform(put("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(new HeavyResource(1, "Tom", "Jackson", 12, "heaven street"))) ).andExpect(status().isOk()); @@ -50,7 +50,7 @@ public class HeavyResourceControllerTest { @Test public void givenNewAddressOfResource_whenExecutePatchRequest_thenUpdateResourcePartially() throws Exception { - mockMvc.perform(patch("/heavy") + mockMvc.perform(patch("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(new HeavyResourceAddressOnly(1, "5th avenue"))) ).andExpect(status().isOk()); @@ -61,7 +61,7 @@ public class HeavyResourceControllerTest { HashMap updates = new HashMap<>(); updates.put("address", "5th avenue"); - mockMvc.perform(patch("/heavy/1") + mockMvc.perform(patch("/heavyresource/1") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(updates)) ).andExpect(status().isOk()); From d3e57d38a620f76a4d288fa9ea0e95efe15fc09a Mon Sep 17 00:00:00 2001 From: Tian Baoqiang Date: Mon, 27 Mar 2017 00:23:28 +0800 Subject: [PATCH 085/149] #BAEL-636 (#1468) * jira #BAEL-636 * remove redundant dependency spring-boot-starter-reactive --- spring-5/pom.xml | 11 ++ .../java/com/baeldung/functional/Actor.java | 23 +++ .../com/baeldung/functional/FormHandler.java | 50 ++++++ .../functional/FunctionalWebApplication.java | 80 +++++++++ .../functional/IndexRewriteFilter.java | 29 ++++ spring-5/src/main/resources/files/hello.txt | 1 + ...nctionalWebApplicationIntegrationTest.java | 154 ++++++++++++++++++ .../src/test/resources/baeldung-weekly.png | Bin 0 -> 22275 bytes 8 files changed, 348 insertions(+) create mode 100644 spring-5/src/main/java/com/baeldung/functional/Actor.java create mode 100644 spring-5/src/main/java/com/baeldung/functional/FormHandler.java create mode 100644 spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java create mode 100644 spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java create mode 100644 spring-5/src/main/resources/files/hello.txt create mode 100644 spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java create mode 100644 spring-5/src/test/resources/baeldung-weekly.png diff --git a/spring-5/pom.xml b/spring-5/pom.xml index eca36cc1d1..59bead4b73 100644 --- a/spring-5/pom.xml +++ b/spring-5/pom.xml @@ -35,6 +35,17 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-webflux + + + + + io.projectreactor + reactor-core + 3.0.6.BUILD-SNAPSHOT + diff --git a/spring-5/src/main/java/com/baeldung/functional/Actor.java b/spring-5/src/main/java/com/baeldung/functional/Actor.java new file mode 100644 index 0000000000..23c88b89e1 --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/Actor.java @@ -0,0 +1,23 @@ +package com.baeldung.functional; + +class Actor { + private String firstname; + private String lastname; + + public Actor() { + } + + public Actor(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + public String getFirstname() { + return firstname; + } + + public String getLastname() { + return lastname; + } + +} diff --git a/spring-5/src/main/java/com/baeldung/functional/FormHandler.java b/spring-5/src/main/java/com/baeldung/functional/FormHandler.java new file mode 100644 index 0000000000..c44db76f56 --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/FormHandler.java @@ -0,0 +1,50 @@ +package com.baeldung.functional; + +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers; +import static org.springframework.web.reactive.function.BodyExtractors.toFormData; +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +public class FormHandler { + + Mono handleLogin(ServerRequest request) { + return request + .body(toFormData()) + .map(MultiValueMap::toSingleValueMap) + .map(formData -> { + System.out.println("form data: " + formData.toString()); + if ("baeldung".equals(formData.get("user")) && "you_know_what_to_do".equals(formData.get("token"))) { + return ok() + .body(Mono.just("welcome back!"), String.class) + .block(); + } + return ServerResponse + .badRequest() + .build() + .block(); + }); + } + + Mono handleUpload(ServerRequest request) { + return request + .body(toDataBuffers()) + .collectList() + .map(dataBuffers -> { + AtomicLong atomicLong = new AtomicLong(0); + dataBuffers.forEach(d -> atomicLong.addAndGet(d + .asByteBuffer() + .array().length)); + System.out.println("data length:" + atomicLong.get()); + return ok() + .body(fromObject(atomicLong.toString())) + .block(); + }); + } +} diff --git a/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java b/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java new file mode 100644 index 0000000000..573813b166 --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/FunctionalWebApplication.java @@ -0,0 +1,80 @@ +package com.baeldung.functional; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; +import org.springframework.boot.web.server.WebServer; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.WebHandler; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import reactor.core.publisher.Flux; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.server.RequestPredicates.*; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +public class FunctionalWebApplication { + + private static final Actor BRAD_PITT = new Actor("Brad", "Pitt"); + private static final Actor TOM_HANKS = new Actor("Tom", "Hanks"); + private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS)); + + private RouterFunction routingFunction() { + FormHandler formHandler = new FormHandler(); + + RouterFunction restfulRouter = route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest + .bodyToMono(Actor.class) + .doOnNext(actors::add) + .then(ok().build())); + + return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))) + .andRoute(POST("/login"), formHandler::handleLogin) + .andRoute(POST("/upload"), formHandler::handleUpload) + .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/"))) + .andNest(path("/actor"), restfulRouter) + .filter((request, next) -> { + System.out.println("Before handler invocation: " + request.path()); + return next.handle(request); + }); + } + + WebServer start() throws Exception { + WebHandler webHandler = toHttpHandler(routingFunction()); + HttpHandler httpHandler = WebHttpHandlerBuilder + .webHandler(webHandler) + .prependFilter(new IndexRewriteFilter()) + .build(); + + Tomcat tomcat = new Tomcat(); + tomcat.setHostname("localhost"); + tomcat.setPort(9090); + Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir")); + ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler); + Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet); + rootContext.addServletMappingDecoded("/", "httpHandlerServlet"); + + TomcatWebServer server = new TomcatWebServer(tomcat); + server.start(); + return server; + + } + + public static void main(String[] args) { + try { + new FunctionalWebApplication().start(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java b/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java new file mode 100644 index 0000000000..3e19f81943 --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/IndexRewriteFilter.java @@ -0,0 +1,29 @@ +package com.baeldung.functional; + +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +class IndexRewriteFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { + ServerHttpRequest request = serverWebExchange.getRequest(); + if (request + .getURI() + .getPath() + .equals("/")) { + return webFilterChain.filter(serverWebExchange + .mutate() + .request(builder -> builder + .method(request.getMethod()) + .contextPath(request.getContextPath()) + .path("/test")) + .build()); + } + return webFilterChain.filter(serverWebExchange); + } + +} diff --git a/spring-5/src/main/resources/files/hello.txt b/spring-5/src/main/resources/files/hello.txt new file mode 100644 index 0000000000..b6fc4c620b --- /dev/null +++ b/spring-5/src/main/resources/files/hello.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java b/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java new file mode 100644 index 0000000000..bf28ed1e7d --- /dev/null +++ b/spring-5/src/test/java/com/baeldung/functional/FunctionalWebApplicationIntegrationTest.java @@ -0,0 +1,154 @@ +package com.baeldung.functional; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.web.server.WebServer; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.BodyInserters.fromResource; + +public class FunctionalWebApplicationIntegrationTest { + + private static WebTestClient client; + private static WebServer server; + + @BeforeClass + public static void setup() throws Exception { + server = new FunctionalWebApplication().start(); + client = WebTestClient + .bindToServer() + .baseUrl("http://localhost:" + server.getPort()) + .build(); + } + + @AfterClass + public static void destroy() { + server.stop(); + } + + @Test + public void givenRouter_whenGetTest_thenGotHelloWorld() throws Exception { + client + .get() + .uri("/test") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .value() + .isEqualTo("helloworld"); + } + + @Test + public void givenIndexFilter_whenRequestRoot_thenRewrittenToTest() throws Exception { + client + .get() + .uri("/") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .value() + .isEqualTo("helloworld"); + } + + @Test + public void givenLoginForm_whenPostValidToken_thenSuccess() throws Exception { + MultiValueMap formData = new LinkedMultiValueMap<>(1); + formData.add("user", "baeldung"); + formData.add("token", "you_know_what_to_do"); + + client + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .exchange(BodyInserters.fromFormData(formData)) + .expectStatus() + .isOk() + .expectBody(String.class) + .value() + .isEqualTo("welcome back!"); + } + + @Test + public void givenLoginForm_whenRequestWithInvalidToken_thenFail() throws Exception { + MultiValueMap formData = new LinkedMultiValueMap<>(2); + formData.add("user", "baeldung"); + formData.add("token", "try_again"); + + client + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .exchange(BodyInserters.fromFormData(formData)) + .expectStatus() + .isBadRequest(); + } + + @Test + public void givenUploadForm_whenRequestWithMultipartData_thenSuccess() throws Exception { + Resource resource = new ClassPathResource("/baeldung-weekly.png"); + client + .post() + .uri("/upload") + .contentType(MediaType.MULTIPART_FORM_DATA) + .exchange(fromResource(resource)) + .expectStatus() + .isOk() + .expectBody(String.class) + .value() + .isEqualTo(String.valueOf(resource.contentLength())); + } + + @Test + public void givenActors_whenAddActor_thenAdded() throws Exception { + client + .get() + .uri("/actor") + .exchange() + .expectStatus() + .isOk() + .expectBody(Actor.class) + .list() + .hasSize(2); + + client + .post() + .uri("/actor") + .exchange(fromObject(new Actor("Clint", "Eastwood"))) + .expectStatus() + .isOk(); + + client + .get() + .uri("/actor") + .exchange() + .expectStatus() + .isOk() + .expectBody(Actor.class) + .list() + .hasSize(3); + } + + @Test + public void givenResources_whenAccess_thenGot() throws Exception { + client + .get() + .uri("/files/hello.txt") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .value() + .isEqualTo("hello"); + } + +} diff --git a/spring-5/src/test/resources/baeldung-weekly.png b/spring-5/src/test/resources/baeldung-weekly.png new file mode 100644 index 0000000000000000000000000000000000000000..5a27d61dae718016a947083e01411ed1bc14f90e GIT binary patch literal 22275 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 Date: Sun, 26 Mar 2017 19:38:09 +0200 Subject: [PATCH 086/149] Update pom.xml (#1507) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0295e5c72..0939ea91b8 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ javaxval jaxb jee7 - jhipster + jjwt jooq jpa-storedprocedure From 6322aa350f1c1a35d751463bd2bb57dbc03bf65e Mon Sep 17 00:00:00 2001 From: lor6 Date: Sun, 26 Mar 2017 21:19:07 +0300 Subject: [PATCH 087/149] Bael 737 v2 (#1509) * in memory test * update dependencies * rename db * remove create db * update version * update properties file --- spring-jpa/src/test/resources/persistence-student.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-jpa/src/test/resources/persistence-student.properties b/spring-jpa/src/test/resources/persistence-student.properties index 21dcd5b4a0..3b6b580630 100644 --- a/spring-jpa/src/test/resources/persistence-student.properties +++ b/spring-jpa/src/test/resources/persistence-student.properties @@ -3,7 +3,7 @@ jdbc.url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1 hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.show_sql=true -hibernate.hbm2ddl.auto=create-drop +hibernate.hbm2ddl.auto=create hibernate.cache.use_second_level_cache=false hibernate.cache.use_query_cache=false \ No newline at end of file From e6836c01c7b6fb0ab058e93f9ef46aa6540a9c6d Mon Sep 17 00:00:00 2001 From: Mohamed Sanaulla Date: Sun, 26 Mar 2017 23:57:36 +0300 Subject: [PATCH 088/149] sample for BAEL-747 Check if a number is prime in Java (#1508) --- .../primechecker/BigIntegerPrimeChecker.java | 13 ++++++++++ .../primechecker/BruteForcePrimeChecker.java | 13 ++++++++++ .../primechecker/OptimisedPrimeChecker.java | 15 ++++++++++++ .../baeldung/primechecker/PrimeChecker.java | 6 +++++ .../primechecker/PrimesPrimeChecker.java | 12 ++++++++++ .../BigIntegerPrimeCheckerTest.java | 23 ++++++++++++++++++ .../BruteForcePrimeCheckerTest.java | 24 +++++++++++++++++++ .../OptimisedPrimeCheckerTest.java | 23 ++++++++++++++++++ .../primechecker/PrimesPrimeCheckerTest.java | 22 +++++++++++++++++ 9 files changed, 151 insertions(+) create mode 100644 core-java/src/main/java/com/baeldung/primechecker/BigIntegerPrimeChecker.java create mode 100644 core-java/src/main/java/com/baeldung/primechecker/BruteForcePrimeChecker.java create mode 100644 core-java/src/main/java/com/baeldung/primechecker/OptimisedPrimeChecker.java create mode 100644 core-java/src/main/java/com/baeldung/primechecker/PrimeChecker.java create mode 100644 core-java/src/main/java/com/baeldung/primechecker/PrimesPrimeChecker.java create mode 100644 core-java/src/test/java/com/baeldung/primechecker/BigIntegerPrimeCheckerTest.java create mode 100644 core-java/src/test/java/com/baeldung/primechecker/BruteForcePrimeCheckerTest.java create mode 100644 core-java/src/test/java/com/baeldung/primechecker/OptimisedPrimeCheckerTest.java create mode 100644 core-java/src/test/java/com/baeldung/primechecker/PrimesPrimeCheckerTest.java diff --git a/core-java/src/main/java/com/baeldung/primechecker/BigIntegerPrimeChecker.java b/core-java/src/main/java/com/baeldung/primechecker/BigIntegerPrimeChecker.java new file mode 100644 index 0000000000..1ac4fed63f --- /dev/null +++ b/core-java/src/main/java/com/baeldung/primechecker/BigIntegerPrimeChecker.java @@ -0,0 +1,13 @@ +package com.baeldung.primechecker; + +import java.math.BigInteger; + +public class BigIntegerPrimeChecker implements PrimeChecker{ + + @Override + public boolean isPrime(int number) { + BigInteger bigInt = BigInteger.valueOf(number); + return bigInt.isProbablePrime(100); + } + +} diff --git a/core-java/src/main/java/com/baeldung/primechecker/BruteForcePrimeChecker.java b/core-java/src/main/java/com/baeldung/primechecker/BruteForcePrimeChecker.java new file mode 100644 index 0000000000..7a94479b8f --- /dev/null +++ b/core-java/src/main/java/com/baeldung/primechecker/BruteForcePrimeChecker.java @@ -0,0 +1,13 @@ +package com.baeldung.primechecker; + +import java.util.stream.IntStream; + +public class BruteForcePrimeChecker implements PrimeChecker{ + + @Override + public boolean isPrime(int number) { + return IntStream.range(2, number).filter(n -> (number % n == 0)).count() == 0; + } + + +} diff --git a/core-java/src/main/java/com/baeldung/primechecker/OptimisedPrimeChecker.java b/core-java/src/main/java/com/baeldung/primechecker/OptimisedPrimeChecker.java new file mode 100644 index 0000000000..40669f4181 --- /dev/null +++ b/core-java/src/main/java/com/baeldung/primechecker/OptimisedPrimeChecker.java @@ -0,0 +1,15 @@ +package com.baeldung.primechecker; + +import java.util.stream.IntStream; + +public class OptimisedPrimeChecker implements PrimeChecker{ + + @Override + public boolean isPrime(int number) { + return IntStream.range(2, (int)Math.sqrt(number) + 1) + .filter(n -> (number % n == 0)) + .count() == 0; + } + + +} diff --git a/core-java/src/main/java/com/baeldung/primechecker/PrimeChecker.java b/core-java/src/main/java/com/baeldung/primechecker/PrimeChecker.java new file mode 100644 index 0000000000..22260268bc --- /dev/null +++ b/core-java/src/main/java/com/baeldung/primechecker/PrimeChecker.java @@ -0,0 +1,6 @@ +package com.baeldung.primechecker; + +public interface PrimeChecker { + + public boolean isPrime( int number ); +} diff --git a/core-java/src/main/java/com/baeldung/primechecker/PrimesPrimeChecker.java b/core-java/src/main/java/com/baeldung/primechecker/PrimesPrimeChecker.java new file mode 100644 index 0000000000..0c6a636612 --- /dev/null +++ b/core-java/src/main/java/com/baeldung/primechecker/PrimesPrimeChecker.java @@ -0,0 +1,12 @@ +package com.baeldung.primechecker; + +import org.apache.commons.math3.primes.Primes; + +public class PrimesPrimeChecker implements PrimeChecker{ + + @Override + public boolean isPrime(int number) { + return Primes.isPrime(number); + } + +} diff --git a/core-java/src/test/java/com/baeldung/primechecker/BigIntegerPrimeCheckerTest.java b/core-java/src/test/java/com/baeldung/primechecker/BigIntegerPrimeCheckerTest.java new file mode 100644 index 0000000000..6a5228cc50 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/primechecker/BigIntegerPrimeCheckerTest.java @@ -0,0 +1,23 @@ +package com.baeldung.primechecker; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class BigIntegerPrimeCheckerTest { + + PrimeChecker primeChecker = new BigIntegerPrimeChecker(); + + @Test + public void givenPrimeNumber_whenCheckIsPrime_thenTrue(){ + assertTrue(primeChecker.isPrime(13)); + assertTrue(primeChecker.isPrime(1009)); + } + + @Test + public void givenNonPrimeNumber_whenCheckIsPrime_thenFalse(){ + assertTrue(!primeChecker.isPrime(50)); + assertTrue(!primeChecker.isPrime(1001)); + } + +} diff --git a/core-java/src/test/java/com/baeldung/primechecker/BruteForcePrimeCheckerTest.java b/core-java/src/test/java/com/baeldung/primechecker/BruteForcePrimeCheckerTest.java new file mode 100644 index 0000000000..7139373f5e --- /dev/null +++ b/core-java/src/test/java/com/baeldung/primechecker/BruteForcePrimeCheckerTest.java @@ -0,0 +1,24 @@ +package com.baeldung.primechecker; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class BruteForcePrimeCheckerTest { + + BruteForcePrimeChecker primeChecker = new BruteForcePrimeChecker(); + + @Test + public void givenPrimeNumber_whenCheckIsPrime_thenTrue(){ + assertTrue(primeChecker.isPrime(13)); + assertTrue(primeChecker.isPrime(1009)); + } + + @Test + public void givenNonPrimeNumber_whenCheckIsPrime_thenFalse(){ + assertTrue(!primeChecker.isPrime(50)); + assertTrue(!primeChecker.isPrime(1001)); + } + + + +} diff --git a/core-java/src/test/java/com/baeldung/primechecker/OptimisedPrimeCheckerTest.java b/core-java/src/test/java/com/baeldung/primechecker/OptimisedPrimeCheckerTest.java new file mode 100644 index 0000000000..bb4c06a53a --- /dev/null +++ b/core-java/src/test/java/com/baeldung/primechecker/OptimisedPrimeCheckerTest.java @@ -0,0 +1,23 @@ +package com.baeldung.primechecker; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class OptimisedPrimeCheckerTest { + + PrimeChecker primeChecker = new OptimisedPrimeChecker(); + + @Test + public void givenPrimeNumber_whenCheckIsPrime_thenTrue(){ + assertTrue(primeChecker.isPrime(13)); + assertTrue(primeChecker.isPrime(1009)); + } + + @Test + public void givenNonPrimeNumber_whenCheckIsPrime_thenFalse(){ + assertTrue(!primeChecker.isPrime(50)); + assertTrue(!primeChecker.isPrime(1001)); + } + +} diff --git a/core-java/src/test/java/com/baeldung/primechecker/PrimesPrimeCheckerTest.java b/core-java/src/test/java/com/baeldung/primechecker/PrimesPrimeCheckerTest.java new file mode 100644 index 0000000000..f8b194e855 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/primechecker/PrimesPrimeCheckerTest.java @@ -0,0 +1,22 @@ +package com.baeldung.primechecker; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class PrimesPrimeCheckerTest { + PrimeChecker primeChecker = new PrimesPrimeChecker(); + + @Test + public void givenPrimeNumber_whenCheckIsPrime_thenTrue() { + assertTrue(primeChecker.isPrime(13)); + assertTrue(primeChecker.isPrime(1009)); + } + + @Test + public void givenNonPrimeNumber_whenCheckIsPrime_thenFalse() { + assertTrue(!primeChecker.isPrime(50)); + assertTrue(!primeChecker.isPrime(1001)); + } + +} From 427077ebfabc170f31e771c5486cc7a0cb25737b Mon Sep 17 00:00:00 2001 From: Nancy Bosecker Date: Mon, 27 Mar 2017 05:46:58 -0700 Subject: [PATCH 089/149] Jackson Map Serialize/Deserialize (#1511) * Solr w Apache SolrJ * Solr w Apache SolrJ * updated test names and moved add to @before method * create apache-solrj module, moved code from spring-data-solr * More examples for indexing,delete,and query for solrj * More examples for indexing,delete,and query for solrj * Jackson Map Serialize/Deserialize * Jackson Map Serialize/Deserialize --- .../com/baeldung/jackson/entities/MyPair.java | 80 +++++++++++++++++++ .../serialization/MyPairSerializer.java | 25 ++++++ .../JacksonMapDeserializeTest.java | 63 +++++++++++++++ .../JacksonMapSerializeTest.java | 71 ++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 jackson/src/main/java/com/baeldung/jackson/entities/MyPair.java create mode 100644 jackson/src/main/java/com/baeldung/jackson/serialization/MyPairSerializer.java create mode 100644 jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java create mode 100644 jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java diff --git a/jackson/src/main/java/com/baeldung/jackson/entities/MyPair.java b/jackson/src/main/java/com/baeldung/jackson/entities/MyPair.java new file mode 100644 index 0000000000..ebe41890fe --- /dev/null +++ b/jackson/src/main/java/com/baeldung/jackson/entities/MyPair.java @@ -0,0 +1,80 @@ +package com.baeldung.jackson.entities; + +import com.fasterxml.jackson.annotation.JsonValue; + +public class MyPair { + + private String first; + private String second; + + public MyPair(String first, String second) { + this.first = first; + this.second = second; + } + + public MyPair(String both) { + String[] pairs = both.split("and"); + this.first = pairs[0].trim(); + this.second = pairs[1].trim(); + } + + @Override + @JsonValue + public String toString() { + return first + " and " + second; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((first == null) ? 0 : first.hashCode()); + result = prime * result + ((second == null) ? 0 : second.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof MyPair)) { + return false; + } + MyPair other = (MyPair) obj; + if (first == null) { + if (other.first != null) { + return false; + } + } else if (!first.equals(other.first)) { + return false; + } + if (second == null) { + if (other.second != null) { + return false; + } + } else if (!second.equals(other.second)) { + return false; + } + return true; + } + + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + public String getSecond() { + return second; + } + + public void setSecond(String second) { + this.second = second; + } +} \ No newline at end of file diff --git a/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairSerializer.java b/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairSerializer.java new file mode 100644 index 0000000000..68afb6c193 --- /dev/null +++ b/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairSerializer.java @@ -0,0 +1,25 @@ +package com.baeldung.jackson.serialization; + +import java.io.IOException; +import java.io.StringWriter; + +import com.baeldung.jackson.entities.MyPair; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class MyPairSerializer extends JsonSerializer { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public void serialize(MyPair value, JsonGenerator gen, + SerializerProvider serializers) throws IOException, + JsonProcessingException { + StringWriter writer = new StringWriter(); + mapper.writeValue(writer, value); + gen.writeFieldName(writer.toString()); + } +} \ No newline at end of file diff --git a/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java b/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java new file mode 100644 index 0000000000..3be3981c9b --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java @@ -0,0 +1,63 @@ +package com.baeldung.jackson.deserialization; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.baeldung.jackson.entities.MyPair; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JacksonMapDeserializeTest { + + private Map map; + private Map cmap; + + @Test + public void whenSimpleMapDeserialize_thenCorrect() + throws JsonParseException, JsonMappingException, IOException { + + final String jsonInput = "{\"key\": \"value\"}"; + final ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeRef = new TypeReference>() { + }; + + final Map map = mapper.readValue(jsonInput, typeRef); + + Assert.assertEquals("value", map.get("key")); + } + + @Test + public void whenObjectStringMapDeserialize_thenCorrect() + throws JsonParseException, JsonMappingException, IOException { + + final String jsonInput = "{\"Abbott and Costello\" : \"Comedy\"}"; + final ObjectMapper mapper = new ObjectMapper(); + + TypeReference> typeRef = new TypeReference>() { + }; + map = mapper.readValue(jsonInput, typeRef); + + Assert.assertEquals("Comedy", map.get(new MyPair("Abbott", "Costello"))); + } + + @Test + public void whenObjectObjectMapDeserialize_thenCorrect() + throws JsonParseException, JsonMappingException, IOException { + + final String jsonInput = "{\"Abbott and Costello\" : \"Comedy and 1940s\"}"; + final ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeRef = new TypeReference>() { + }; + + cmap = mapper.readValue(jsonInput, typeRef); + + Assert.assertEquals(new MyPair("Comedy", "1940s"), + cmap.get(new MyPair("Abbott", "Costello"))); + } +} diff --git a/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java b/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java new file mode 100644 index 0000000000..71ba698e8e --- /dev/null +++ b/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java @@ -0,0 +1,71 @@ +package com.baeldung.jackson.serialization; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.baeldung.jackson.entities.MyPair; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.MapSerializer; + +public class JacksonMapSerializeTest { + + @JsonSerialize(keyUsing = MyPairSerializer.class) + private Map map; + + @JsonSerialize(keyUsing = MapSerializer.class) + private Map cmap; + + @JsonSerialize(keyUsing = MyPairSerializer.class) + private MyPair mapKey; + + @JsonSerialize(keyUsing = MyPairSerializer.class) + private MyPair mapValue; + + @Test + public void whenSimpleMapSerialize_thenCorrect() + throws JsonProcessingException { + + Map map = new HashMap(); + map.put("key", "value"); + + final ObjectMapper mapper = new ObjectMapper(); + final String jsonResult = mapper.writeValueAsString(map); + + Assert.assertEquals("{\"key\":\"value\"}", jsonResult); + } + + @Test + public void whenCustomObjectStringMapSerialize_thenCorrect() + throws JsonProcessingException { + + map = new HashMap(); + MyPair key = new MyPair("Abbott", "Costello"); + map.put(key, "Comedy"); + + final ObjectMapper mapper = new ObjectMapper(); + final String jsonResult = mapper.writeValueAsString(map); + + Assert.assertEquals("{\"Abbott and Costello\":\"Comedy\"}", jsonResult); + } + + @Test + public void whenCustomObjectObjectMapSerialize_thenCorrect() + throws JsonProcessingException { + + cmap = new HashMap(); + mapKey = new MyPair("Abbott", "Costello"); + mapValue = new MyPair("Comedy", "1940's"); + cmap.put(mapKey, mapValue); + + final ObjectMapper mapper = new ObjectMapper(); + final String jsonResult = mapper.writeValueAsString(cmap); + + Assert.assertEquals("{\"Abbott and Costello\":\"Comedy and 1940's\"}", + jsonResult); + } +} From c5806d4e058281142a0dbfee1abc2ec084c735ea Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Mon, 27 Mar 2017 08:46:04 -0500 Subject: [PATCH 090/149] BAEL-737 README (#1514) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md * BAEL-680: renamed test methods * BAEL-714: Updated README.md * BAEL-737: Updated README.md --- spring-jpa/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-jpa/README.md b/spring-jpa/README.md index 313865e3f8..70f4404f98 100644 --- a/spring-jpa/README.md +++ b/spring-jpa/README.md @@ -13,6 +13,7 @@ - [Hibernate Second-Level Cache](http://www.baeldung.com/hibernate-second-level-cache) - [Spring, Hibernate and a JNDI Datasource](http://www.baeldung.com/spring-persistence-jpa-jndi-datasource) - [Deleting Objects with Hibernate](http://www.baeldung.com/delete-with-hibernate) +- [Self-Contained Testing Using an In-Memory Database](http://www.baeldung.com/spring-jpa-test-in-memory-database) ### Eclipse Config After importing the project into Eclipse, you may see the following error: From 365d75a8d7aca4a380926724c0ebe75d4d1396d8 Mon Sep 17 00:00:00 2001 From: Nancy Bosecker Date: Mon, 27 Mar 2017 09:44:59 -0700 Subject: [PATCH 091/149] Jackson version updated (#1516) * Solr w Apache SolrJ * Solr w Apache SolrJ * updated test names and moved add to @before method * create apache-solrj module, moved code from spring-data-solr * More examples for indexing,delete,and query for solrj * More examples for indexing,delete,and query for solrj * Jackson Map Serialize/Deserialize * Jackson Map Serialize/Deserialize * Jackson version update --- jackson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jackson/pom.xml b/jackson/pom.xml index 881ba8e24c..8e627c146b 100644 --- a/jackson/pom.xml +++ b/jackson/pom.xml @@ -195,7 +195,7 @@ - 2.8.6 + 2.8.7 1.7.21 From 1c1c557a399327fc1cf031357c6c041c817a6760 Mon Sep 17 00:00:00 2001 From: Danil Kornishev Date: Mon, 27 Mar 2017 15:21:03 -0400 Subject: [PATCH 092/149] Spring State Machine (#1493) * Neo4j cleanup * Neo4j cleanup * Neo4j cleanup x2 * State Machine Init * cleanup * White background, Java Util Logging * Change to Logging * Static import of asserts. rename test methods * fix attempt for S3 -> S4 * Remove awaitility. add teardown * fix attempt for S3 -> S4 * fix attempt for S3 -> S4 Final --- pom.xml | 1 + spring-state-machine/bpmn/forkjoin.bpmn | 116 ++++++++++++++++++ spring-state-machine/bpmn/img/forkjoin.png | Bin 0 -> 50788 bytes spring-state-machine/bpmn/img/simple.png | Bin 0 -> 22706 bytes spring-state-machine/bpmn/simple.bpmn | 76 ++++++++++++ spring-state-machine/pom.xml | 31 +++++ .../ApplicationReviewEvents.java | 5 + .../ApplicationReviewStates.java | 5 + .../ForkJoinStateMachineConfiguration.java | 74 +++++++++++ ...HierarchicalStateMachineConfiguration.java | 47 +++++++ .../JunctionStateMachineConfiguration.java | 60 +++++++++ .../SimpleEnumStateMachineConfiguration.java | 53 ++++++++ .../SimpleStateMachineConfiguration.java | 105 ++++++++++++++++ .../config/StateMachineListener.java | 16 +++ .../ForkJoinStateMachineTest.java | 45 +++++++ .../HierarchicalStateMachineTest.java | 37 ++++++ .../JunctionStateMachineTest.java | 24 ++++ .../statemachine/StateEnumMachineTest.java | 33 +++++ .../statemachine/StateMachineBuilderTest.java | 35 ++++++ .../spring/statemachine/StateMachineTest.java | 51 ++++++++ 20 files changed, 814 insertions(+) create mode 100644 spring-state-machine/bpmn/forkjoin.bpmn create mode 100644 spring-state-machine/bpmn/img/forkjoin.png create mode 100644 spring-state-machine/bpmn/img/simple.png create mode 100644 spring-state-machine/bpmn/simple.bpmn create mode 100644 spring-state-machine/pom.xml create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java create mode 100644 spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java create mode 100644 spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java diff --git a/pom.xml b/pom.xml index 0939ea91b8..6b4511e45b 100644 --- a/pom.xml +++ b/pom.xml @@ -189,6 +189,7 @@ spring-sleuth spring-social-login spring-spel + spring-state-machine spring-thymeleaf spring-userservice spring-zuul diff --git a/spring-state-machine/bpmn/forkjoin.bpmn b/spring-state-machine/bpmn/forkjoin.bpmn new file mode 100644 index 0000000000..0cb060f74b --- /dev/null +++ b/spring-state-machine/bpmn/forkjoin.bpmn @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-state-machine/bpmn/img/forkjoin.png b/spring-state-machine/bpmn/img/forkjoin.png new file mode 100644 index 0000000000000000000000000000000000000000..642ab6949d18b49012529d7c5b9b14f909c9d701 GIT binary patch literal 50788 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�gf@SIUg-QO`umQ3S)QlWiV3@kS`RLu_ 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 D__yV@ literal 0 HcmV?d00001 diff --git a/spring-state-machine/bpmn/img/simple.png b/spring-state-machine/bpmn/img/simple.png new file mode 100644 index 0000000000000000000000000000000000000000..7a79bf1d895a401b2b59d4b093e770f3546bc3d3 GIT binary patch literal 22706 zcmeFY1y>zSw>65p1PJc#Y=XPH2MDgg-E~8N;KAM9-Ge*9-QC^Y8`HouI^fEt~n>6N(z$52zUr!U|`77Qew(rV378p@568~pwC^ETum@ABuooY zQ6*_nQ4%FbJ5vj56EHBT(1avd)wm(7fdhKlfB>uz-}uQil6IL3!XpE63UHB7(od?V zgSkJggvFu6#npvA>mrg^3HP0oExtgaB_z1i_(CUeN`rZyuiT8MIqi=C+|Ju?vYt#k zSRsAe7-dNY#W2AJw+|4=-`bO};6vq(1vPUm z2)?{6#A=gO3d%|M zjcK)IymHCgNngN(3TribRKa%po>DwcRg)zZz?NbswysDo0#5rCj2uYZL^ER(*s*K* zFl!yI-8_^($jz?B6tL)Beij(Te6B?WNJbB7UHT{td@yOIJK~Gm8KVMHBv7oJevTu>WF_>zn-L1Kc%|Go7^y3=EneQIXl0D~U!l9&Lxa zf1V+S0_!=?ux+*L@QVW0cm%;n)V_n0S6)AN1U?Q+q7$Ydt95#rvilFe=q(x1sQn`G z0cn@qGmo}K=0V43UMH5^p98PN2Spyi5ifA!G2h3Yqc;lR-pC3Bk}cK7ADfsFIb?H% zF>57lwVZv~u)+@Z`+~!*cI>^q3h;*^m0>!1_jBa1%~A zJQRi()t-rno$O3hO?_0J4OjBRS|b`Cjd3n$hXl$UlDG?sOe3yaCmO|_5KcdJMz)_gd*L-!EJvkKE3208pN%99P6dfO3bB2~2s25wa< z3C>2c+PfG;uJpvXZ=c&+yG}m0i%bRlvU7Se^7-z)5#=z-WJYJkSxDv~()kdu5T)7H znMRKCiu^u>p&dxr&17I!ExQO`4$oXOqrvKm$xFK*nsBLFi{1|mi#H$f^>E)2NW4pY zOU(M1=e3gaQ@ZXDW^(&c=`QL%fywacT6xAXCG#?D)CA^w2}>n;K2Z$?ETr>ddSXC3 zXtyJBXZ)Hp1_Lm8jx$0so8!QPA< zn0j9#<{+b5nENhhcQE}=P(8wIQebskKFzN)Yq@W7!a=V6DQ8Two~v zRQ3?*;0N89lkm2Fu=X~$en|$Oko?i0gr(q+bI2`2IKKNPlg@{bZwEb+kxEg-`cjaV zz!9U7hDBpa!7Kk#C25W3*;U`=a)fCPa3+0-Cdw!F@b?n=690=XFSVGI2h&B2f69Oj zFC(xpcgb8pE2L6%|iZJ{1@7v1ax`nB8H-P6+D^)8dO?46B82` zlMLgck!$0mp~MlQ*mjx~@|T!WG4=va6*Fb=*@js>OZq#aJH`kJ)_jjibc^QthI*c5 z&1IuySniheK%Hy?N!t>g0-d~7muB^AmTQ>n%%&M1a2DA)c zi&eQQ?IjJRVk*rGa4M4vnj{>uwkiK$%TQ2?QtDGiD(6hw7SzeKJBL?FJ`~m+(H{a2 z4-UPD#Z1Weg!Y{G(DxvR35WgZWaxg=)zIC1qx+^%>deXgHak&* zDyNot%96t0?0&s9^uVr!tP&v=5s25yUTKMBVQKMfg}~NmnK?%=`Q6;Zyuiw4nx|l{ zN`Aq0X5YeYTA<<$RweLgXw*27btT202agAwhb%*vhmeQ7MWW?P3v>&t3-Qs{TY@{~ zTaKfKBjjVNli*{(bk#zq9f{GVd9-QupUt1$KvD8uuHJ+o`5-{JY6L{Yb;NN55#CJr zT0~t$S%h8cCwT&S7P;SP&Z)VDu7wtb355%zN~}QE%q7>lO4o&Fu7vArC@yph9^XpvR~ z03NjTw)8}v4EyIp>x=Xguf-<-wo?*Pk~b5e5LbRjVTH9~;oCyOUdu?sO2kp(?(dn3 z72Sc1J-xH{KG6yPotlN6=AuxumV+M?>UxQKB2`9u7wsAC*uchT1A%veKLUguG96z! zFgv2&oZpn+j^AM3&R(&f8(}^{{DkCyT7{oQGKQUpMuFjmVnne-ErgMTR)-RV|ALGb zR3DI=Bbi-N6jE7f*c@rOCsN*dV3bOxjcuj!1gVGamBBTAn;b6BtiL`;6 zW_CMPpQg!nbI*{cd1I!VS|PyebU(y>?WExpNr4u-9eYpdZ5mitswq0;H?$H9yR?4F zc$!3jI!>h^xR9Vd?47xts+@XFHplK_Wa*O%)Y4oAn@Cgd24)aNcaXn%OSntO_nVvQ z>~}LbswvMqbf63RZwRPcoxe8gLa++QWb>|Y1D)=(nKha?VTV!@X41=$qIlHB`*X9z z_&sbr542+)(hV z;HjV;UzC%>_PZ_J`mOCv`dHGi^@yu=jy0Oi5fdmft(SJXyv z?}xmbjj;_dLsWN7Aj13Pc*^su_s4t63oq&;fw?WS-J(mhL-p<_E()I8fhy_FbpZme zrn8Wpn4mlu$wtCFVtQLKJZv@;jskWUPHHwcLR6mh`R!@PQ_iprONGPqV!jeb$2HpF zk{*@3q8mlj1_UkG@%+pqo3xqxP3zaCw6mEN-f`%xR`>Ob*CoVEYz2MicK7Yrn}VGr zLI9_3P3N@x-1%|M#@P?SdBcq<_pvQ4ry>?PWoG{%XPH}%M&Mh8l7BI7YiFt1L1b|A2T692uBM>K$MOYHU@QSNv&K~XXR!5IFdN@T1V?0nCSL3IMWy_BXC7#J4i z-(PTPWr|BMFz^fuRSjnic{v^l$7tk zx2`LwT0(tMwdjBE2DK8x$OapL_4_~f>xlS5q|5UBnxg6r1b#C^5T#GN%|KIulKP~?sds~V{p(unP{FK$675Zd@B^@b(ttkGh7ikJGQk(PD=31{O zM_M|%*zN7_SJ;cEpOJ+t?KV0b@0ZL*CUZm!s;ei*Z8GKJz7?cXYxhgUfWb%qW6KZh zT5kuMJl`H&_v70Q)|!mux3%%*+#SwWq#+UUY4nHVlH08OmN#Elm>5-oSlu5>YoNm- zrSRtC>c{fc?g8=Vk**nFjpkEi9`AM&!>`SV z#v8vO*$)>isA3nSDbyK5$81ProkkU>Fki&z;zcT*{iUR9q?UeXZC{pTJCHYfJz}yr z?n>cu*^>k|r@B8z{3{4rqWO}>@}4(|?oVW8adb0QD=Uriy;Z&{j(e$CMoTw#3Svt2R!qubTCt0?6;sbW24wsksGhB=~#NfMD}FD7V*DA|DX^=o#|Mt zwq5%-e5CNAC(DC4f~vOZv5+V0>y-ilp{(3n`dZy5-wefL8E2G!Bz>GJg9^!pOpZtd< zPYlZKm+LK9Y*(Ai?Ob#`CE@K?e%r)#FCfK!lbsYN8+cR8e_9*Hk>0s=@ZX9XGZMdl zTPua+5FO9t*S04BC;ZnVG==~_bxUKjP@a|l7IwHRu{U>4E!`JS)PrBR=cD${x|ip& zh{0(x9B*#tr0O{UXU~OhFqnWnv_AF%Jx`%MrZhr1;f7My?t{7H8Ei6y-h)3sz0Ky% z^RMZ>WbxAo;FR0B_(r7FXv_^~|3Lpyd2M->Zgb#DI*tZGaNl~zTa^`0#1PlUL}p|9 zYnFe>l{lZla0x5sPE;(i^4kFB?%q&OAR=vFLAU@S**{Xe6gxOWTHhCNS3)*hR2rY0 zHGK5wbHW7JfIn5agI340C5{IfZW0c`PO*i$zW$f<4{aUFuGKnt>17Op9i@qKL?;A7 ze(c?`!xwYgYrR<^zvZ6Rffrvx5Ci%y{uv-Eslogy7j&E=D){XdCkkB!;Kg!3r&bi3 zA|bD`?Ij!`tt>a;vYMJ(-OQyl?TTtab1|fy3LffIcvxlMmREdG#jZ_0?HX(v&q%oh zrK3NTggX83sR{b$5L0MfU%~l3Z>vpg&}ZCC`uGd98kH*iIL?!Xdt*%~U{B(+ems{r zAJ5V%D@*<V9Ythsg#;7Z4MxEdgCJsv$cb*}FJ3hpnFUKQBr znIVplFnA6#mL^D_E*|W;psq+CFhyV0rYs{P^BQ-_`mgt?DH1F^agjT}HNtX81RkgE^PPLuV1?{l0>2LqhGHqrn=TV7XW)Hl36prwPkObSgYX5yy`dA6i1hq z%^;`Rd~0BWfckbQb><^&Y}P*)id2_4)O>|Dby23LWuLd*WLrs~`R{;yXZ8=^x9VC! z8n|BWbhL-PB>fgsT9aXDE*!=qmvL_80+b`$JJcAMgTUUS$fjNyxHUEg*~X(@RKfhO z7;et^R?`2-WS@i#7OM?e+|E~h%h)?Pl=Kg1?bL4yZwZ%a@qL$tFa5O*p=*b*Q?nB; z{D*v^FMx{r?rqZM(7!TRr8l>LAVoK>A zUY@U^6IguSU#r=FHVokoXXWsjc3!!=05z}` z*Z0>|zQF_Nh=*AAbH&4;MrhYIl}r87zT5yD*S>K|5f(Vv6e+Ba1U;TRovFFaWZ|7T zUgAQO2Ej%)>^|J-s;8EoRLj`-tk|n1#@m*oH>OflOp*K`{l}sjq=BeD^38+nX$Gq9 zUi45ZISd=Lx|Vs7j=uDI8$j0%U@s%5KBCjL1}~?m94S0_2P@$Y@mHd+;*(B+68s+V zhmtH5;kk05pZ`|uNBXUHL|#g6Qi8t9jT&PIFF|sNS@yotF)iaN#o#kp9F=@7*7(ue z;PXF$04Srjdp3-wuogW7t5KOzAhw3g8&89}uYVn+t~tKVtQ8{gk&EPL@E|be&twq# zv6I@kU(J2Q5igu4dJ1A#xF=AyJ(=+xmSvq(K>~I2dv~H5)f)GAcMmVVMJFpC*4EZK zTO=eT@B|b{Hw6-FTke^)&YrJIlRI2;~Rqjh~v!|=Lzuf-w!8UZnLB)^H;HN zaxq@EGnSSw;(Q{@#-B#7t=^7HZGH2xQg8iSxbXh*!~W_jWRzv0CfRZ!M{mPwF!Vof z!}@p((-+G|QT&DKGf%pXs{`@y_NW2^;+*dqP`?a*?bQVMqkj7+d{7@s%pr>sd~aH{ z_t#=7Enl9D5u$-`UkT*o=XE=eZwEdE$@HI47=(eLP}qR*ne)lKJW!kSON<%3z1NlX zLSca+f&TPQev`oOC=h$1T%1DUB4g+Z1>d*jaF{y9`cfPrdX+pL)Y1GCP#Z@s8W!~8 zEM^LiJ`_dw;?7TKW0 zCFFdhx3Vg<8;_@J|7h7PXDj4Y7vy>0@h+gf4m{nj*o_nzqIcC@?IJ1; z3+}&){n6lMKLg&^>qw1Akb zjp(#$8};%iRE<|Zw1Q?t*v;3YmJU;3%vWfSe|3&sRZS+2Wj!!fCr&+}p-Ca%fgcO(b#SdN@g{8wCoGC-(c=RVVhx z_SS(&=fergCNoExu~|ug!J_PJ5_b{3F&qebwaRG3U>%y)-)FI>JWJCrLF>~ONpJb` z4;;9?zR2>7e)9C3L$yhXL$DKGBqo$5(@hRWDV%nhx5snz4VZ=&($rm7lt&?&LSW#L z0C1t{+q?Idi=?5rgnl9JMct5Z(Y=V-iHzDoYajQ?bUPBd%9nz?UP*?VP_}-UAydgu z9aMbG^Efs27`CaHQcn6fbdP1faIop7-MnWCto3h(c}CQC#Pa=cT8!kQ9p#T*JACjYiT zJ~z?VdpPt3jSlmO`3A0$nw{qeVl@rX^aN%%aPh@d3)U*?(v+kp4easl<-rOxqNg8# zo_?mIzFrmm=xVda=i}{R`C`3YdsCht;>4mO7{LivLlCO%O2F&UzSy|!+yTO!^~aD& zlGIQ)I`_xI!AMi%z>vdP)Qx-D5+5~M*Ny9ncH@vZ2Gg|Bs!5VA)kq?vlQO1e5)LRR zgrX7H$>M+>6uc;|ii8FSxA|oDF_4~)Za2j&bL!_RW8*5>$$UjwB;teN)Aim~F0{&{ zR8ORqG4&O`4ZH+zS@sdB5S^F zKN=cr-?hr}f3v`+ye#l99<<$*i`0CcWh`o;23L_V3#Eh?4MHx#TCaTVO^8iPN}G~f ze7ybCRlBx@id==Y9fX8Swr?JPI~!8aFYCJ_+Xm&~lM83>CH;A5@w)l!_p-Jp$)GSf zEF|@(T@Jf-0*)K08`Y2y2`5ZiByA-GGSE{HT<83Ts#zwbBeI_SGbCSIp61=S!dAjo zUDg+Ij>>#~^K!Bqw=Aw7q2Md5s3?rte159a^6ej-cVjbcmU54#2-=4@MFGtN({1^7 zIeyamK&NJbJfTI%M$0)m?uS*UUP`lcSoki3p~x}be9;dS$0PqF%QMIFF}~_E-;RdI zhVO*uZ~Z)uU9SpwS<@Q&7hJV@DEJLXc8uSsx3j{@nXRvxZ8r1W9csdftB{UI(^DLf z+NKR60eVN$7xn%-vYyY5hm$!!>*5;iHuTCMB%*;=f3O=!I1PIOwE7hcJZhU3Q?+M@ zyWF;%F(s1khspGpy>nQkuU%9sO9npS?}^uHz#~&V;s6OaYUMOz^DQ??J`?h5&@IV} z*UpvJ7^KoUF`6(cCc}r*by&Y#tTF1vLJ=PK zu_*ZZYm>@Lz zSp+-2kmKIh+H`Z`*U)G*N{lZRZ`L@zo>SMCzS)nl-i8_pTv(Ob48_izl!U&07q_!; zb*xKnu%m>PalXNhY)mA(STnX*he)x0wa)9feS=L5^1Qt9zk7J4dh(Ng;_rpu^f-MP z>9}6cT8}f!@>a`q-ws8h-tO3U@^aA6GLebk2Hks+;i&O&JneF#SQ8%9%%C{B60=hS zIYT*$x;?^9ZVBx2oWhg+v*e}2wq~jHTvrM|7I#ID)dtz14(+Myi+JF4+mn9PP8_sh z-|k6O=kF|Tm6l8}l%t^Dui(xUn`{v6l_!)-+)FwlNA;04n#7R+4&g7FT7Q=Xzbg)F z^X5-PYwXjim^2<2%f**$fB3?71Q$kYQ`!)uZ&gYQb0}5SMjc$`lBu|ef}{IZ(A>IgSSrzP?{Sn*nv?wA+I z8(=-uIU4Wpgnc0qi;M9&tV3vcP%*l$3yC>iNg@=j87juDG3)M;L&7C&URrEYHP&=a zs9WuP#`aL4q34|%&a~;*8b9jU=5(umv9WnHkuTOqLn-i`iCHX{mRN68>k7P_vT~o4 zOYwq>nEna*jIsF``+bvv$r1zyawQU@@eM|ew?wQOBG-^e2fZ)5VGZMq;3)&Lq-?G7 zp=f_`-c_rZsg~FTc;aeOiPXkRQh)PhRklj3Ei#bYl=e8;p_xj6x1-+oc2vLP-po=qwR&K z2;VP8?2axkZ8Q0cg)bGQAQ)SUlcasH+Xb;vCJuCKQWxvPc!qVRjxT48utRf8Nz5cd?m$p60bMvi zI=SXF!GHa@6J>?yN-hy@?+U4-)B027gs(AD_vBcO%|cZ_7{eC7q&J=5gu2Zq%H>^l zqSEH}uBt-FXP+27L<|g%%W{kC>$M)=0QSXQ35NFW-IdAFbis2tyO9Us&BWI{3F(jb zx4U1Iqi@Pv%z|MQAx{gCW&_<2z^(4ltF9zCgD20km3($;kOeKa zfRCpHB7N!NTQhwvSkiUkfMzx!xWc^<4ny3l*GN2&!A@ek+*HJN09~eS%P)(=CUrD} zSDi{em0~f>VJ|^_7*zK{2aDn5aFYglQefO*vCQxHd3DlB9Xc2e{=51!Kv zBrJ+5@oR+57jbcM(u+V}BXt~v6lpxN(|PV#up~NgJ}og?w9R2ovC71$@)YJvl)56D z6Sa|!QH!FC!ZiQ9R`|jrZZEPxoC31U%VQja(g>C{daE#iH+| z;H^*wOPqMDv#vKLJO}8TkK3yqS6X9jTpeAN-4UGY#Ys%-posiT@Y{I_wriN?v=Q;Z z9_ve@L0W=&Z!l_7C$U^0K`Q{%6~gVL>J}Yd)Y1*_*JN&}(kwLnxc2Lxv?`v_JF_hOPo9TC@jOCj?WpV3|Yc#VxrSi#plZ}PZCn($>X0q5|4wg)} zTBz`m5?QD7LTn*x$+boNx+jQrAAWdlUuvGu9L*@w0B+!Ux2kYH4U#t0d}gATh21lm zANgrxQ=a9lrh6hlI3!46;NSKB)?qd*p+$8C1A@`(hmxH2Jytu2NQpvmffbZ0g>uqX z*amvP3ex>h@Hp)^I@XrSQ6un=f9>SF5~?W#))|CS2I-GUy0l!Tv=%-y%(v`=(gUw^0tN=i!gIuv$5OBqIEgV#U06T6hh9p! z@5!Bsuvb0L#mjq?T+uUqi!J!>akFm`7O$c3v}{#XP(sC!_#aLwRu)WRb(2aiIr`ox z0U{zvXh*HlR=2{z{|wrERp9MbC50d8%D9DWqOOh+ z>>B7KpT;3S@}uA0g~b5`U~;0ty(i*KWRqlnArnE%Y9jIFceZLt8?!(E0{6#2hx z%ofL(Buto3%4zTpq^eJ9zK>69zZy5xW@igYHd-wx1ao=ekK=s*0^FA4cQH;D)I`kw z!cA78{o$Nkr^2`tU=egi)0Xpill8%nCmEIaSKYXXxp4%Z{e;(%`2Nt*e9|VHr%Ydl>*Xs zLVh!7g(N%5^Pd`rf+mDI%geFpu)d#Cm)#iz&re{8yans;$0D5M^DGcC)!NJ$4R6%j zQ+xYnmUpBX2uz)73h_n|GQF;cC=&b1|08jb+(8-m;iHHswh%%M0Xkom)gcjJ2;hAS zVkeu%(z$06FN4x1K)|S4{;BBKT2b4SP9S2hY5dPYdzYIi-s0nHg@9)+O<&m-7!*|Z zI8%A!iDiD}Lgxe*9@rwxVvpWtQhP#Pty<@H&%0YU0-iKE_0Eolg+UD!q*V;FV_b#9 z@<%n}k7s|av*Rj1f4cb-6nqcE+1(|xtOVYRHr6v)#OWbkB67$y!MeE-empsO9AA0IWkL}+Qe56QrgWPf* zU_qtfqx7V)*CAve1U;u8$5#6FLOT4pq<4Oi zJqVod;l@-d_5{J|Ezf)FlPkY&OrN+~y#|S+2{?bS0R1-1o*!KQP$@1Ez>R7+CkJ&) zS6~Bs5CLu3?6#9V!aJr6vJ%?Z46qUTUti0HXZ8I}9-Qi3LOI&kJfRAKv?9U=-;t;# zq*4<-5(5)+13=}w-{ zF{_CS?)!Bk{y@m&@sCcMp8#G(At*NgB5IvA?{Yr)K5;O>u-RyG4x?dzLd=D!wTD)V zv&CR}huRk|i>L5t+HyFfr!69Vo{^q_K@ree;J_e_W1gDl;f@jRU~7C4QL#=E3?y=?^H{aTZZ$hpci`P$PO+MbJ*-nd?pg7-%KEE z(mvif*{CmDd(&{7?nVu}wqSlHvmQkJM;3|Rf?_V}`vMc{$hETIGpAAQ^^wg4V_7TW zjq2*dQWaP#S$z_!^-5|7Ki<*#D-Y~bcB#Ky0pteaNo+K< z3F6L8#?!e;=|2FNtcp+Fq$;nUr^X+yZ1v4Ewi&L2E?U`F8lgmVYE6YZq%e~;f5Art zjV;GhIyM7J8fH50-vswmZ)RGLPhQvobI&j@n4i|u`Ro)!VNi_wBMIrL8w9245^RF< zsth3TRu^&)s}XaKBTg`VMiR)iWuai*9!{6)B*sAevd&IxQm4>imjD->T|Z7l(#pv1 z|B&;(EAXp7S~a6_S|}%b66jI==&^`-%z$5N;w{dF+ly$4hq4jEleoOsGg*)c*x=%* z6k_r1+P=!X)5P+sN-xO&I4%cB0v7atMHvKENVTxpwOo?;gq!BInO$Nq-d<0Z%tt6$ z;75cGmC7bD?Hfr1Wbn952g%v%{fhfGsc;nDp7wBDda7l2W-~JMTXlAyn+E*Pud4qod zyXu~KMI0iO)R$3FgGWPE^)riTxhPQPNk34|P#g#OtCK#D34~~COvj`aCq*vOtX1O} zc52u172vz+M9~Y`c1k;6)wPBZ0vuYVeGk0n`thv?a2SfqzUO(FI?Gd8fc{wyb3 z4mo0ynOsU-s*vcV3zlx0u;x#igt6XW3;?JDDi;HNk+ zcrit=U>{U`4u;bR#bOQX!Roqr7G`qR6?n|l+nnt&m$-4;$4nWxGi?gD^X7wN?$()u zGg{0|u(JkreGH9c27Ohg0!vcsN;|ZZ7z`AvLdiY^a&QG;+XjXzdut?tj=jc(M=v*l zgw^-s$GWVyQLoYDNrDuEaoM5dpn=6n-3`su#%mUu#~QwFgoynGxsc!1OSwCp@6T3* zc*09MRmIgD{-B zmy6&qs72bV#RoMP8*f^EFEIo}M9Rg`vucTyEaKfw-XgZ2clJ?0a0fQ{_;gH$n#P?<<`YziCQJvFQoH z^~r0yFOx=;-L3_T4`A+&iNt*k`I- zN!zcmULTZ?;2R1+oYE6W{?ofBNB0p!8`Im;vdYp1XY_fTmj_On?PK02Qhzv{Zrbm0 zMw%F(zRZxod+jY&sz)=pheAnp;X+w|L~?g<<@IVHqT8eSyPKkj7^R*5&>iU0*h#wkZ=sjxI?epo^>`uz-Au<)DG#!IItVnq#UdjyN`lwj z{A*?4?YKKflOU~7ta6Zhw1xe+t%xe>bg~4zi@jNyae^o(^CKFl8R-GCMP)q3V@;#@ zPIZlEeHayPIv@tIcKWIODEY){p>l2^Vt;&9y~yuQg>x^U0*M?1$N0p%UeCEZ+)K5W zaL}cA{M7Zmzm0S4AT5DdfW=w^;cL&mgcy$_d=3*!vZUhgRCJ(=?v0gRtQs62$Ko7Y znW5^+L3G%>iKUdSp9S9TP>~LfZTcHXNB85Yg*&nHMP^K9ZLSY%9rj8-;c<<%t*XXF}|`4B#9{*6Ah7p^cGEH zl{CP9?F9crx{lKr`?K|ieSwi;dTRd&&YqoGN9J{js|LYC?zNAyKx{YqEV9VT0{ z9|TWtb_z`0%d=;}G`2Hu*t4*b;Hi7r zEufL1S_#8eb61)=BR@%Ddn=n9{8QJ8!;0NtXgHLzobQC`FF)!gE|9ndGkl6J(>1aq z>+q#7X7uys8T| zrCSxAjO*0NKYZ#h^*4QX<2nFu|H-#H9o;I-fvI;v--i)I8ON?!xycr2DQFhSN2(%M zM3;9DPfFYDM4`0+oSo-9U9i?xo|Jj&bYvd3KkcGhtTxpk;V=s+@Sd^Rlu*sCMQ_&p zz(R%nrxVNf0WLm1elQ3b|5m2_x6O)Yl^=&TQ~}JmV~~qXd2-lX$sb#-GMF(f7Q)@o z5kO~~7TQjrM=QvC=1daMG{V#>hp6gW61&LQN6&rS#U)AWgoOo$_$;^^u z)f6d5UA`_b_ye0LYgKZ+6Fk2k`xL9~bW|lu+j3FB{#ibNk;hl~KG2IRO>?r=+NfjS z4Q&YBHke?DP=$VZtu0k`j|+lIwxpnPnvwsHr)Quqnp%s?Ng*b(o`pKYnJ?H2$UZdk zw_u5*R*bZiHeuNNO1Ak!-}9k zQ+TIgt(|OvryO7yjb(xQ9uk-al16d*rtbr7yNjj zdb?s*+0F>a;OKmy3p{IBzv!dU3h`hYch>tKiz$S{-rnBV-RP!#4JP;q{CF0Lll?!S zcLAEqnf5++&3z}r6MpY+!u7`E%eQjhsZMb`7|(FL+!W4$!Eo9k@i;g*u!^FLg@%Uy z4b2e;Tkq`sd^A)G+uyV9~xyrL$sLT+M`T;@_?3vU>^GjiOJZ&%4 zuTAHYLt`Tere|r=YhEue(j$@aLw5d^hWO)AGDX0< zMOD?HNc)=b^Ox$+7oESxM{Zoz;p`7xrKXr)LW6fe@=ZJ@y_zbb<9>~AHf)wTtRXUR z`}yIb3c5?|;Ns6~I9Q|c5c-=D(O|CwbJ9J=Qart6?8-De zTi}H1pxi)`fMf-jlzPU@H>Q2xWEKAB!@TQJ)`>xor7kxDT}@4ms$NC|9#$UTTJ3S- zgVk{T91nc_mJ7;H*dIn3E|%(ey_Q|7F$xY<0Od;EBqrT_vx&^ltGN_moD-9g z-{H~LwYr_lRMwC=SF@qHW>F!>!vGyL%u&1`0lA6YQBPMQHl%~E_8^4M=jf)^ZbaZM zsP$%Y{AcL&J^RXDn)t@xJmB|yvRDd>KJiUUc}k~*Phfv#?yP64wHtIN~UKFoO64r{0mz~F!qx_ ztyZUwZQWCH8$ z*_;oR20ChfW``z0AYwDkMg+DH+l6NW&$-*3v40Y%UOR_UUUp{*O!lle5Jtx7dtcHI z4Ud;HfngL8TKRV-A8{4uIXCBrrO&=lJURClU9THmeA^g6{j0}<%vCewHQ|7R_hTK4 zg~p6oD~+m_>2}+5%B=S5l+g~O-wf^se)Bzhggc{{lyeFD!2#$RAZPum!vG=AM2Sk# zv41&+mQawfE6C({{N`ZJw11`{{ZQreCfoG@0&L4Kh|Z=b82c}uRyT7r5uHMJ`GTXrX-R~9KGOSHGO432x2!eZqb+--gW#5R*9`?F6kd`!*tAhf{NHwL8 zrNg*l^Is&ykt=Y4bG=MU;}uJ&oxN>aq|jO|xbdGfmUbcpqd2wclN#H-`h+ z;@p=yyn&4N?>}{N6V>MFC?Eki5i3Da9E04A>2ZEgi@S25G(~yGpD}m^-2q>Z$Xj-Y%vN0twKe8tE}rcS2DVMmt(zUvTa{K~%{V?M<5_KYmK(%X{wxBvfE4E{K&DDq2w2!JZ z6BuwERBVJ>ZXs(kj9y7eGo>!Y`7%O$-n2ISp6d>`*(Ixi@<%Tk^e+|M2kB0?nXE|B z8q3F@VT~a7N25WcJRUckBUj#JBndWCr(6W@OJUueGKF01dd71lAMc~44FidaNA=n| zhzzG}cuOk=X*M$h6W-BZyrNLQuAElVaT(C zhAGe)-Vn)f5kFk&(_JbWiq1d-CrnL0(c<@JXtFg#LvRe5Mcl^nE7(UM)IbzS;PupV98QB;IM};wu3VF=k?fiNCpx$2(N_C z^xW&%?BH;=sDiK6922PYQJ@I&TTg-8Y@EQ469tl*iuaP44U)BffWRp{R6)i(d=rY5 z<5P)HUQF*5B(v*u@mF&1FwGwrNAGfx6D4WnNqnB6&h7POZQc_rwKppn*H2biBQ2CC z#<%*CBUeR4#8{{}_y-?LIz~9DzFW}9EL6<)`>F3eg7D;^$u*?O8>U&m+M7udNO&k} zuZrSse!n{@=ypJU#IIR`G-YsDegv6&NNu9~GyteN!u}qx4CYbzoRP9rHuvmt1;Ijz z&oOUokTQyd8kR5^T_gxLt3RO;>zRYQ*$B0`G?Lps-d_sZ+H|y2kxWM^mTiw+WC6I> zFQilJi8V)tB%67HJ`}P<+GKkLd&->--nbuI?iQ2f!ctW3ypix&D2wg4s7XO`10l;$ z)fb=2k75-;9;<6yNjlQyt3Ce#!RS9x42e6u#DF|KTuNw0%S=wpk~X<0w~@!6Nqrim z0pums5U{dT{wbvsx9`R;8IrzuTnBg35_52c#)hfb>rEhw<55I!wH7=$s=yL(?=px0 z1(GwMNvvrPz>pO8oJm0lquI}c9#fQbTU6YYvfjCSeTHv;7OV{yc&hn9wMFvhzq&um7i=D}RT2Z~rr6$Rr9y$!^S? z5VB_{Wv5iKg>0353q!UNm1r_*R7NUGj-u?xPO>kNHG8&W%U1T~d(YEzJ?A*r_aAt! z>-lZ2nQK1F{l4G#^15HIcSY#HfdRAax5*YNOa67jwIi!6ws<+0o1x|57nMzVZc7HA zQ?Yn0fXru3L`>vyH-xO)xZg31xINDnN%tORWm&;H+Z-!CJpGhP^L>==JYVx;irHsn zGP^~3O9$9aF%=7PpyJ98BWa%T7miXBo?@PlSF}{=!IBHuolSsR3z%vql?o3GK+b`6 z_wgwvp(~janMv;6hshpjDtuug;MO%fQvgNs)Tt4^)nTF`=LtmDkV)>3{S2{_uN^+m zj5<8K^3j1&U@-lBjF(=^(ff#h%blZ&7M++qGGQ(gE3vI-zB1I)PDDRlmhF(T+?`1x}Bq0^X$RYK4PcZq(6UPchV;K+kH4HvJ zkv08v;3j)``6aht!Q=>Cr)0UDY2s!;U77SpVm|{c-d1Wtuqa?h|^9FsOO- zqD~|EuFuW1x4*~!!8)#EPe`T)ch27J?z^^zucoFtZW=A6+Z@9ZpP~!GB>wdn>Ik|E zP}6Z?$5VApgzl9Px>NVa0(J4JoX0Egxq+=zjZM2q1Ws36ii601bC6pgu;%Bx8$4n} zRe5UUIKFkQhD_NRFb!lHt2UIWXAMlCH@jleG5?e3k54K?YOp znXex*((4_0bA#c*#L6m$=&`wPb{u*;(X^_Rk<}QJgx1mO1wjroJzZ2Q$Zpw{BnO7o zP}K#huC5t*Lf55SO@uICrrHY#@4xh5S+tnJXO5woY^Yid-c$5vLmG(D4w9!>Lp+cs z#4ur?xhkKl)?Q_kkn*i2s^Sj7!(&PsWvKELRY$5}j=Mb>S49D_A~WOhR$>^u$i zUBak|d>`k(Rf1I-S8M14dpfsQ6TT%4;uWK}H!aG970d{YcuWGwi{Ne;6KJ&|l;`jag06 zf1U(mb=q>XbAub>`li*mCgCvY(vr9N@l{*>=F?OG~t)1N=pjtd=FQ@ z33l$&;(bC`2ee=mcsHK)$5ORGOW`P81hsW;hJ72YA;`6O4^{SpTtNtQfKO&-W~P+G z&fU)}UH^SuK?Oz#nEB&&Lf5lJSZYO}qLoN5>oH?-7Y_9spM0pi{>H+yPSDlGzin!Z zltjwmk9o1MIM#53&$jwO8ej1+e`BTZbm#H4$JUttz(l`3NYlF~Skh>!m-?yGkVzys z!3t8AKaYiatvD;oT@qp-dWn$`e}E9sMnB)9$MH4Ru5m$7FwRu#0ffFP`sUa99tjDn z(LclJz5|-kX$MWAK-tdLT=J_|{0m8O0)O@O)COPm0$R@S;a>8)DySWSJ(e03Hx$(Z zON>_vSdN8Sv6~Hn`@3We6yjP;$mTDsEgh^V3z&-AL6c6EdN`@Sm+vX^bzA!+7Q_c6 zI(ylU%6-cdsS{ebJZI z1_HIys&e3Z(#u25LT#)lPU863SRD|)@BkaN?eQD=N^Qh07kanWV1^X_jsX4I2UYTh zZeMfK;(=l+tr)$x!FvKDfZL|{RnW3u3uScB#2Y4v0ASY9Aneg?wKU$eMOu`^U>}DN z1fI96$DPDfX(3whlmPHS54S;k^`?`hcIsj^oz>urpq>~zfl@mgaE4}LP==Gs9T4Q& z&9$YU_ME({sVZA_Di{?KLei?d5P{emO+5=>mz|U15EGLNG%N9R%{+A>NSe`^>=w3o zl@CCP-uW#E7aDtD$hYEbrqFhd3X8lff#s6zSb(vVGQzn&KCm`)Rbo(U^ZUz^3jVIVr3is zbECjbdottPlF26B`9iE}HgYDo! zbaMC)Z!FOnWpO6Pxw7#ET7nE9LgQ>eUOc)_gSw-)w>J9Qt75yrJvrZ8*)a!2zx6I=B%1g=LVv@TD+w(~+A-A$sLtvycBk-P1rsKnOIt^8`#j4-G z)Q<&E5Yp}$v+~A$X=n~v*SG1$rspQxg|aNMyr%Z1&(!-1Z)kr!4V$9iO}0eA?+|DU zmU@1~9!R7UrA9p*^ED7~xqiHz#-h~dPRO3fK`H?4Du0o*E`hV|+AhR@zU`h1e zvtKXn?LAmuF}o)nU-z^0HaCb(Bp zDNl$((8^{0{H=F{9wi`p^yT!z*tFG@eXNO&wjrGXfv$9|SJueX^cY3K*QaXk&f~jO zVsx~i!4chC9Uy7&F1wn73pIhlAo32!9A79s+7%Ev079Y{$~5NDO{ec+WPwRqDyOj z44I5)xAeObb>BvXlM(=hhKHe*y^cPFTphz~iUDuAEjs=)!`s_TQryZY=rn zv_0wdnW&h`%1W~f?u_9l{_N8Rt14_XHRX=4ej6XK?Jt3nG@jl6k&8QRXh-^`1`r$y z(_g9N${6;T;>^f6p0rRGb-Ey{?aC_3U+h*1JYkeacY7$gf!V6wi;HG0{5379@R8{Yr^;G z)2A%oIm?=^r-!`p$isr6L*cJc*16)bw*~8g`5JbA{02Mpci&z$dlD|5EG;OeF%9g3 zXf&}dYDd$jfq0f*wmBw79r0bBg>ti&T5thEXJlmbEhXiB1{3xyO#ACvTDm$7&2z3X ze+V~`Cv5%fnwDe`z&W65AohM+74^J93zsh3Fd;mo_M;z|?7iX*dEH~iHaW7xEwPAU z7dYVHmi7^fFF4YIfmPujf}B=;eF1Naj>4AlIwkfYIzb>ryA#G3_B~nl9UHNVU0O1= zAJFfeJ%PB}UVJhy;#>OR8uIGu!uMZoGS3_isQ@PFd08ceC1O$3K^rXt+Xb;SJ);wi zPta1x;zJQbY%_lw%5DEuCfecgeU+P$=EOElJv2=Fw1Gy3y7jI91LyT3 AC;$Ke literal 0 HcmV?d00001 diff --git a/spring-state-machine/bpmn/simple.bpmn b/spring-state-machine/bpmn/simple.bpmn new file mode 100644 index 0000000000..8ed463e9f9 --- /dev/null +++ b/spring-state-machine/bpmn/simple.bpmn @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml new file mode 100644 index 0000000000..5393626083 --- /dev/null +++ b/spring-state-machine/pom.xml @@ -0,0 +1,31 @@ + + + + parent-modules + com.baeldung + 1.0.0-SNAPSHOT + + 4.0.0 + + baeldung-spring-state-machine + + 1.8 + 1.8 + + + + + org.springframework.statemachine + spring-statemachine-core + 1.2.3.RELEASE + + + junit + junit + 4.11 + test + + + \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java new file mode 100644 index 0000000000..971fc5dde7 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java @@ -0,0 +1,5 @@ +package com.baeldung.spring.stateMachine.applicationReview; + +public enum ApplicationReviewEvents { + APPROVE, REJECT +} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java new file mode 100644 index 0000000000..1df2db1f86 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java @@ -0,0 +1,5 @@ +package com.baeldung.spring.stateMachine.applicationReview; + +public enum ApplicationReviewStates { + PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED +} diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java new file mode 100644 index 0000000000..c55104a627 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java @@ -0,0 +1,74 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +@Configuration +@EnableStateMachine +public class ForkJoinStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .fork("SFork") + .join("SJoin") + .end("SF") + .and() + .withStates() + .parent("SFork") + .initial("Sub1-1") + .end("Sub1-2") + .and() + .withStates() + .parent("SFork") + .initial("Sub2-1") + .end("Sub2-2"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SFork").event("E1") + .and().withExternal() + .source("Sub1-1").target("Sub1-2").event("sub1") + .and().withExternal() + .source("Sub2-1").target("Sub2-2").event("sub2") + .and() + .withFork() + .source("SFork") + .target("Sub1-1") + .target("Sub2-1") + .and() + .withJoin() + .source("Sub1-2") + .source("Sub2-2") + .target("SJoin"); + } + + @Bean + public Guard mediumGuard() { + return (ctx) -> false; + } + + @Bean + public Guard highGuard() { + return (ctx) -> false; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java new file mode 100644 index 0000000000..708dbd3077 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java @@ -0,0 +1,47 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +@Configuration +@EnableStateMachine +public class HierarchicalStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .state("SI") + .end("SF") + .and() + .withStates() + .parent("SI") + .initial("SUB1") + .state("SUB2") + .end("SUBEND"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SF").event("end") + .and().withExternal() + .source("SUB1").target("SUB2").event("se1") + .and().withExternal() + .source("SUB2").target("SUBEND").event("s-end"); + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java new file mode 100644 index 0000000000..e1bae10fb7 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java @@ -0,0 +1,60 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +@Configuration +@EnableStateMachine +public class JunctionStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .junction("SJ") + .state("high") + .state("medium") + .state("low") + .end("SF"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("SJ").event("E1") + .and() + .withJunction() + .source("SJ") + .first("high", highGuard()) + .then("medium", mediumGuard()) + .last("low") + .and().withExternal() + .source("low").target("SF").event("end"); + } + + @Bean + public Guard mediumGuard() { + return (ctx) -> false; + } + + @Bean + public Guard highGuard() { + return (ctx) -> false; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java new file mode 100644 index 0000000000..4e11851644 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java @@ -0,0 +1,53 @@ +package com.baeldung.spring.stateMachine.config; + +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +import java.util.Arrays; +import java.util.HashSet; + +@Configuration +@EnableStateMachine +public class SimpleEnumStateMachineConfiguration extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(ApplicationReviewStates.PEER_REVIEW) + .state(ApplicationReviewStates.PRINCIPAL_REVIEW) + .end(ApplicationReviewStates.APPROVED) + .end(ApplicationReviewStates.REJECTED); + + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.PRINCIPAL_REVIEW).event(ApplicationReviewEvents.APPROVE) + .and().withExternal() + .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.APPROVED).event(ApplicationReviewEvents.APPROVE) + .and().withExternal() + .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT) + .and().withExternal() + .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT); + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java new file mode 100644 index 0000000000..bb7556f97f --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java @@ -0,0 +1,105 @@ +package com.baeldung.spring.stateMachine.config; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.logging.Logger; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.guard.Guard; + +@Configuration +@EnableStateMachine +public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter { + + public static final Logger LOGGER = Logger.getLogger(SimpleStateMachineConfiguration.class.getName()); + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withConfiguration() + .autoStartup(true) + .listener(new StateMachineListener()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .end("SF") + .states(new HashSet<>(Arrays.asList("S1", "S2"))) + .state("S4", executeAction(), errorAction()) + .stateEntry("S3", entryAction()) + .stateDo("S3", executeAction()) + .stateExit("S3", exitAction()); + + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions.withExternal() + .source("SI").target("S1").event("E1").action(initAction()) + .and().withExternal() + .source("S1").target("S2").event("E2") + .and().withExternal() + .source("SI").target("S3").event("E3") + .and().withExternal() + .source("S3").target("S4").event("E4").and().withExternal().source("S4").target("SF").event("end").guard(simpleGuard()) + .and().withExternal() + .source("S2").target("SF").event("end"); + } + + @Bean + public Guard simpleGuard() { + return (ctx) -> { + int approvalCount = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); + return approvalCount > 0; + }; + } + + @Bean + public Action entryAction() { + return (ctx) -> { + LOGGER.info("Entry " + ctx.getTarget().getId()); + }; + } + + @Bean + public Action executeAction() { + return (ctx) -> { + LOGGER.info("Do " + ctx.getTarget().getId()); + int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); + approvals++; + ctx.getExtendedState().getVariables().put("approvalCount", approvals); + }; + } + + @Bean + public Action exitAction() { + return (ctx) -> { + LOGGER.info("Exit " + ctx.getSource().getId() + " -> " + ctx.getTarget().getId()); + }; + } + + @Bean + public Action errorAction() { + return (ctx) -> { + LOGGER.info("Error " + ctx.getSource().getId() + ctx.getException()); + }; + } + + @Bean + public Action initAction() { + return (ctx) -> { + LOGGER.info(ctx.getTarget().getId()); + }; + } +} \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java new file mode 100644 index 0000000000..bb7859c683 --- /dev/null +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java @@ -0,0 +1,16 @@ +package com.baeldung.spring.stateMachine.config; + +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; + +import java.util.logging.Logger; + +public class StateMachineListener extends StateMachineListenerAdapter { + + public static final Logger LOGGER = Logger.getLogger(StateMachineListener.class.getName()); + + @Override + public void stateChanged(State from, State to) { + LOGGER.info(String.format("Transitioned from %s to %s%n", from == null ? "none" : from.getId(), to.getId())); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java new file mode 100644 index 0000000000..416da5f0fe --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration; + +public class ForkJoinStateMachineTest { + + @Test + public void whenForkStateEntered_thenMultipleSubStatesEntered() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + boolean success = stateMachine.sendEvent("E1"); + + assertTrue(success); + + assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); + } + + @Test + public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + boolean success = stateMachine.sendEvent("E1"); + + assertTrue(success); + + assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds())); + + assertTrue(stateMachine.sendEvent("sub1")); + assertTrue(stateMachine.sendEvent("sub2")); + assertEquals("SJoin", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java new file mode 100644 index 0000000000..3557a63211 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration; + +public class HierarchicalStateMachineTest { + + @Test + public void whenTransitionToSubMachine_thenSubStateIsEntered() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + + assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("se1"); + + assertEquals(Arrays.asList("SI", "SUB2"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("s-end"); + + assertEquals(Arrays.asList("SI", "SUBEND"), stateMachine.getState().getIds()); + + stateMachine.sendEvent("end"); + + assertEquals(1, stateMachine.getState().getIds().size()); + assertEquals("SF", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java new file mode 100644 index 0000000000..d0c1225c9b --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java @@ -0,0 +1,24 @@ +package com.baeldung.spring.stateMachine; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration; + +public class JunctionStateMachineTest { + + @Test + public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class); + StateMachine stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + + stateMachine.sendEvent("E1"); + Assert.assertEquals("low", stateMachine.getState().getId()); + + stateMachine.sendEvent("end"); + Assert.assertEquals("SF", stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java new file mode 100644 index 0000000000..1fd7bd85f0 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java @@ -0,0 +1,33 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; +import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; +import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration; + +public class StateEnumMachineTest { + + private StateMachine stateMachine; + + @Before + public void setUp() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class); + stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + } + + @Test + public void whenStateMachineConfiguredWithEnums_thenStateMachineAcceptsEnumEvents() { + assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.APPROVE)); + assertEquals(ApplicationReviewStates.PRINCIPAL_REVIEW, stateMachine.getState().getId()); + assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT)); + assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java new file mode 100644 index 0000000000..cdd1e951e0 --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java @@ -0,0 +1,35 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineBuilder; + +public class StateMachineBuilderTest { + + @Test + public void whenUseStateMachineBuilder_thenBuildSuccessAndMachineWorks() throws Exception { + StateMachineBuilder.Builder builder = StateMachineBuilder.builder(); + builder.configureStates().withStates() + .initial("SI") + .state("S1") + .end("SF"); + + builder.configureTransitions() + .withExternal() + .source("SI").target("S1").event("E1") + .and().withExternal() + .source("S1").target("SF").event("E2"); + + StateMachine machine = builder.build(); + + machine.start(); + + machine.sendEvent("E1"); + assertEquals("S1", machine.getState().getId()); + + machine.sendEvent("E2"); + assertEquals("SF", machine.getState().getId()); + } +} diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java new file mode 100644 index 0000000000..9409f66f4f --- /dev/null +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java @@ -0,0 +1,51 @@ +package com.baeldung.spring.stateMachine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration; + +public class StateMachineTest { + + private AnnotationConfigApplicationContext ctx; + private StateMachine stateMachine; + + @Before + public void setUp() { + ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class); + stateMachine = ctx.getBean(StateMachine.class); + stateMachine.start(); + } + + @Test + public void whenSimpleStringStateMachineEvents_thenEndState() { + assertEquals("SI", stateMachine.getState().getId()); + + stateMachine.sendEvent("E1"); + assertEquals("S1", stateMachine.getState().getId()); + + stateMachine.sendEvent("E2"); + assertEquals("S2", stateMachine.getState().getId()); + } + + @Test + public void whenSimpleStringMachineActionState_thenActionExecuted() { + + stateMachine.sendEvent("E3"); + assertEquals("S3", stateMachine.getState().getId()); + + boolean acceptedE4 = stateMachine.sendEvent("E4"); + + assertTrue(acceptedE4); + assertEquals("S4", stateMachine.getState().getId()); + assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount")); + + stateMachine.sendEvent("end"); + assertEquals("SF", stateMachine.getState().getId()); + } +} From 6a0e1420649662bad2569bbc6186bf266fc32d1a Mon Sep 17 00:00:00 2001 From: Tomasz Sobala Date: Mon, 27 Mar 2017 21:28:15 +0200 Subject: [PATCH 093/149] BAEL-431 Exploring the Spring Boot TestRestTemplate (#1444) * injecting beans * XML-based configuration replaced with Java Config. * [BAEL-431] Exploring TestRestTemplate. * Revert of evaluation task "XML-based configuration replaced with Java Config." This reverts commit 66471cf0574c85f8ff514ec4caf5ba44ebba1a74. * Revert of evaluation task "injecting beans" This reverts commit d2ac20185e636245bc0ae0b4ccb952965de88e28. * [BAEL-431] fix to the tests in TestRestTemplateBasicLiveTest. --- spring-rest/pom.xml | 4 + .../client/TestRestTemplateBasicLiveTest.java | 118 ++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java diff --git a/spring-rest/pom.xml b/spring-rest/pom.xml index da26d8abe9..a9b208bcd2 100644 --- a/spring-rest/pom.xml +++ b/spring-rest/pom.xml @@ -29,6 +29,10 @@ org.springframework.boot spring-boot-devtools + + org.springframework.boot + spring-boot-test + diff --git a/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java b/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java new file mode 100644 index 0000000000..9f4319d857 --- /dev/null +++ b/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java @@ -0,0 +1,118 @@ +package org.baeldung.client; + +import okhttp3.Request; +import okhttp3.RequestBody; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import static org.baeldung.client.Consts.APPLICATION_PORT; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +public class TestRestTemplateBasicLiveTest { + + private RestTemplate restTemplate; + private static final String FOO_RESOURCE_URL = "http://localhost:" + APPLICATION_PORT + "/spring-rest/myfoos"; + private static final String URL_SECURED_BY_AUTHENTICATION = "http://browserspy.dk/password-ok.php"; + private static final String BASE_URL = "http://localhost:" + APPLICATION_PORT + "/spring-rest"; + + @Before + public void beforeTest() { + restTemplate = new RestTemplate(); + } + + // GET + @Test + public void givenTestRestTemplate_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate(); + ResponseEntity response = testRestTemplate.getForEntity(FOO_RESOURCE_URL + "/1", String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenRestTemplateWrapper_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplate); + ResponseEntity response = testRestTemplate.getForEntity(FOO_RESOURCE_URL + "/1", String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenRestTemplateBuilderWrapper_whenSendGetForEntity_thenStatusOk() { + RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); + restTemplateBuilder.build(); + TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplateBuilder); + ResponseEntity response = testRestTemplate.getForEntity(FOO_RESOURCE_URL + "/1", String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenRestTemplateWrapperWithCredentials_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplate, "test", "test"); + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenTestRestTemplateWithCredentials_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenTestRestTemplateWithBasicAuth_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate(); + ResponseEntity response = testRestTemplate.withBasicAuth("test", "test"). + getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + @Test + public void givenTestRestTemplateWithCredentialsAndEnabledCookies_whenSendGetForEntity_thenStatusOk() { + TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test", TestRestTemplate. + HttpClientOption.ENABLE_COOKIES); + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + String.class); + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + } + + // HEAD + @Test + public void givenFooService_whenCallHeadForHeaders_thenReceiveAllHeaders() { + TestRestTemplate testRestTemplate = new TestRestTemplate(); + final HttpHeaders httpHeaders = testRestTemplate.headForHeaders(FOO_RESOURCE_URL); + assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON)); + } + + // POST + @Test + public void givenService_whenPostForObject_thenCreatedObjectIsReturned() { + TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); + final RequestBody body = RequestBody.create(okhttp3.MediaType.parse("text/html; charset=utf-8"), + "{\"id\":1,\"name\":\"Jim\"}"); + final Request request = new Request.Builder().url(BASE_URL + "/users/detail").post(body).build(); + Object response = testRestTemplate.postForObject(URL_SECURED_BY_AUTHENTICATION, request, String.class); + assertTrue(response.toString().contains("Success")); + } + + // PUT + @Test + public void givenService_whenPutForObject_thenCreatedObjectIsReturned() { + TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); + final RequestBody body = RequestBody.create(okhttp3.MediaType.parse("text/html; charset=utf-8"), + "{\"id\":1,\"name\":\"Jim\"}"); + final Request request = new Request.Builder().url(BASE_URL + "/users/detail").post(body).build(); + testRestTemplate.put(URL_SECURED_BY_AUTHENTICATION, request, String.class); + } + +} From 0c8aa7e46deb161c6fbe863d9e5acb2f77685370 Mon Sep 17 00:00:00 2001 From: azrairshad Date: Mon, 27 Mar 2017 14:17:58 -0700 Subject: [PATCH 094/149] Added an example for array copy via stream (#1510) --- .../baeldung/arraycopy/ArrayCopyUtilTest.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java index 2c9a97c496..060f3c3109 100644 --- a/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java +++ b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java @@ -144,7 +144,31 @@ public class ArrayCopyUtilTest { //change in employees' element didn't change in the copied array Assert.assertFalse(copiedArray[0].getName().equals(employees[0].getName())); } - + + @Test + public void givenArraysOfNonPrimitiveType_whenCopiedViaStream_thenDoShallowCopy(){ + Employee[] copiedArray = Arrays.stream(employees).toArray(Employee[]::new); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == employees.length); + employees[0].setName(employees[0].getName()+"_Changed"); + //change in employees' element didn't change in the copied array + Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); + } + + @Test + public void givenArraysOfPrimitiveType_whenCopiedViaStream_thenSuccessful(){ + String[] strArray = {"orange", "red", "green'"}; + + String[] copiedArray = Arrays.stream(strArray).toArray(String[]::new); + + Assert.assertNotNull(copiedArray); + Assert.assertTrue(copiedArray.length == strArray.length); + Assert.assertTrue(copiedArray[0] == strArray[0]); + Assert.assertTrue(copiedArray[1] == strArray[1]); + Assert.assertTrue(copiedArray[2] == strArray[2]); + } + private Address[] createAddressArray(){ Address[] addresses = new Address[1]; addresses[0] = createAddress(); From 85969c69d22e0d9313648d417bbdf6e51915fe9d Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Tue, 28 Mar 2017 08:17:00 +0200 Subject: [PATCH 095/149] State machine refactor (#1520) * State machine refactor * Add surefire plugin * Refactor --- spring-state-machine/pom.xml | 29 ++++++++++++++++++- .../ApplicationReviewEvents.java | 2 +- .../ApplicationReviewStates.java | 2 +- .../ForkJoinStateMachineConfiguration.java | 2 +- ...HierarchicalStateMachineConfiguration.java | 2 +- .../JunctionStateMachineConfiguration.java | 2 +- .../SimpleEnumStateMachineConfiguration.java | 12 ++------ .../SimpleStateMachineConfiguration.java | 2 +- .../config/StateMachineListener.java | 2 +- .../ForkJoinStateMachineTest.java | 13 ++++----- .../HierarchicalStateMachineTest.java | 12 ++++---- .../JunctionStateMachineTest.java | 5 ++-- .../statemachine/StateEnumMachineTest.java | 13 ++++----- .../statemachine/StateMachineBuilderTest.java | 2 +- ....java => StateMachineIntegrationTest.java} | 13 ++++----- spring-zuul/pom.xml | 1 - 16 files changed, 65 insertions(+), 49 deletions(-) rename spring-state-machine/src/test/java/com/baeldung/spring/statemachine/{StateMachineTest.java => StateMachineIntegrationTest.java} (88%) diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml index 5393626083..bec03c39e8 100644 --- a/spring-state-machine/pom.xml +++ b/spring-state-machine/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - baeldung-spring-state-machine + spring-state-machine 1.8 1.8 @@ -27,5 +27,32 @@ 4.11 test + + + com.jayway.awaitility + awaitility + 1.7.0 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + **/*IntegrationTest.java + **/*LiveTest.java + + + + + + + + \ No newline at end of file diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java index 971fc5dde7..300bd6027e 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.applicationReview; +package com.baeldung.spring.statemachine.applicationreview; public enum ApplicationReviewEvents { APPROVE, REJECT diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java index 1df2db1f86..3d173e7471 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.applicationReview; +package com.baeldung.spring.statemachine.applicationreview; public enum ApplicationReviewStates { PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java index c55104a627..3a3e632c51 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java index 708dbd3077..2bf9405c39 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java index e1bae10fb7..2f48a9dbb5 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java index 4e11851644..5339dea7d0 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java @@ -1,19 +1,13 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; -import org.springframework.context.annotation.Bean; +import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewEvents; +import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewStates; import org.springframework.context.annotation.Configuration; -import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.Guard; - -import java.util.Arrays; -import java.util.HashSet; @Configuration @EnableStateMachine diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java index bb7556f97f..e9b448f6e7 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; import java.util.Arrays; import java.util.HashSet; diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java index bb7859c683..47a274404e 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine.config; +package com.baeldung.spring.statemachine.config; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.state.State; diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java index 416da5f0fe..7b4b1928ea 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java @@ -1,15 +1,14 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; +package com.baeldung.spring.statemachine; +import com.baeldung.spring.statemachine.config.ForkJoinStateMachineConfiguration; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; -import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class ForkJoinStateMachineTest { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java index 3557a63211..d2944be173 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java @@ -1,14 +1,14 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; +package com.baeldung.spring.statemachine; +import com.baeldung.spring.statemachine.config.HierarchicalStateMachineConfiguration; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; -import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + public class HierarchicalStateMachineTest { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java index d0c1225c9b..f01683638b 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java @@ -1,17 +1,16 @@ -package com.baeldung.spring.stateMachine; +package com.baeldung.spring.statemachine; import org.junit.Assert; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; -import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration; public class JunctionStateMachineTest { @Test public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(com.baeldung.spring.statemachine.config.JunctionStateMachineConfiguration.class); StateMachine stateMachine = ctx.getBean(StateMachine.class); stateMachine.start(); diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java index 1fd7bd85f0..257ed8768c 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java @@ -1,16 +1,15 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +package com.baeldung.spring.statemachine; +import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewEvents; +import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewStates; +import com.baeldung.spring.statemachine.config.SimpleEnumStateMachineConfiguration; import org.junit.Before; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents; -import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates; -import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class StateEnumMachineTest { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java index cdd1e951e0..a5e4d07f18 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java @@ -1,4 +1,4 @@ -package com.baeldung.spring.stateMachine; +package com.baeldung.spring.statemachine; import static org.junit.Assert.assertEquals; diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java similarity index 88% rename from spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java rename to spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java index 9409f66f4f..d7b26eeb97 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java @@ -1,16 +1,15 @@ -package com.baeldung.spring.stateMachine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +package com.baeldung.spring.statemachine; +import com.baeldung.spring.statemachine.config.SimpleStateMachineConfiguration; import org.junit.Before; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; -import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -public class StateMachineTest { +public class StateMachineIntegrationTest { private AnnotationConfigApplicationContext ctx; private StateMachine stateMachine; @@ -34,7 +33,7 @@ public class StateMachineTest { } @Test - public void whenSimpleStringMachineActionState_thenActionExecuted() { + public void whenSimpleStringMachineActionState_thenActionExecuted() throws InterruptedException { stateMachine.sendEvent("E3"); assertEquals("S3", stateMachine.getState().getId()); diff --git a/spring-zuul/pom.xml b/spring-zuul/pom.xml index 50b20b8791..02fe589a3a 100644 --- a/spring-zuul/pom.xml +++ b/spring-zuul/pom.xml @@ -45,7 +45,6 @@ org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin.version} true From a18d779294b7c023f4167774ec811e2cf331955d Mon Sep 17 00:00:00 2001 From: Tian Baoqiang Date: Tue, 28 Mar 2017 17:56:57 +0800 Subject: [PATCH 096/149] BAEL-636: add standalone deployment (#1521) --- spring-5/pom.xml | 7 -- .../com/baeldung/functional/RootServlet.java | 87 +++++++++++++++++++ .../java/com/baeldung/web/FooController.java | 3 +- spring-5/src/main/webapp/WEB-INF/web.xml | 21 +++++ 4 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 spring-5/src/main/java/com/baeldung/functional/RootServlet.java create mode 100644 spring-5/src/main/webapp/WEB-INF/web.xml diff --git a/spring-5/pom.xml b/spring-5/pom.xml index 59bead4b73..6c52e6c8cc 100644 --- a/spring-5/pom.xml +++ b/spring-5/pom.xml @@ -40,13 +40,6 @@ spring-boot-starter-webflux - - - io.projectreactor - reactor-core - 3.0.6.BUILD-SNAPSHOT - - org.apache.commons diff --git a/spring-5/src/main/java/com/baeldung/functional/RootServlet.java b/spring-5/src/main/java/com/baeldung/functional/RootServlet.java new file mode 100644 index 0000000000..457206221b --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/RootServlet.java @@ -0,0 +1,87 @@ +package com.baeldung.functional; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; + +import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers; +import static org.springframework.web.reactive.function.BodyExtractors.toFormData; +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.server.RequestPredicates.*; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +public class RootServlet extends ServletHttpHandlerAdapter { + + public RootServlet() { + this(WebHttpHandlerBuilder + .webHandler(toHttpHandler(routingFunction())) + .prependFilter(new IndexRewriteFilter()) + .build()); + } + + private RootServlet(HttpHandler httpHandler) { + super(httpHandler); + } + + private static final Actor BRAD_PITT = new Actor("Brad", "Pitt"); + private static final Actor TOM_HANKS = new Actor("Tom", "Hanks"); + private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS)); + + private static RouterFunction routingFunction() { + + return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))) + .andRoute(POST("/login"), serverRequest -> serverRequest + .body(toFormData()) + .map(MultiValueMap::toSingleValueMap) + .map(formData -> { + System.out.println("form data: " + formData.toString()); + if ("baeldung".equals(formData.get("user")) && "you_know_what_to_do".equals(formData.get("token"))) { + return ok() + .body(Mono.just("welcome back!"), String.class) + .block(); + } + return ServerResponse + .badRequest() + .build() + .block(); + })) + .andRoute(POST("/upload"), serverRequest -> serverRequest + .body(toDataBuffers()) + .collectList() + .map(dataBuffers -> { + AtomicLong atomicLong = new AtomicLong(0); + dataBuffers.forEach(d -> atomicLong.addAndGet(d + .asByteBuffer() + .array().length)); + System.out.println("data length:" + atomicLong.get()); + return ok() + .body(fromObject(atomicLong.toString())) + .block(); + })) + .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/"))) + .andNest(path("/actor"), route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest + .bodyToMono(Actor.class) + .doOnNext(actors::add) + .then(ok().build()))) + .filter((request, next) -> { + System.out.println("Before handler invocation: " + request.path()); + return next.handle(request); + }); + + } + +} diff --git a/spring-5/src/main/java/com/baeldung/web/FooController.java b/spring-5/src/main/java/com/baeldung/web/FooController.java index de6928033e..d0b69e707e 100644 --- a/spring-5/src/main/java/com/baeldung/web/FooController.java +++ b/spring-5/src/main/java/com/baeldung/web/FooController.java @@ -1,6 +1,7 @@ package com.baeldung.web; import java.util.List; +import java.util.Optional; import javax.validation.constraints.Max; import javax.validation.constraints.Min; @@ -33,7 +34,7 @@ public class FooController { @ResponseBody @Validated public Foo findById(@PathVariable @Min(0) final long id) { - return repo.findOne(id); + return repo.findOne(id).orElse(null); } @RequestMapping(method = RequestMethod.GET) diff --git a/spring-5/src/main/webapp/WEB-INF/web.xml b/spring-5/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..bfcf43dad2 --- /dev/null +++ b/spring-5/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + Spring Functional Application + + + functional + com.baeldung.functional.RootServlet + 1 + true + + + functional + / + + + + \ No newline at end of file From 0bc9bfbacbd10b0673a65ec52a0293da2db7f6ff Mon Sep 17 00:00:00 2001 From: Tian Baoqiang Date: Tue, 28 Mar 2017 19:50:03 +0800 Subject: [PATCH 097/149] BAEL-636: register servlet using java config (#1525) --- .../FunctionalSpringBootApplication.java | 87 +++++++++++++++++++ .../com/baeldung/functional/RootServlet.java | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 spring-5/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java diff --git a/spring-5/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java b/spring-5/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java new file mode 100644 index 0000000000..258a23a2bf --- /dev/null +++ b/spring-5/src/main/java/com/baeldung/functional/FunctionalSpringBootApplication.java @@ -0,0 +1,87 @@ +package com.baeldung.functional; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.server.reactive.HttpHandler; +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.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; +import reactor.core.publisher.Flux; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.server.RequestPredicates.*; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication +@ComponentScan(basePackages = { "com.baeldung.functional" }) +public class FunctionalSpringBootApplication { + + private static final Actor BRAD_PITT = new Actor("Brad", "Pitt"); + private static final Actor TOM_HANKS = new Actor("Tom", "Hanks"); + private static final List actors = new CopyOnWriteArrayList<>(Arrays.asList(BRAD_PITT, TOM_HANKS)); + + private RouterFunction routingFunction() { + FormHandler formHandler = new FormHandler(); + + RouterFunction restfulRouter = route(GET("/"), serverRequest -> ok().body(Flux.fromIterable(actors), Actor.class)).andRoute(POST("/"), serverRequest -> serverRequest + .bodyToMono(Actor.class) + .doOnNext(actors::add) + .then(ok().build())); + + return route(GET("/test"), serverRequest -> ok().body(fromObject("helloworld"))) + .andRoute(POST("/login"), formHandler::handleLogin) + .andRoute(POST("/upload"), formHandler::handleUpload) + .and(RouterFunctions.resources("/files/**", new ClassPathResource("files/"))) + .andNest(path("/actor"), restfulRouter) + .filter((request, next) -> { + System.out.println("Before handler invocation: " + request.path()); + return next.handle(request); + }); + } + + @Bean + public ServletRegistrationBean servletRegistrationBean() throws Exception { + HttpHandler httpHandler = WebHttpHandlerBuilder + .webHandler(toHttpHandler(routingFunction())) + .prependFilter(new IndexRewriteFilter()) + .build(); + ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new RootServlet(httpHandler), "/"); + registrationBean.setLoadOnStartup(1); + registrationBean.setAsyncSupported(true); + return registrationBean; + } + + @Configuration + @EnableWebSecurity + @Profile("!https") + static class SecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(final HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest() + .permitAll(); + } + } + + public static void main(String[] args) { + SpringApplication.run(FunctionalSpringBootApplication.class, args); + } + +} diff --git a/spring-5/src/main/java/com/baeldung/functional/RootServlet.java b/spring-5/src/main/java/com/baeldung/functional/RootServlet.java index 457206221b..54892c829f 100644 --- a/spring-5/src/main/java/com/baeldung/functional/RootServlet.java +++ b/spring-5/src/main/java/com/baeldung/functional/RootServlet.java @@ -33,7 +33,7 @@ public class RootServlet extends ServletHttpHandlerAdapter { .build()); } - private RootServlet(HttpHandler httpHandler) { + RootServlet(HttpHandler httpHandler) { super(httpHandler); } From 70d8fecc54c9c7dd5812a6e3c3329e81e3f15c49 Mon Sep 17 00:00:00 2001 From: Parth Joshi Date: Tue, 28 Mar 2017 18:29:33 +0530 Subject: [PATCH 098/149] Comparator comparing (#1515) * Initial commit for Comparator.comparing article. * Changes in the code as per suggestions in review. * Change in test names as per suggestions... * Changes in tests names for nullFirst and nullLast cases * clean up. --- .../java8/comparator/Java8ComparatorTest.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java b/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java index 57e3898274..ebcbb7a3fc 100644 --- a/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java +++ b/core-java/src/test/java/com/baeldung/java8/comparator/Java8ComparatorTest.java @@ -76,6 +76,15 @@ public class Java8ComparatorTest { // System.out.println(Arrays.toString(employees)); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); } + + @Test + public void whenReversed_thenSortedByNameDesc() { + Comparator employeeNameComparator = Comparator.comparing(Employee::getName); + Comparator employeeNameComparatorReversed = employeeNameComparator.reversed(); + Arrays.sort(employees, employeeNameComparatorReversed); +// System.out.println(Arrays.toString(employees)); + assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); + } @Test public void whenComparingInt_thenSortedByAge() { @@ -153,4 +162,6 @@ public class Java8ComparatorTest { assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByNameAge)); } -} \ No newline at end of file + +} + From ef91c379b74f6645f53c348f96234bf19df44f4f Mon Sep 17 00:00:00 2001 From: ahamedm Date: Tue, 28 Mar 2017 18:50:00 +0400 Subject: [PATCH 099/149] BAEL-696 Implement OR in the REST API Query Language (#1518) * Dependency Injection Types, XML-Config, Java-Config, Test Classes * Formatting done with Formatter Configuration in Eclipse * REST Query Lang - Adv Search Ops - Improvement - C1 * REST Query Lang - Adv Search Ops - Improvement - C2 * BAEL-696 Code formatting * REST Query Lang - Adv Search Ops - Improvement - C3 * BAEL-636: add standalone deployment (#1521) * BAEL-696 Formatting --- .gitignore | 2 + .../persistence/IEnhancedSpecification.java | 10 - .../dao/GenericSpecificationsBuilder.java | 98 +++--- .../dao/UserSpecificationsBuilder.java | 18 +- .../web/controller/UserController.java | 298 +++++++++--------- .../baeldung/web/util/SearchOperation.java | 8 +- .../baeldung/web/util/SpecSearchCriteria.java | 14 +- .../JPASpecificationIntegrationTest.java | 23 +- 8 files changed, 250 insertions(+), 221 deletions(-) delete mode 100644 spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java diff --git a/.gitignore b/.gitignore index 784627b616..f3fa30f3e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*/bin/* + *.class # Package Files # diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java deleted file mode 100644 index 58d08a161e..0000000000 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/IEnhancedSpecification.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.baeldung.persistence; - -import org.springframework.data.jpa.domain.Specification; - -public interface IEnhancedSpecification extends Specification { - - default boolean isOfLowPrecedence() { - return false; - } -} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java index 4936c2e1c1..45c015f233 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java @@ -1,6 +1,7 @@ package org.baeldung.persistence.dao; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @@ -12,53 +13,64 @@ import org.springframework.data.jpa.domain.Specifications; public class GenericSpecificationsBuilder { - private final List params; + private final List params; - public GenericSpecificationsBuilder() { - this.params = new ArrayList<>(); - } + public GenericSpecificationsBuilder() { + this.params = new ArrayList<>(); + } - public final GenericSpecificationsBuilder with(final String key, final String operation, final Object value, - final String prefix, final String suffix) { - return with(null, key, operation, value, prefix, suffix); - } + public final GenericSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) { + return with(null, key, operation, value, prefix, suffix); + } - public final GenericSpecificationsBuilder with(final String precedenceIndicator, final String key, - final String operation, final Object value, final String prefix, final String suffix) { - SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); - if (op != null) { - if (op == SearchOperation.EQUALITY) // the operation may be complex operation - { - final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX); - final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + public final GenericSpecificationsBuilder with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) { + SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); + if (op != null) { + if (op == SearchOperation.EQUALITY) // the operation may be complex operation + { + final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX); - if (startWithAsterisk && endWithAsterisk) { - op = SearchOperation.CONTAINS; - } else if (startWithAsterisk) { - op = SearchOperation.ENDS_WITH; - } else if (endWithAsterisk) { - op = SearchOperation.STARTS_WITH; - } - } - params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value)); - } - return this; - } + if (startWithAsterisk && endWithAsterisk) { + op = SearchOperation.CONTAINS; + } else if (startWithAsterisk) { + op = SearchOperation.ENDS_WITH; + } else if (endWithAsterisk) { + op = SearchOperation.STARTS_WITH; + } + } + params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value)); + } + return this; + } - public Specification build(Function> converter) { + public Specification build(Function> converter) { + + if (params.size() == 0) { + return null; + } + + params.sort(Comparator.comparing(SpecSearchCriteria::isOrPredicate)); + + final List> specs = params + .stream() + .map(converter) + .collect(Collectors.toCollection(ArrayList::new)); + + Specification result = specs.get(0); + + for (int idx = 1; idx < specs.size(); idx++) { + result = params + .get(idx) + .isOrPredicate() + ? Specifications + .where(result) + .or(specs.get(idx)) + : Specifications + .where(result) + .and(specs.get(idx)); + } + return result; + } - if (params.size() == 0) - return null; - - params.sort((spec0, spec1) -> Boolean.compare(spec0.isLowPrecedence(), spec1.isLowPrecedence())); - - final List> specs = params.stream().map(converter).collect(Collectors.toCollection(ArrayList::new)); - - Specification result = specs.get(0); - - for (int idx = 1; idx < specs.size(); idx++) { - result=params.get(idx).isLowPrecedence()? Specifications.where(result).or(specs.get(idx)): Specifications.where(result).and(specs.get(idx)); - } - return result; - } } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java index 8a10163f51..bbcb521241 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java @@ -1,14 +1,15 @@ package org.baeldung.persistence.dao; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + import org.baeldung.persistence.model.User; import org.baeldung.web.util.SearchOperation; import org.baeldung.web.util.SpecSearchCriteria; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specifications; -import java.util.ArrayList; -import java.util.List; - public final class UserSpecificationsBuilder { private final List params; @@ -48,14 +49,17 @@ public final class UserSpecificationsBuilder { if (params.size() == 0) return null; - params.sort((spec0, spec1) -> { - return Boolean.compare(spec0.isLowPrecedence(), spec1.isLowPrecedence()); - }); + params.sort(Comparator.comparing(SpecSearchCriteria::isOrPredicate)); Specification result = new UserSpecification(params.get(0)); for (int i = 1; i < params.size(); i++) { - result = params.get(i).isLowPrecedence() ? Specifications.where(result).or(new UserSpecification(params.get(i))) : Specifications.where(result).and(new UserSpecification(params.get(i))); + result = params.get(i) + .isOrPredicate() + ? Specifications.where(result) + .or(new UserSpecification(params.get(i))) + : Specifications.where(result) + .and(new UserSpecification(params.get(i))); } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java index fff089a62b..4c21d9836d 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java @@ -1,141 +1,157 @@ -package org.baeldung.web.controller; - -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.BooleanExpression; -import cz.jirutka.rsql.parser.RSQLParser; -import cz.jirutka.rsql.parser.ast.Node; -import org.baeldung.persistence.dao.*; -import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor; -import org.baeldung.persistence.model.MyUser; -import org.baeldung.persistence.model.User; -import org.baeldung.web.util.SearchCriteria; -import org.baeldung.web.util.SearchOperation; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.querydsl.binding.QuerydslPredicate; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -//@EnableSpringDataWebSupport -@Controller -@RequestMapping(value = "/auth/") -public class UserController { - - @Autowired - private IUserDAO service; - - @Autowired - private UserRepository dao; - - @Autowired - private MyUserRepository myUserRepository; - - public UserController() { - super(); - } - - // API - READ - - @RequestMapping(method = RequestMethod.GET, value = "/users") - @ResponseBody - public List findAll(@RequestParam(value = "search", required = false) final String search) { - final List params = new ArrayList(); - if (search != null) { - final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),"); - final Matcher matcher = pattern.matcher(search + ","); - while (matcher.find()) { - params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3))); - } - } - return service.searchUser(params); - } - - @RequestMapping(method = RequestMethod.GET, value = "/users/spec") - @ResponseBody - public List findAllBySpecification(@RequestParam(value = "search") final String search) { - final UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET); - final Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); - final Matcher matcher = pattern.matcher(search + ","); - while (matcher.find()) { - builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5)); - } - - final Specification spec = builder.build(); - return dao.findAll(spec); - } - - @RequestMapping(method = RequestMethod.GET, value = "/users/espec") - @ResponseBody - public List findAllByOptionalSpecification(@RequestParam(value = "search") final String search) { - final Specification spec = resolveSpecification(search); - return dao.findAll(spec); - } - - protected Specification resolveSpecification(String searchParameters) { - - final UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET); - final Pattern pattern = Pattern.compile("(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); - final Matcher matcher = pattern.matcher(searchParameters + ","); - while (matcher.find()) { - builder.with(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(5), matcher.group(4), matcher.group(6)); - } - return builder.build(); - } - - @RequestMapping(method = RequestMethod.GET, value = "/myusers") - @ResponseBody - public Iterable findAllByQuerydsl(@RequestParam(value = "search") final String search) { - final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder(); - if (search != null) { - final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),"); - final Matcher matcher = pattern.matcher(search + ","); - while (matcher.find()) { - builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); - } - } - final BooleanExpression exp = builder.build(); - return myUserRepository.findAll(exp); - } - - @RequestMapping(method = RequestMethod.GET, value = "/users/rsql") - @ResponseBody - public List findAllByRsql(@RequestParam(value = "search") final String search) { - final Node rootNode = new RSQLParser().parse(search); - final Specification spec = rootNode.accept(new CustomRsqlVisitor()); - return dao.findAll(spec); - } - - @RequestMapping(method = RequestMethod.GET, value = "/api/myusers") - @ResponseBody - public Iterable findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) final Predicate predicate) { - return myUserRepository.findAll(predicate); - } - - // API - WRITE - - @RequestMapping(method = RequestMethod.POST, value = "/users") - @ResponseStatus(HttpStatus.CREATED) - public void create(@RequestBody final User resource) { - Preconditions.checkNotNull(resource); - dao.save(resource); - } - - @RequestMapping(method = RequestMethod.POST, value = "/myusers") - @ResponseStatus(HttpStatus.CREATED) - public void addMyUser(@RequestBody final MyUser resource) { - Preconditions.checkNotNull(resource); - myUserRepository.save(resource); - - } - -} +package org.baeldung.web.controller; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.baeldung.persistence.dao.IUserDAO; +import org.baeldung.persistence.dao.MyUserPredicatesBuilder; +import org.baeldung.persistence.dao.MyUserRepository; +import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.dao.UserSpecificationsBuilder; +import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor; +import org.baeldung.persistence.model.MyUser; +import org.baeldung.persistence.model.User; +import org.baeldung.web.util.SearchCriteria; +import org.baeldung.web.util.SearchOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.querydsl.binding.QuerydslPredicate; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; + +import cz.jirutka.rsql.parser.RSQLParser; +import cz.jirutka.rsql.parser.ast.Node; + +//@EnableSpringDataWebSupport +@Controller +@RequestMapping(value = "/auth/") +public class UserController { + + @Autowired + private IUserDAO service; + + @Autowired + private UserRepository dao; + + @Autowired + private MyUserRepository myUserRepository; + + public UserController() { + super(); + } + + // API - READ + + @RequestMapping(method = RequestMethod.GET, value = "/users") + @ResponseBody + public List findAll(@RequestParam(value = "search", required = false) String search) { + List params = new ArrayList(); + if (search != null) { + Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),"); + Matcher matcher = pattern.matcher(search + ","); + while (matcher.find()) { + params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3))); + } + } + return service.searchUser(params); + } + + @RequestMapping(method = RequestMethod.GET, value = "/users/spec") + @ResponseBody + public List findAllBySpecification(@RequestParam(value = "search") String search) { + UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); + String operationSetExper = Joiner + .on("|") + .join(SearchOperation.SIMPLE_OPERATION_SET); + Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); + Matcher matcher = pattern.matcher(search + ","); + while (matcher.find()) { + builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5)); + } + + Specification spec = builder.build(); + return dao.findAll(spec); + } + + @GetMapping(value = "/users/espec") + @ResponseBody + public List findAllByOrPredicate(@RequestParam(value = "search") String search) { + Specification spec = resolveSpecification(search); + return dao.findAll(spec); + } + + protected Specification resolveSpecification(String searchParameters) { + + UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); + String operationSetExper = Joiner + .on("|") + .join(SearchOperation.SIMPLE_OPERATION_SET); + Pattern pattern = Pattern.compile("(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); + Matcher matcher = pattern.matcher(searchParameters + ","); + while (matcher.find()) { + builder.with(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(5), matcher.group(4), matcher.group(6)); + } + return builder.build(); + } + + @RequestMapping(method = RequestMethod.GET, value = "/myusers") + @ResponseBody + public Iterable findAllByQuerydsl(@RequestParam(value = "search") String search) { + MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder(); + if (search != null) { + Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),"); + Matcher matcher = pattern.matcher(search + ","); + while (matcher.find()) { + builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); + } + } + BooleanExpression exp = builder.build(); + return myUserRepository.findAll(exp); + } + + @RequestMapping(method = RequestMethod.GET, value = "/users/rsql") + @ResponseBody + public List findAllByRsql(@RequestParam(value = "search") String search) { + Node rootNode = new RSQLParser().parse(search); + Specification spec = rootNode.accept(new CustomRsqlVisitor()); + return dao.findAll(spec); + } + + @RequestMapping(method = RequestMethod.GET, value = "/api/myusers") + @ResponseBody + public Iterable findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) Predicate predicate) { + return myUserRepository.findAll(predicate); + } + + // API - WRITE + + @RequestMapping(method = RequestMethod.POST, value = "/users") + @ResponseStatus(HttpStatus.CREATED) + public void create(@RequestBody User resource) { + Preconditions.checkNotNull(resource); + dao.save(resource); + } + + @RequestMapping(method = RequestMethod.POST, value = "/myusers") + @ResponseStatus(HttpStatus.CREATED) + public void addMyUser(@RequestBody MyUser resource) { + Preconditions.checkNotNull(resource); + myUserRepository.save(resource); + + } + +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java index 41a556c18a..fa09662201 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java @@ -4,10 +4,10 @@ public enum SearchOperation { EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS; public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~" }; - - public static final String LOW_PRECEDENCE_INDICATOR="'"; - - public static final String ZERO_OR_MORE_REGEX="*"; + + public static final String OR_PREDICATE_FLAG = "'"; + + public static final String ZERO_OR_MORE_REGEX = "*"; public static SearchOperation getSimpleOperation(final char input) { switch (input) { diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java index 7dbb66edea..6b37fb579c 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java @@ -5,7 +5,7 @@ public class SpecSearchCriteria { private String key; private SearchOperation operation; private Object value; - private boolean lowPrecedence; + private boolean orPredicate; public SpecSearchCriteria() { @@ -18,9 +18,9 @@ public class SpecSearchCriteria { this.value = value; } - public SpecSearchCriteria(final String lowPrecedenceIndicator, final String key, final SearchOperation operation, final Object value) { + public SpecSearchCriteria(final String orPredicate, final String key, final SearchOperation operation, final Object value) { super(); - this.lowPrecedence = lowPrecedenceIndicator != null && lowPrecedenceIndicator.equals(SearchOperation.LOW_PRECEDENCE_INDICATOR); + this.orPredicate = orPredicate != null && orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG); this.key = key; this.operation = operation; this.value = value; @@ -50,12 +50,12 @@ public class SpecSearchCriteria { this.value = value; } - public boolean isLowPrecedence() { - return lowPrecedence; + public boolean isOrPredicate() { + return orPredicate; } - public void setLowPrecedence(boolean lowPrecedence) { - this.lowPrecedence = lowPrecedence; + public void setOrPredicate(boolean orPredicate) { + this.orPredicate = orPredicate; } } diff --git a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java index e5c408bfdb..244e19db90 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java @@ -70,7 +70,9 @@ public class JPASpecificationIntegrationTest { public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john")); final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe")); - final List results = repository.findAll(Specifications.where(spec).and(spec1)); + final List results = repository.findAll(Specifications + .where(spec) + .and(spec1)); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); @@ -80,10 +82,13 @@ public class JPASpecificationIntegrationTest { public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() { UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - final SpecSearchCriteria spec = new SpecSearchCriteria("'", "firstName", SearchOperation.EQUALITY, "john"); - final SpecSearchCriteria spec1 = new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"); + SpecSearchCriteria spec = new SpecSearchCriteria("'", "firstName", SearchOperation.EQUALITY, "john"); + SpecSearchCriteria spec1 = new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"); - final List results = repository.findAll(builder.with(spec1).with(spec).build()); + List results = repository.findAll(builder + .with(spec1) + .with(spec) + .build()); assertThat(results, hasSize(2)); assertThat(userJohn, isIn(results)); @@ -97,7 +102,8 @@ public class JPASpecificationIntegrationTest { builder.with("'", "firstName", ":", "john", null, null); builder.with(null, "lastName", ":", "doe", null, null); - final List results = repository.findAll(builder.build(converter)); + List results = repository.findAll(builder.build(converter)); + assertThat(results, hasSize(2)); assertThat(userJohn, isIn(results)); assertThat(userTom, isIn(results)); @@ -116,7 +122,6 @@ public class JPASpecificationIntegrationTest { public void givenMinAge_whenGettingListOfUsers_thenCorrect() { final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25")); final List results = repository.findAll(Specifications.where(spec)); - assertThat(userTom, isIn(results)); assertThat(userJohn, not(isIn(results))); } @@ -125,7 +130,6 @@ public class JPASpecificationIntegrationTest { public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo")); final List results = repository.findAll(spec); - assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); } @@ -134,7 +138,6 @@ public class JPASpecificationIntegrationTest { public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.ENDS_WITH, "n")); final List results = repository.findAll(spec); - assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); } @@ -152,7 +155,9 @@ public class JPASpecificationIntegrationTest { public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "20")); final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.LESS_THAN, "25")); - final List results = repository.findAll(Specifications.where(spec).and(spec1)); + final List results = repository.findAll(Specifications + .where(spec) + .and(spec1)); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); From 17042f0420772d036b8d24dab1c6644ec918a8a7 Mon Sep 17 00:00:00 2001 From: Chandravadan S Date: Tue, 28 Mar 2017 21:01:25 +0530 Subject: [PATCH 100/149] BAEL-732: String to enum (#1526) --- .../com/baeldung/enums/PizzaUnitTest.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java b/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java index 6cf6ad3551..db7aa6d920 100644 --- a/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java +++ b/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java @@ -1,12 +1,14 @@ package com.baeldung.enums; -import org.junit.Test; +import static junit.framework.TestCase.assertTrue; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; -import static junit.framework.TestCase.assertTrue; +import org.junit.Test; + +import com.baeldung.enums.Pizza.PizzaStatusEnum; public class PizzaUnitTest { @@ -75,5 +77,25 @@ public class PizzaUnitTest { pz.deliver(); assertTrue(pz.getStatus() == Pizza.PizzaStatusEnum.DELIVERED); } + + @Test + public void givenValidEnumValueAsString_whenConvertedIntoEnum_thenGetsConvertedCorrectly() { + String pizzaEnumValue = "READY"; + PizzaStatusEnum pizzaStatusEnum = PizzaStatusEnum.valueOf(pizzaEnumValue); + assertTrue(pizzaStatusEnum == PizzaStatusEnum.READY); + } + + @Test(expected = IllegalArgumentException.class) + public void givenInvalidEnumValueCaseWiseAsString_whenConvertedIntoEnum_thenThrowsException() { + String pizzaEnumValue = "rEAdY"; + PizzaStatusEnum pizzaStatusEnum = PizzaStatusEnum.valueOf(pizzaEnumValue); + } + + @Test(expected = IllegalArgumentException.class) + public void givenInvalidEnumValueContentWiseAsString_whenConvertedIntoEnum_thenThrowsException() { + String pizzaEnumValue = "invalid"; + PizzaStatusEnum pizzaStatusEnum = PizzaStatusEnum.valueOf(pizzaEnumValue); + } + } From 6e2dcfcf5903d2e273ef48507e8c3daf3f0d8f3b Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Tue, 28 Mar 2017 22:45:46 +0200 Subject: [PATCH 101/149] Refactor PizzaUnitTest (#1527) --- .../test/java/com/baeldung/enums/PizzaUnitTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java b/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java index db7aa6d920..35aa07821c 100644 --- a/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java +++ b/core-java/src/test/java/com/baeldung/enums/PizzaUnitTest.java @@ -1,14 +1,13 @@ package com.baeldung.enums; -import static junit.framework.TestCase.assertTrue; +import com.baeldung.enums.Pizza.PizzaStatusEnum; +import org.junit.Test; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; -import org.junit.Test; - -import com.baeldung.enums.Pizza.PizzaStatusEnum; +import static junit.framework.TestCase.assertTrue; public class PizzaUnitTest { @@ -71,7 +70,7 @@ public class PizzaUnitTest { } @Test - public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() { + public void whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() { Pizza pz = new Pizza(); pz.setStatus(Pizza.PizzaStatusEnum.READY); pz.deliver(); @@ -79,14 +78,14 @@ public class PizzaUnitTest { } @Test - public void givenValidEnumValueAsString_whenConvertedIntoEnum_thenGetsConvertedCorrectly() { + public void whenConvertedIntoEnum_thenGetsConvertedCorrectly() { String pizzaEnumValue = "READY"; PizzaStatusEnum pizzaStatusEnum = PizzaStatusEnum.valueOf(pizzaEnumValue); assertTrue(pizzaStatusEnum == PizzaStatusEnum.READY); } @Test(expected = IllegalArgumentException.class) - public void givenInvalidEnumValueCaseWiseAsString_whenConvertedIntoEnum_thenThrowsException() { + public void whenConvertedIntoEnum_thenThrowsException() { String pizzaEnumValue = "rEAdY"; PizzaStatusEnum pizzaStatusEnum = PizzaStatusEnum.valueOf(pizzaEnumValue); } From 6fe778979a3b7fce3ff5a483a1544e8eba360deb Mon Sep 17 00:00:00 2001 From: Devendra Tiwari Date: Wed, 29 Mar 2017 02:18:47 +0530 Subject: [PATCH 102/149] Guide to Guava | common.util.concurrent (#1528) Code and Tests for common.util.concurrent package --- .../tutorial/AtomicLongMapTutorials.java | 28 ++++++++ .../guava/tutorial/MonitorExample.java | 29 +++++++++ guava21/src/test/java/AtomicLongMapTests.java | 64 +++++++++++++++++++ guava21/src/test/java/MonitorUnitTests.java | 61 ++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java create mode 100644 guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java create mode 100644 guava21/src/test/java/AtomicLongMapTests.java create mode 100644 guava21/src/test/java/MonitorUnitTests.java diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java b/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java new file mode 100644 index 0000000000..69ad04ad9c --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java @@ -0,0 +1,28 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.util.concurrent.AtomicLongMap; + +public class AtomicLongMapTutorials { + + private AtomicLongMap atomicLongMap; + + public AtomicLongMapTutorials(){ + atomicLongMap = AtomicLongMap.create(); + } + + + public void addKeys(){ + atomicLongMap.addAndGet("apple",250); + atomicLongMap.addAndGet("bat",350); + atomicLongMap.addAndGet("cat",450); + atomicLongMap.addAndGet("dog",550); + } + + public static void main(String[] args){ + AtomicLongMapTutorials atomicLongMapTutorials = new AtomicLongMapTutorials(); + atomicLongMapTutorials.addKeys(); + + System.out.println(atomicLongMapTutorials.atomicLongMap.get("2")); + } + +} diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java new file mode 100644 index 0000000000..2f316c293e --- /dev/null +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java @@ -0,0 +1,29 @@ +package com.baeldung.guava.tutorial; + +import com.google.common.util.concurrent.Monitor; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +public class MonitorExample { + private List students = new ArrayList(); + private static final int MAX_SIZE = 100; + + private Monitor monitor = new Monitor(); + + + public void addToCourse(String item) throws InterruptedException { + Monitor.Guard studentsBelowCapacity = monitor.newGuard(this::isStudentsCapacityUptoLimit); + monitor.enterWhen(studentsBelowCapacity); + try { + students.add(item); + } finally { + monitor.leave(); + } + } + + public Boolean isStudentsCapacityUptoLimit(){ + return students.size() > MAX_SIZE; + } +} diff --git a/guava21/src/test/java/AtomicLongMapTests.java b/guava21/src/test/java/AtomicLongMapTests.java new file mode 100644 index 0000000000..aad72907de --- /dev/null +++ b/guava21/src/test/java/AtomicLongMapTests.java @@ -0,0 +1,64 @@ +import com.google.common.util.concurrent.AtomicLongMap; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AtomicLongMapTests { + + private static final String SPRING_COURSE_KEY = "Spring"; + private static final String HIBERNATE_COURSE_KEY = "hibernate"; + private static final String GUAVA_COURSE_KEY = "Guava"; + + AtomicLongMap courses = AtomicLongMap.create(); + + public void setUp(){ + courses.put(SPRING_COURSE_KEY, 1056); + courses.put(HIBERNATE_COURSE_KEY, 259); + courses.put(GUAVA_COURSE_KEY, 78); + } + + +@Test +public void accumulateAndGet_withLongBinaryOperator_thenSuccessful(){ + long noOfStudents = 56; + long oldValue = courses.get(SPRING_COURSE_KEY); + + long totalNotesRequired = courses.accumulateAndGet( + "Guava", + noOfStudents, + (x,y) -> (x * y)); + + assertEquals(totalNotesRequired, oldValue * noOfStudents ); +} + + @Test + public void getAndAccumulate_withLongBinaryOperator_thenSuccessful(){ + long noOfStudents = 56; + long beforeUpdate = courses.get(SPRING_COURSE_KEY); + + long onUpdate = courses.accumulateAndGet("Guava", + noOfStudents, + (x,y) -> (x * y) + ); + + long afterUpdate = courses.get(SPRING_COURSE_KEY); + + assertEquals(onUpdate, afterUpdate); + assertEquals(afterUpdate, beforeUpdate * noOfStudents); + } + +@Test +public void updateAndGet_withLongUnaryOperator_thenSuccessful(){ + long beforeUpdate = courses.get(SPRING_COURSE_KEY); + + long onUpdate = courses.updateAndGet( + "Guava", + (x) -> (x/2)); + + long afterUpdate = courses.get(SPRING_COURSE_KEY); + + assertEquals(onUpdate, afterUpdate); + assertEquals(afterUpdate, beforeUpdate/2); +} +} diff --git a/guava21/src/test/java/MonitorUnitTests.java b/guava21/src/test/java/MonitorUnitTests.java new file mode 100644 index 0000000000..6427072db9 --- /dev/null +++ b/guava21/src/test/java/MonitorUnitTests.java @@ -0,0 +1,61 @@ +import com.google.common.util.concurrent.Monitor; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class MonitorUnitTests { + + @Test + public void whenGaurdConditionIsTrue_IsSuccessful() { + Monitor monitor = new Monitor(); + boolean enteredInCriticalSection = false; + + Monitor.Guard gaurdCondition = monitor.newGuard(this::returnTrue); + + if(monitor.enterIf(gaurdCondition)){ + try{ + System.out.println("Entered in critical section"); + enteredInCriticalSection = true; + }finally { + monitor.leave(); + } + } + + Assert.assertTrue(enteredInCriticalSection); + + } + + @Test + public void whenGaurdConditionIsFalse_IsSuccessful() { + Monitor monitor = new Monitor(); + boolean enteredInCriticalSection = false; + + Monitor.Guard gaurdCondition = monitor.newGuard(this::returnFalse); + + if(monitor.enterIf(gaurdCondition)){ + try{ + System.out.println("Entered in critical section"); + enteredInCriticalSection = true; + }finally { + monitor.leave(); + } + } + + Assert.assertFalse(enteredInCriticalSection); + } + + private boolean returnTrue(){ + return true; + } + + private boolean returnFalse(){ + return false; + } +} From 361df0769448c4c761a1fef9b5b088ed8de29ec9 Mon Sep 17 00:00:00 2001 From: Nancy Bosecker Date: Tue, 28 Mar 2017 18:18:00 -0700 Subject: [PATCH 103/149] Added KeyDeserializer class and test code (#1523) * Solr w Apache SolrJ * Solr w Apache SolrJ * updated test names and moved add to @before method * create apache-solrj module, moved code from spring-data-solr * More examples for indexing,delete,and query for solrj * More examples for indexing,delete,and query for solrj * Jackson Map Serialize/Deserialize * Jackson Map Serialize/Deserialize * Jackson version update * keydeserializer code added * keydeserializer code added --- .../jackson/entities/ClassWithAMap.java | 24 +++++++++++++++++++ .../serialization/MyPairDeserializer.java | 18 ++++++++++++++ .../JacksonMapDeserializeTest.java | 10 +++++++- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 jackson/src/main/java/com/baeldung/jackson/entities/ClassWithAMap.java create mode 100644 jackson/src/main/java/com/baeldung/jackson/serialization/MyPairDeserializer.java diff --git a/jackson/src/main/java/com/baeldung/jackson/entities/ClassWithAMap.java b/jackson/src/main/java/com/baeldung/jackson/entities/ClassWithAMap.java new file mode 100644 index 0000000000..54ebff8a56 --- /dev/null +++ b/jackson/src/main/java/com/baeldung/jackson/entities/ClassWithAMap.java @@ -0,0 +1,24 @@ +package com.baeldung.jackson.entities; + +import java.util.Map; + +import com.baeldung.jackson.serialization.MyPairDeserializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +public class ClassWithAMap { + + @JsonProperty("map") + @JsonDeserialize(keyUsing = MyPairDeserializer.class) + private final Map map; + + @JsonCreator + public ClassWithAMap(Map map) { + this.map = map; + } + + public Map getMap() { + return map; + } +} \ No newline at end of file diff --git a/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairDeserializer.java b/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairDeserializer.java new file mode 100644 index 0000000000..0aa6db98d0 --- /dev/null +++ b/jackson/src/main/java/com/baeldung/jackson/serialization/MyPairDeserializer.java @@ -0,0 +1,18 @@ +package com.baeldung.jackson.serialization; + +import java.io.IOException; + +import com.baeldung.jackson.entities.MyPair; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; + +public class MyPairDeserializer extends KeyDeserializer { + + @Override + public MyPair deserializeKey(String key, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + return new MyPair(key); + } +} \ No newline at end of file diff --git a/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java b/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java index 3be3981c9b..04eb89306b 100644 --- a/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java +++ b/jackson/src/test/java/com/baeldung/jackson/deserialization/JacksonMapDeserializeTest.java @@ -7,6 +7,7 @@ import java.util.Map; import org.junit.Assert; import org.junit.Test; +import com.baeldung.jackson.entities.ClassWithAMap; import com.baeldung.jackson.entities.MyPair; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; @@ -36,14 +37,21 @@ public class JacksonMapDeserializeTest { public void whenObjectStringMapDeserialize_thenCorrect() throws JsonParseException, JsonMappingException, IOException { - final String jsonInput = "{\"Abbott and Costello\" : \"Comedy\"}"; + final String jsonInput = "{\"Abbott and Costello\":\"Comedy\"}"; final ObjectMapper mapper = new ObjectMapper(); TypeReference> typeRef = new TypeReference>() { }; + map = mapper.readValue(jsonInput, typeRef); Assert.assertEquals("Comedy", map.get(new MyPair("Abbott", "Costello"))); + + ClassWithAMap classWithMap = mapper.readValue(jsonInput, + ClassWithAMap.class); + + Assert.assertEquals("Comedy", + classWithMap.getMap().get(new MyPair("Abbott", "Costello"))); } @Test From 1a7ccb824882d3df8c6353a4f4fc59ea791b7ff8 Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Wed, 29 Mar 2017 04:14:32 +0200 Subject: [PATCH 104/149] Bael 756 (#1513) * BAEL-756 code for kotlin null-safety article * BAEL-756 fix typo * BAEL-756 increment version of Kotlin * BAEL-756 remove duplicate form pom --- kotlin/pom.xml | 7 +- .../com/baeldung/kotlin/NullSafetyTest.kt | 140 ++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt diff --git a/kotlin/pom.xml b/kotlin/pom.xml index f928cc4037..07fb6863d4 100644 --- a/kotlin/pom.xml +++ b/kotlin/pom.xml @@ -90,10 +90,9 @@ 4.12 - 1.0.6 - 1.0.6 - 1.0.6 - 1.0.6 + 1.1.1 + 1.1.1 + 1.1.1 \ No newline at end of file diff --git a/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt b/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt new file mode 100644 index 0000000000..2adc1032bc --- /dev/null +++ b/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt @@ -0,0 +1,140 @@ +package com.baeldung.kotlin + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import kotlin.test.assertTrue + + +class NullSafetyTest { + @Test + fun givenNonNullableField_whenAssignValueToIt_thenNotNeedToCheckAgainstNull() { + //given + var a: String = "value" + //a = null compilation error + + //then + assertEquals(a.length, 5) + } + + @Test + fun givenNullableField_whenReadValue_thenNeedToCheckAgainstNull() { + //given + var b: String? = "value" + b = null + + //when + if (b != null) { + + } else { + assertNull(b) + } + } + + @Test + fun givenComplexObject_whenUseSafeCall_thenShouldChainCallsResultingWithValue() { + //given + val p: Person? = Person(Country("ENG")) + + //when + val res = p?.country?.code + + //then + assertEquals(res, "ENG") + } + + @Test + fun givenComplexObject_whenUseSafeCall_thenShouldChainCallsResultingWithNull() { + //given + val p: Person? = Person(Country(null)) + + //when + val res = p?.country?.code + + //then + assertNull(res) + } + + @Test + fun givenCollectionOfObjects_whenUseLetOperator_thenShouldApplyActionOnlyOnNonNullValue() { + //given + val firstName = "Tom" + val secondName = "Michael" + val names: List = listOf(firstName, null, secondName) + + //when + var res = listOf() + for (item in names) { + item?.let { res = res.plus(it) } + } + + //then + assertEquals(2, res.size) + assertTrue { res.contains(firstName) } + assertTrue { res.contains(secondName) } + } + + @Test + fun givenNullableReference_whenUseElvisOperator_thenShouldReturnValueIfReferenceIsNotNull() { + //given + val value: String? = "name" + + //when + val res = value?.length ?: -1 + + //then + assertEquals(res, 4) + } + + @Test + fun givenNullableReference_whenUseElvisOperator_thenShouldReturnDefaultValueIfReferenceIsNull() { + //given + val value: String? = null + + //when + val res = value?.length ?: -1 + + //then + assertEquals(res, -1) + } + + @Test + fun givenNullableField_whenUsingDoubleExclamationMarkOperatorOnNull_thenThrowNPE() { + //given + var b: String? = "value" + b = null + + //when + assertFailsWith { + b!!.length + } + } + + @Test + fun givenNullableField_whenUsingDoubleExclamationMarkOperatorOnNotNull_thenReturnValue() { + //given + val b: String? = "value" + + //then + assertEquals(b!!.length, 5) + } + + @Test + fun givenNullableList_whenUseFilterNotNullMethod_thenRemoveALlNullValues() { + //given + val list: List = listOf("a", null, "b") + + //when + val res = list.filterNotNull() + + //then + assertEquals(res.size, 2) + assertTrue { res.contains("a") } + assertTrue { res.contains("b") } + } +} + +data class Person(val country: Country?) + +data class Country(val code: String?) \ No newline at end of file From e6d9dde9317f0429cd2f39f4f930b7496dede2a9 Mon Sep 17 00:00:00 2001 From: Tian Baoqiang Date: Wed, 29 Mar 2017 17:43:21 +0800 Subject: [PATCH 105/149] add a main-class variable on maven building and exclude IntegrationTests (#1530) * add a main-class variable on maven building and exclude IntegrationTests * hardcode spring-boot main class to Spring5Application --- spring-5/pom.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spring-5/pom.xml b/spring-5/pom.xml index 6c52e6c8cc..f116ed73c0 100644 --- a/spring-5/pom.xml +++ b/spring-5/pom.xml @@ -70,6 +70,21 @@ org.springframework.boot spring-boot-maven-plugin + + com.baeldung.Spring5Application + JAR + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LiveTest.java + + From 6f75ad86a652d485776c8baae28c811f0481e4be Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Wed, 29 Mar 2017 13:41:37 +0200 Subject: [PATCH 106/149] Bael 756 (#1531) * BAEL-756 code for kotlin null-safety article * BAEL-756 fix typo * BAEL-756 increment version of Kotlin * BAEL-756 remove duplicate form pom * BAEL-756 add also and run example --- .../com/baeldung/kotlin/NullSafetyTest.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt b/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt index 2adc1032bc..0ecc74b6fb 100644 --- a/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt +++ b/kotlin/src/test/kotlin/com/baeldung/kotlin/NullSafetyTest.kt @@ -66,7 +66,27 @@ class NullSafetyTest { //when var res = listOf() for (item in names) { - item?.let { res = res.plus(it) } + item?.let { res = res.plus(it); it } + ?.also{it -> println("non nullable value: $it")} + } + + //then + assertEquals(2, res.size) + assertTrue { res.contains(firstName) } + assertTrue { res.contains(secondName) } + } + + @Test + fun fivenCollectionOfObject_whenUseRunOperator_thenExecuteActionOnNonNullValue(){ + //given + val firstName = "Tom" + val secondName = "Michael" + val names: List = listOf(firstName, null, secondName) + + //when + var res = listOf() + for (item in names) { + item?.run{res = res.plus(this)} } //then From b63ef448db503c66b11f99f65d6c6b9bc73a7454 Mon Sep 17 00:00:00 2001 From: Nancy Bosecker Date: Wed, 29 Mar 2017 08:42:38 -0700 Subject: [PATCH 107/149] removed explicit types from map instantiation (#1532) * Solr w Apache SolrJ * Solr w Apache SolrJ * updated test names and moved add to @before method * create apache-solrj module, moved code from spring-data-solr * More examples for indexing,delete,and query for solrj * More examples for indexing,delete,and query for solrj * Jackson Map Serialize/Deserialize * Jackson Map Serialize/Deserialize * Jackson version update * keydeserializer code added * keydeserializer code added * remove explicit types from map instantion --- .../jackson/serialization/JacksonMapSerializeTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java b/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java index 71ba698e8e..841b12ca03 100644 --- a/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java +++ b/jackson/src/test/java/com/baeldung/jackson/serialization/JacksonMapSerializeTest.java @@ -30,7 +30,7 @@ public class JacksonMapSerializeTest { public void whenSimpleMapSerialize_thenCorrect() throws JsonProcessingException { - Map map = new HashMap(); + Map map = new HashMap<>(); map.put("key", "value"); final ObjectMapper mapper = new ObjectMapper(); @@ -43,7 +43,7 @@ public class JacksonMapSerializeTest { public void whenCustomObjectStringMapSerialize_thenCorrect() throws JsonProcessingException { - map = new HashMap(); + map = new HashMap<>(); MyPair key = new MyPair("Abbott", "Costello"); map.put(key, "Comedy"); @@ -57,7 +57,7 @@ public class JacksonMapSerializeTest { public void whenCustomObjectObjectMapSerialize_thenCorrect() throws JsonProcessingException { - cmap = new HashMap(); + cmap = new HashMap<>(); mapKey = new MyPair("Abbott", "Costello"); mapValue = new MyPair("Comedy", "1940's"); cmap.put(mapKey, mapValue); From ea345fa246b290b7ee98fcc5e5016877b2bde7d2 Mon Sep 17 00:00:00 2001 From: gitterjim-I Date: Wed, 29 Mar 2017 22:18:20 +0100 Subject: [PATCH 108/149] Integrate forEach and stream changes to test, removing non-test class. (#1529) * article Bael-667 initial commit. * Switch to use logging framework for output. --- core-java/0.8260098203820962 | 0 .../FlattenNestedListTest.java | 82 +++++++++++-------- 2 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 core-java/0.8260098203820962 diff --git a/core-java/0.8260098203820962 b/core-java/0.8260098203820962 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java index cf9334954b..fdf4934cb7 100644 --- a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java +++ b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java @@ -1,6 +1,8 @@ package com.baeldung.list.flattennestedlist; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; @@ -8,45 +10,60 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; public class FlattenNestedListTest { + private List> lol = new ArrayList<>(); + List ls1 = Arrays.asList("one:one", "one:two", "one:three"); + List ls2 = Arrays.asList("two:one", "two:two", "two:three"); + List ls3 = Arrays.asList("three:one", "three:two", "three:three"); - @Test - public void givenListOfListOfString_flattenNestedList1() { - // given - List ls1 = Arrays.asList("one:one", "one:two", "one:three"); - List ls2 = Arrays.asList("two:one", "two:two", "two:three"); - List ls3 = Arrays.asList("three:one", "three:two", "three:three"); + @Before + public void setup() { + lol.addAll(Arrays.asList(ls1, ls2, ls3)); + } - List> list = Arrays.asList(ls1, ls2, ls3); - - // when - List ls = flattenListOfListsImperatively(list); - - // then - assertNotNull(ls); - assertTrue(ls.size() == 9); - //TODO content assertion + @After + public void tearDown() { + lol = null; } @Test - public void givenListOfListOfString_flattenNestedList2() { - // given - List ls1 = Arrays.asList("one:one", "one:two", "one:three"); - List ls2 = Arrays.asList("two:one", "two:two", "two:three"); - List ls3 = Arrays.asList("three:one", "three:two", "three:three"); + public void givenString_flattenNestedList1() { + List ls = flattenListOfListsImperatively(lol); - List> list = Arrays.asList(ls1, ls2, ls3); - - // when - List ls = flattenListOfListsStream(list); - - // then assertNotNull(ls); assertTrue(ls.size() == 9); - //TODO content assertion + // assert content + assertEquals(ls.get(0), "one:one"); + assertEquals(ls.get(1), "one:two"); + assertEquals(ls.get(2), "one:three"); + assertEquals(ls.get(3), "two:one"); + assertEquals(ls.get(4), "two:two"); + assertEquals(ls.get(5), "two:three"); + assertEquals(ls.get(6), "three:one"); + assertEquals(ls.get(7), "three:two"); + assertEquals(ls.get(8), "three:three"); + } + + @Test + public void givenString_flattenNestedList2() { + List ls = flattenListOfListsStream(lol); + + assertNotNull(ls); + assertTrue(ls.size() == 9); + // assert content + assertEquals(ls.get(0), "one:one"); + assertEquals(ls.get(1), "one:two"); + assertEquals(ls.get(2), "one:three"); + assertEquals(ls.get(3), "two:one"); + assertEquals(ls.get(4), "two:two"); + assertEquals(ls.get(5), "two:three"); + assertEquals(ls.get(6), "three:one"); + assertEquals(ls.get(7), "three:two"); + assertEquals(ls.get(8), "three:three"); } public List flattenListOfListsImperatively(List> list) { @@ -56,9 +73,6 @@ public class FlattenNestedListTest { } public List flattenListOfListsStream(List> list) { - return list.stream() - .flatMap(Collection::stream) - .collect(Collectors.toList()); + return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); } - } From 47dfe6b641733689028ce727837ebf834d8aacce Mon Sep 17 00:00:00 2001 From: "Eunice A. Obugyei" Date: Thu, 30 Mar 2017 07:28:32 +0000 Subject: [PATCH 109/149] Introduction to JAX-WS[http://jira.baeldung.com/browse/BAEL-611] (#1224) * Introduction to JAX-WS[http://jira.baeldung.com/browse/BAEL-611] * Introduction to JAX-WS[http://jira.baeldung.com/browse/BAEL-611] * Removed unnecessary comment * Added mockito-core dependency * Introduction to JAX-WS[http://jira.baeldung.com/browse/BAEL-611] Added Exception test cases * Applied baeldung formatter in Eclipse --- jee7/pom.xml | 1 + .../com/baeldung/jaxws/EmployeeService.java | 31 +++++ .../baeldung/jaxws/EmployeeServiceImpl.java | 48 ++++++++ .../config/EmployeeServicePublisher.java | 12 ++ .../exception/EmployeeAlreadyExists.java | 16 +++ .../jaxws/exception/EmployeeNotFound.java | 17 +++ .../com/baeldung/jaxws/model/Employee.java | 33 ++++++ .../jaxws/repository/EmployeeRepository.java | 22 ++++ .../repository/EmployeeRepositoryImpl.java | 68 +++++++++++ .../jaxws/EmployeeServiceLiveTest.java | 108 ++++++++++++++++++ 10 files changed, 356 insertions(+) create mode 100644 jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeNotFound.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/model/Employee.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java create mode 100644 jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java create mode 100644 jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java diff --git a/jee7/pom.xml b/jee7/pom.xml index f275f56d58..3d5d7ccdc4 100644 --- a/jee7/pom.xml +++ b/jee7/pom.xml @@ -28,6 +28,7 @@ 3.6.0 2.6 + 1.10.19 diff --git a/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java b/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java new file mode 100644 index 0000000000..7544369992 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java @@ -0,0 +1,31 @@ +package com.baeldung.jaxws; + +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; + +import javax.jws.WebMethod; +import javax.jws.WebService; +import java.util.List; + +@WebService +public interface EmployeeService { + + @WebMethod + Employee getEmployee(int id) throws EmployeeNotFound; + + @WebMethod + Employee updateEmployee(int id, String name) throws EmployeeNotFound; + + @WebMethod + boolean deleteEmployee(int id) throws EmployeeNotFound; + + @WebMethod + Employee addEmployee(int id, String name) throws EmployeeAlreadyExists; + + @WebMethod + int countEmployees(); + + @WebMethod + List getAllEmployees(); +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java b/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java new file mode 100644 index 0000000000..35b84fe620 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java @@ -0,0 +1,48 @@ +package com.baeldung.jaxws; + +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; +import com.baeldung.jaxws.repository.EmployeeRepository; + +import javax.inject.Inject; +import javax.jws.WebMethod; +import javax.jws.WebService; +import java.util.List; + +@WebService(serviceName = "EmployeeService", endpointInterface = "com.baeldung.jaxws.EmployeeService") +public class EmployeeServiceImpl implements EmployeeService { + + @Inject + private EmployeeRepository employeeRepositoryImpl; + + @WebMethod + public Employee getEmployee(int id) throws EmployeeNotFound { + return employeeRepositoryImpl.getEmployee(id); + } + + @WebMethod + public Employee updateEmployee(int id, String name) throws EmployeeNotFound { + return employeeRepositoryImpl.updateEmployee(id, name); + } + + @WebMethod + public boolean deleteEmployee(int id) throws EmployeeNotFound { + return employeeRepositoryImpl.deleteEmployee(id); + } + + @WebMethod + public Employee addEmployee(int id, String name) throws EmployeeAlreadyExists { + return employeeRepositoryImpl.addEmployee(id, name); + } + + @WebMethod + public int countEmployees() { + return employeeRepositoryImpl.count(); + } + + @WebMethod + public List getAllEmployees() { + return employeeRepositoryImpl.getAllEmployees(); + } +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java b/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java new file mode 100644 index 0000000000..6d7acc88e0 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java @@ -0,0 +1,12 @@ +package com.baeldung.jaxws.config; + +import com.baeldung.jaxws.EmployeeServiceImpl; + +import javax.xml.ws.Endpoint; + +public class EmployeeServicePublisher { + + public static void main(String[] args) { + Endpoint.publish("http://localhost:8080/employeeservice", new EmployeeServiceImpl()); + } +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java new file mode 100644 index 0000000000..d7c9d0f2cc --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java @@ -0,0 +1,16 @@ +package com.baeldung.jaxws.exception; + +import javax.xml.ws.WebFault; +import java.io.Serializable; + +@WebFault +public class EmployeeAlreadyExists extends Exception implements Serializable { + + public EmployeeAlreadyExists() { + super("This employee already exist"); + } + + public EmployeeAlreadyExists(String str) { + super(str); + } +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeNotFound.java b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeNotFound.java new file mode 100644 index 0000000000..667e3e0c72 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeNotFound.java @@ -0,0 +1,17 @@ +package com.baeldung.jaxws.exception; + +import javax.xml.ws.WebFault; +import java.io.Serializable; + +@WebFault +public class EmployeeNotFound extends Exception implements Serializable { + + public EmployeeNotFound() { + super("The specified employee does not exist"); + } + + public EmployeeNotFound(String str) { + super(str); + } + +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java b/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java new file mode 100644 index 0000000000..27d02354c0 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java @@ -0,0 +1,33 @@ +package com.baeldung.jaxws.model; + +import java.io.Serializable; + +public class Employee implements Serializable { + private int id; + private String firstName; + + public Employee() { + + } + + public Employee(int id, String firstName) { + this.id = id; + this.firstName = firstName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java new file mode 100644 index 0000000000..3a8930ac04 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java @@ -0,0 +1,22 @@ +package com.baeldung.jaxws.repository; + +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; + +import java.util.List; + +public interface EmployeeRepository { + + List getAllEmployees(); + + Employee getEmployee(int id) throws EmployeeNotFound; + + Employee updateEmployee(int id, String name) throws EmployeeNotFound; + + boolean deleteEmployee(int id) throws EmployeeNotFound; + + Employee addEmployee(int id, String name) throws EmployeeAlreadyExists; + + int count(); +} diff --git a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java new file mode 100644 index 0000000000..0e728ae253 --- /dev/null +++ b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java @@ -0,0 +1,68 @@ +package com.baeldung.jaxws.repository; + +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; + +import java.util.ArrayList; +import java.util.List; + +public class EmployeeRepositoryImpl implements EmployeeRepository { + private List employeeList; + + public EmployeeRepositoryImpl() { + employeeList = new ArrayList<>(); + employeeList.add(new Employee(1, "Jane")); + employeeList.add(new Employee(2, "Jack")); + employeeList.add(new Employee(3, "George")); + } + + public List getAllEmployees() { + return employeeList; + } + + public Employee getEmployee(int id) throws EmployeeNotFound { + for (Employee emp : employeeList) { + if (emp.getId() == id) { + return emp; + } + } + throw new EmployeeNotFound(); + } + + public Employee updateEmployee(int id, String name) throws EmployeeNotFound { + for (Employee employee1 : employeeList) { + if (employee1.getId() == id) { + employee1.setId(id); + employee1.setFirstName(name); + return employee1; + } + } + throw new EmployeeNotFound(); + } + + public boolean deleteEmployee(int id) throws EmployeeNotFound { + for (Employee emp : employeeList) { + if (emp.getId() == id) { + employeeList.remove(emp); + return true; + } + } + throw new EmployeeNotFound(); + } + + public Employee addEmployee(int id, String name) throws EmployeeAlreadyExists { + for (Employee emp : employeeList) { + if (emp.getId() == id) { + throw new EmployeeAlreadyExists(); + } + } + Employee employee = new Employee(id, name); + employeeList.add(employee); + return employee; + } + + public int count() { + return employeeList.size(); + } +} diff --git a/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java b/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java new file mode 100644 index 0000000000..8907179b14 --- /dev/null +++ b/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java @@ -0,0 +1,108 @@ +package com.baeldung.jaxws; + +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; +import com.baeldung.jaxws.repository.EmployeeRepository; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +@RunWith(Arquillian.class) +public class EmployeeServiceLiveTest { + + private static final String APP_NAME = "jee7"; + private static final String WSDL_PATH = "EmployeeService?wsdl"; + private static QName SERVICE_NAME = new QName("http://jaxws.baeldung.com/", "EmployeeService"); + private static URL wsdlUrl; + + @ArquillianResource + private URL deploymentUrl; + + private EmployeeService employeeServiceProxy; + + @Deployment(testable = false) + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, APP_NAME + ".war").addPackage(EmployeeService.class.getPackage()).addPackage(Employee.class.getPackage()).addPackage(EmployeeNotFound.class.getPackage()).addPackage(EmployeeRepository.class.getPackage()) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + } + + @Before + public void setUp() { + try { + wsdlUrl = new URL(deploymentUrl, WSDL_PATH); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + Service service = Service.create(wsdlUrl, SERVICE_NAME); + employeeServiceProxy = service.getPort(EmployeeService.class); + } + + @Test + public void givenGetAllEmployees_thenCorrectNumberOfEmployeesReturned() { + int employeeCount = employeeServiceProxy.countEmployees(); + List employeeList = employeeServiceProxy.getAllEmployees(); + assertEquals(employeeList.size(), employeeCount); + } + + @Test + public void givenEmployeeId_whenEmployeeExists_thenCorrectEmployeeReturned() throws EmployeeNotFound { + Employee employee = employeeServiceProxy.getEmployee(2); + assertEquals(employee.getFirstName(), "Jack"); + } + + @Test(expected = EmployeeNotFound.class) + public void givenEmployeeId_whenEmployeeNotExists_thenEmployeeNotFoundExceptionReturned() throws EmployeeNotFound { + employeeServiceProxy.getEmployee(20); + } + + @Test + public void givenAddEmployee_whenEmployeeDoesntAlreadyExist_thenEmployeeCountIncreased() throws EmployeeAlreadyExists { + int employeeCount = employeeServiceProxy.countEmployees(); + employeeServiceProxy.addEmployee(4, "Anna"); + assertEquals(employeeServiceProxy.countEmployees(), employeeCount + 1); + } + + @Test(expected = EmployeeAlreadyExists.class) + public void givenAddEmployee_whenEmployeeAlreadyExist_thenEmployeeAlreadyExistsExceptionReturned() throws EmployeeAlreadyExists { + employeeServiceProxy.addEmployee(1, "Anna"); + } + + @Test + public void givenUpdateEmployee_whenEmployeeExists_thenUpdatedEmployeeReturned() throws EmployeeNotFound { + Employee updated = employeeServiceProxy.updateEmployee(1, "Joan"); + assertEquals(updated.getFirstName(), "Joan"); + } + + @Test(expected = EmployeeNotFound.class) + public void givenUpdateEmployee_whenEmployeeNotExists_thenUpdatedEmployeeReturned() throws EmployeeNotFound { + employeeServiceProxy.updateEmployee(20, "Joan"); + } + + @Test + public void givenDeleteEmployee_whenEmployeeExists_thenCorrectStatusReturned() throws EmployeeNotFound { + boolean deleteEmployee = employeeServiceProxy.deleteEmployee(3); + assertEquals(deleteEmployee, true); + } + + @Test(expected = EmployeeNotFound.class) + public void givenDeleteEmployee_whenEmployeeNotExists_thenEmployeeNotFoundExceptionReturned() throws EmployeeNotFound { + employeeServiceProxy.deleteEmployee(20); + } + +} From ec089be605ffcc7e699ef9d35630a824fd436c1e Mon Sep 17 00:00:00 2001 From: Sunil Mogadati Date: Thu, 30 Mar 2017 02:54:26 -0600 Subject: [PATCH 110/149] BAEL-611: Minor formatting changes (#1534) * Add NDC and JBoss Logging to the demo application * NDC for Log4j, Log4j2 and JBoss Logging * Simplify NDC example by making it a single operation instead of two * Make NDC example as RestController, Use JBoss Logging only as a logging bridge * Fix merge conflicts in pull request - log-mdc pom.xml updated * BAEL-445 Update to Spring security SpEL example * BAEL-445: Change tabs to spaces in the updated code * BAEL-245: Add Enum Serialization exmaple * BAEL-245: Remove the folder jackson/src/test/java/com/baeldung/jackson/dtos/withEnum as the example is not used anymore * Add more enum serialization examples to align with previous example and prevent build fail * BAEL-611: Minor formatting changes * BAEL-611: Update Test case method names --- jee7/pom.xml | 1 - .../com/baeldung/jaxws/EmployeeService.java | 9 ++-- .../baeldung/jaxws/EmployeeServiceImpl.java | 11 ++--- .../config/EmployeeServicePublisher.java | 4 +- .../exception/EmployeeAlreadyExists.java | 7 +++- .../com/baeldung/jaxws/model/Employee.java | 1 + .../jaxws/repository/EmployeeRepository.java | 4 +- .../repository/EmployeeRepositoryImpl.java | 6 +-- .../jaxws/EmployeeServiceLiveTest.java | 42 ++++++++++--------- 9 files changed, 46 insertions(+), 39 deletions(-) diff --git a/jee7/pom.xml b/jee7/pom.xml index 3d5d7ccdc4..f275f56d58 100644 --- a/jee7/pom.xml +++ b/jee7/pom.xml @@ -28,7 +28,6 @@ 3.6.0 2.6 - 1.10.19 diff --git a/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java b/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java index 7544369992..9735607da6 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java +++ b/jee7/src/main/java/com/baeldung/jaxws/EmployeeService.java @@ -1,13 +1,14 @@ package com.baeldung.jaxws; +import java.util.List; + +import javax.jws.WebMethod; +import javax.jws.WebService; + import com.baeldung.jaxws.exception.EmployeeAlreadyExists; import com.baeldung.jaxws.exception.EmployeeNotFound; import com.baeldung.jaxws.model.Employee; -import javax.jws.WebMethod; -import javax.jws.WebService; -import java.util.List; - @WebService public interface EmployeeService { diff --git a/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java b/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java index 35b84fe620..c1c9cd4385 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java +++ b/jee7/src/main/java/com/baeldung/jaxws/EmployeeServiceImpl.java @@ -1,15 +1,16 @@ package com.baeldung.jaxws; +import java.util.List; + +import javax.inject.Inject; +import javax.jws.WebMethod; +import javax.jws.WebService; + import com.baeldung.jaxws.exception.EmployeeAlreadyExists; import com.baeldung.jaxws.exception.EmployeeNotFound; import com.baeldung.jaxws.model.Employee; import com.baeldung.jaxws.repository.EmployeeRepository; -import javax.inject.Inject; -import javax.jws.WebMethod; -import javax.jws.WebService; -import java.util.List; - @WebService(serviceName = "EmployeeService", endpointInterface = "com.baeldung.jaxws.EmployeeService") public class EmployeeServiceImpl implements EmployeeService { diff --git a/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java b/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java index 6d7acc88e0..ac3b049320 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java +++ b/jee7/src/main/java/com/baeldung/jaxws/config/EmployeeServicePublisher.java @@ -1,9 +1,9 @@ package com.baeldung.jaxws.config; -import com.baeldung.jaxws.EmployeeServiceImpl; - import javax.xml.ws.Endpoint; +import com.baeldung.jaxws.EmployeeServiceImpl; + public class EmployeeServicePublisher { public static void main(String[] args) { diff --git a/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java index d7c9d0f2cc..4842fbf84c 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java +++ b/jee7/src/main/java/com/baeldung/jaxws/exception/EmployeeAlreadyExists.java @@ -1,13 +1,16 @@ package com.baeldung.jaxws.exception; -import javax.xml.ws.WebFault; import java.io.Serializable; +import javax.xml.ws.WebFault; + @WebFault public class EmployeeAlreadyExists extends Exception implements Serializable { + private static final long serialVersionUID = 1L; + public EmployeeAlreadyExists() { - super("This employee already exist"); + super("This employee already exists"); } public EmployeeAlreadyExists(String str) { diff --git a/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java b/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java index 27d02354c0..5b1673c1e4 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java +++ b/jee7/src/main/java/com/baeldung/jaxws/model/Employee.java @@ -3,6 +3,7 @@ package com.baeldung.jaxws.model; import java.io.Serializable; public class Employee implements Serializable { + private static final long serialVersionUID = 1L; private int id; private String firstName; diff --git a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java index 3a8930ac04..0d5dec0462 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java +++ b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepository.java @@ -1,11 +1,11 @@ package com.baeldung.jaxws.repository; +import java.util.List; + import com.baeldung.jaxws.exception.EmployeeAlreadyExists; import com.baeldung.jaxws.exception.EmployeeNotFound; import com.baeldung.jaxws.model.Employee; -import java.util.List; - public interface EmployeeRepository { List getAllEmployees(); diff --git a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java index 0e728ae253..f67509fff5 100644 --- a/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java +++ b/jee7/src/main/java/com/baeldung/jaxws/repository/EmployeeRepositoryImpl.java @@ -1,12 +1,12 @@ package com.baeldung.jaxws.repository; +import java.util.ArrayList; +import java.util.List; + import com.baeldung.jaxws.exception.EmployeeAlreadyExists; import com.baeldung.jaxws.exception.EmployeeNotFound; import com.baeldung.jaxws.model.Employee; -import java.util.ArrayList; -import java.util.List; - public class EmployeeRepositoryImpl implements EmployeeRepository { private List employeeList; diff --git a/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java b/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java index 8907179b14..5311b3c5fe 100644 --- a/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java +++ b/jee7/src/test/java/com/baeldung/jaxws/EmployeeServiceLiveTest.java @@ -1,9 +1,14 @@ package com.baeldung.jaxws; -import com.baeldung.jaxws.exception.EmployeeAlreadyExists; -import com.baeldung.jaxws.exception.EmployeeNotFound; -import com.baeldung.jaxws.model.Employee; -import com.baeldung.jaxws.repository.EmployeeRepository; +import static org.junit.Assert.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.arquillian.test.api.ArquillianResource; @@ -14,13 +19,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import javax.xml.namespace.QName; -import javax.xml.ws.Service; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.List; - -import static org.junit.Assert.assertEquals; +import com.baeldung.jaxws.exception.EmployeeAlreadyExists; +import com.baeldung.jaxws.exception.EmployeeNotFound; +import com.baeldung.jaxws.model.Employee; +import com.baeldung.jaxws.repository.EmployeeRepository; @RunWith(Arquillian.class) public class EmployeeServiceLiveTest { @@ -54,54 +56,54 @@ public class EmployeeServiceLiveTest { } @Test - public void givenGetAllEmployees_thenCorrectNumberOfEmployeesReturned() { + public void givenEmployees_whenGetCount_thenCorrectNumberOfEmployeesReturned() { int employeeCount = employeeServiceProxy.countEmployees(); List employeeList = employeeServiceProxy.getAllEmployees(); assertEquals(employeeList.size(), employeeCount); } @Test - public void givenEmployeeId_whenEmployeeExists_thenCorrectEmployeeReturned() throws EmployeeNotFound { + public void givenEmployees_whenGetAvailableEmployee_thenCorrectEmployeeReturned() throws EmployeeNotFound { Employee employee = employeeServiceProxy.getEmployee(2); assertEquals(employee.getFirstName(), "Jack"); } @Test(expected = EmployeeNotFound.class) - public void givenEmployeeId_whenEmployeeNotExists_thenEmployeeNotFoundExceptionReturned() throws EmployeeNotFound { + public void givenEmployees_whenGetNonAvailableEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound { employeeServiceProxy.getEmployee(20); } @Test - public void givenAddEmployee_whenEmployeeDoesntAlreadyExist_thenEmployeeCountIncreased() throws EmployeeAlreadyExists { + public void givenEmployees_whenAddNewEmployee_thenEmployeeCountIncreased() throws EmployeeAlreadyExists { int employeeCount = employeeServiceProxy.countEmployees(); employeeServiceProxy.addEmployee(4, "Anna"); assertEquals(employeeServiceProxy.countEmployees(), employeeCount + 1); } @Test(expected = EmployeeAlreadyExists.class) - public void givenAddEmployee_whenEmployeeAlreadyExist_thenEmployeeAlreadyExistsExceptionReturned() throws EmployeeAlreadyExists { + public void givenEmployees_whenAddAlreadyExistingEmployee_thenEmployeeAlreadyExistsException() throws EmployeeAlreadyExists { employeeServiceProxy.addEmployee(1, "Anna"); } @Test - public void givenUpdateEmployee_whenEmployeeExists_thenUpdatedEmployeeReturned() throws EmployeeNotFound { + public void givenEmployees_whenUpdateExistingEmployee_thenUpdatedEmployeeReturned() throws EmployeeNotFound { Employee updated = employeeServiceProxy.updateEmployee(1, "Joan"); assertEquals(updated.getFirstName(), "Joan"); } @Test(expected = EmployeeNotFound.class) - public void givenUpdateEmployee_whenEmployeeNotExists_thenUpdatedEmployeeReturned() throws EmployeeNotFound { + public void givenEmployees_whenUpdateNonExistingEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound { employeeServiceProxy.updateEmployee(20, "Joan"); } @Test - public void givenDeleteEmployee_whenEmployeeExists_thenCorrectStatusReturned() throws EmployeeNotFound { + public void givenEmployees_whenDeleteExistingEmployee_thenSuccessReturned() throws EmployeeNotFound { boolean deleteEmployee = employeeServiceProxy.deleteEmployee(3); assertEquals(deleteEmployee, true); } @Test(expected = EmployeeNotFound.class) - public void givenDeleteEmployee_whenEmployeeNotExists_thenEmployeeNotFoundExceptionReturned() throws EmployeeNotFound { + public void givenEmployee_whenDeleteNonExistingEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound { employeeServiceProxy.deleteEmployee(20); } From e28dfe6a4a7fa86535ec9af97bd4d6d17c4e9d7f Mon Sep 17 00:00:00 2001 From: Doha2012 Date: Thu, 30 Mar 2017 13:52:40 +0200 Subject: [PATCH 111/149] modify ratings controller (#1536) * upgrade to spring boot 1.5.2 * add full update to REST API * modify ratings controller --- .../bootstrap/svcrating/rating/RatingController.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java index cbfeda49c0..91966034f6 100644 --- a/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java +++ b/spring-cloud/spring-cloud-bootstrap/svc-rating/src/main/java/com/baeldung/spring/cloud/bootstrap/svcrating/rating/RatingController.java @@ -2,6 +2,7 @@ package com.baeldung.spring.cloud.bootstrap.svcrating.rating; import java.util.List; import java.util.Map; +import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; @@ -23,11 +24,9 @@ public class RatingController { private RatingService ratingService; @GetMapping - public List findRatingsByBookId(@RequestParam(required = false, defaultValue = "0") Long bookId) { - if (bookId.equals(0L)) { - return ratingService.findAllRatings(); - } - return ratingService.findRatingsByBookId(bookId); + public List findRatingsByBookId(@RequestParam(required = false) Optional bookId) { + return bookId.map(ratingService::findRatingsByBookId) + .orElseGet(ratingService::findAllRatings); } @PostMapping From ce589cc7b5b78edd80ff3b818ed53dd2af5bb98c Mon Sep 17 00:00:00 2001 From: Devendra Tiwari Date: Thu, 30 Mar 2017 23:51:17 +0530 Subject: [PATCH 112/149] Corrected Indentation using formatter. (#1541) --- .../tutorial/AtomicLongMapTutorials.java | 15 ++-- .../guava/tutorial/ComparatorsExamples.java | 6 +- .../guava/tutorial/ConcatStreams.java | 4 +- .../tutorial/InternerBuilderExample.java | 12 +-- .../guava/tutorial/MonitorExample.java | 4 +- .../guava/tutorial/MoreCollectorsExample.java | 7 +- .../guava/tutorial/StreamsUtility.java | 13 ++-- guava21/src/test/java/AtomicLongMapTests.java | 46 +++++------- .../src/test/java/ComparatorsUnitTests.java | 15 ++-- guava21/src/test/java/GauavaStreamsTests.java | 75 ++++++++++--------- .../src/test/java/InternBuilderUnitTests.java | 9 +-- guava21/src/test/java/MonitorUnitTests.java | 24 ++---- .../test/java/MoreCollectorsUnitTests.java | 24 +++--- guava21/src/test/java/StreamUtility.java | 10 +-- 14 files changed, 119 insertions(+), 145 deletions(-) diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java b/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java index 69ad04ad9c..79ce02b7f0 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/AtomicLongMapTutorials.java @@ -6,19 +6,18 @@ public class AtomicLongMapTutorials { private AtomicLongMap atomicLongMap; - public AtomicLongMapTutorials(){ + public AtomicLongMapTutorials() { atomicLongMap = AtomicLongMap.create(); } - - public void addKeys(){ - atomicLongMap.addAndGet("apple",250); - atomicLongMap.addAndGet("bat",350); - atomicLongMap.addAndGet("cat",450); - atomicLongMap.addAndGet("dog",550); + public void addKeys() { + atomicLongMap.addAndGet("apple", 250); + atomicLongMap.addAndGet("bat", 350); + atomicLongMap.addAndGet("cat", 450); + atomicLongMap.addAndGet("dog", 550); } - public static void main(String[] args){ + public static void main(String[] args) { AtomicLongMapTutorials atomicLongMapTutorials = new AtomicLongMapTutorials(); atomicLongMapTutorials.addKeys(); diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java b/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java index 6eb5c7f5ba..abb4c51e8f 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/ComparatorsExamples.java @@ -8,12 +8,10 @@ import java.util.List; public class ComparatorsExamples { - public static void main(String[] args){ + public static void main(String[] args) { - List integers = Arrays.asList(1,2,3,4,4,6,7,8,9,10); - //This will return true + List integers = Arrays.asList(1, 2, 3, 4, 4, 6, 7, 8, 9, 10); boolean isInAscendingOrder = Comparators.isInOrder(integers, new AscedingOrderComparator()); - System.out.println(isInAscendingOrder); } diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java b/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java index ab95b85751..0304d48fbc 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/ConcatStreams.java @@ -5,7 +5,7 @@ import com.google.common.collect.Streams; import java.util.stream.Stream; public class ConcatStreams { - public static Stream concatStreams(Stream stream1, Stream stream2, Stream stream3){ - return Streams.concat(stream1,stream2,stream3); + public static Stream concatStreams(Stream stream1, Stream stream2, Stream stream3) { + return Streams.concat(stream1, stream2, stream3); } } diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java index 6b935ba2a8..5c85e684d5 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/InternerBuilderExample.java @@ -3,16 +3,12 @@ package com.baeldung.guava.tutorial; import com.google.common.collect.Interner; import com.google.common.collect.Interners; -import static com.google.common.collect.Interners.newBuilder; - public class InternerBuilderExample { - public static void main(String[] args){ - Interner interners = Interners.newBuilder() - .concurrencyLevel(2) - .strong() - .build(); - + public static void main(String[] args) { + Interner interners = Interners. newBuilder() + .concurrencyLevel(2) + .strong(). build(); } diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java index 2f316c293e..78bcbe3d49 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/MonitorExample.java @@ -4,7 +4,6 @@ import com.google.common.util.concurrent.Monitor; import java.util.ArrayList; import java.util.List; -import java.util.function.BooleanSupplier; public class MonitorExample { private List students = new ArrayList(); @@ -12,7 +11,6 @@ public class MonitorExample { private Monitor monitor = new Monitor(); - public void addToCourse(String item) throws InterruptedException { Monitor.Guard studentsBelowCapacity = monitor.newGuard(this::isStudentsCapacityUptoLimit); monitor.enterWhen(studentsBelowCapacity); @@ -23,7 +21,7 @@ public class MonitorExample { } } - public Boolean isStudentsCapacityUptoLimit(){ + public Boolean isStudentsCapacityUptoLimit() { return students.size() > MAX_SIZE; } } diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java b/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java index 6cf4b6b0ac..2b3bd7cdc4 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/MoreCollectorsExample.java @@ -10,8 +10,9 @@ public class MoreCollectorsExample { public static void main(String[] args) { List numbers = Arrays.asList(1); - Optional number = numbers.stream() - .map(e -> e * 2) - .collect(MoreCollectors.toOptional()); + Optional number = numbers + .stream() + .map(e -> e * 2) + .collect(MoreCollectors.toOptional()); } } diff --git a/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java b/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java index 4ec3b44ef4..b15f61afd5 100644 --- a/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java +++ b/guava21/src/main/java/com/baeldung/guava/tutorial/StreamsUtility.java @@ -10,9 +10,9 @@ import java.util.stream.Stream; public class StreamsUtility { - public static void main(String[] args){ + public static void main(String[] args) { - List numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,11,12,13,14,15,16,17,18,19,20); + List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); //Using Collection Stream streamFromCollection = Streams.stream(numbers); //Using Iterator @@ -28,15 +28,12 @@ public class StreamsUtility { //Using OptionalDouble to DoubleStream DoubleStream streamFromOptionalDouble = Streams.stream(OptionalDouble.of(1.0)); - Stream concatenatedStreams = Streams.concat(streamFromCollection,streamFromIterable,streamFromIterator); + Stream concatenatedStreams = Streams.concat(streamFromCollection, streamFromIterable, streamFromIterator); - List integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10); + List integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); //This will return 10 Optional lastItem = Streams.findLast(integers.stream()); - Streams.zip( - Stream.of("candy", "chocolate", "bar"), - Stream.of("$1", "$2","$3"), - (arg1, arg2) -> arg1 + ":" + arg2); + Streams.zip(Stream.of("candy", "chocolate", "bar"), Stream.of("$1", "$2", "$3"), (arg1, arg2) -> arg1 + ":" + arg2); } } diff --git a/guava21/src/test/java/AtomicLongMapTests.java b/guava21/src/test/java/AtomicLongMapTests.java index aad72907de..6bde997e8d 100644 --- a/guava21/src/test/java/AtomicLongMapTests.java +++ b/guava21/src/test/java/AtomicLongMapTests.java @@ -1,5 +1,4 @@ import com.google.common.util.concurrent.AtomicLongMap; -import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -12,35 +11,28 @@ public class AtomicLongMapTests { AtomicLongMap courses = AtomicLongMap.create(); - public void setUp(){ + public void setUp() { courses.put(SPRING_COURSE_KEY, 1056); courses.put(HIBERNATE_COURSE_KEY, 259); courses.put(GUAVA_COURSE_KEY, 78); } + @Test + public void accumulateAndGet_withLongBinaryOperator_thenSuccessful() { + long noOfStudents = 56; + long oldValue = courses.get(SPRING_COURSE_KEY); -@Test -public void accumulateAndGet_withLongBinaryOperator_thenSuccessful(){ - long noOfStudents = 56; - long oldValue = courses.get(SPRING_COURSE_KEY); + long totalNotesRequired = courses.accumulateAndGet("Guava", noOfStudents, (x, y) -> (x * y)); - long totalNotesRequired = courses.accumulateAndGet( - "Guava", - noOfStudents, - (x,y) -> (x * y)); - - assertEquals(totalNotesRequired, oldValue * noOfStudents ); -} + assertEquals(totalNotesRequired, oldValue * noOfStudents); + } @Test - public void getAndAccumulate_withLongBinaryOperator_thenSuccessful(){ + public void getAndAccumulate_withLongBinaryOperator_thenSuccessful() { long noOfStudents = 56; long beforeUpdate = courses.get(SPRING_COURSE_KEY); - long onUpdate = courses.accumulateAndGet("Guava", - noOfStudents, - (x,y) -> (x * y) - ); + long onUpdate = courses.accumulateAndGet("Guava", noOfStudents, (x, y) -> (x * y)); long afterUpdate = courses.get(SPRING_COURSE_KEY); @@ -48,17 +40,15 @@ public void accumulateAndGet_withLongBinaryOperator_thenSuccessful(){ assertEquals(afterUpdate, beforeUpdate * noOfStudents); } -@Test -public void updateAndGet_withLongUnaryOperator_thenSuccessful(){ - long beforeUpdate = courses.get(SPRING_COURSE_KEY); + @Test + public void updateAndGet_withLongUnaryOperator_thenSuccessful() { + long beforeUpdate = courses.get(SPRING_COURSE_KEY); - long onUpdate = courses.updateAndGet( - "Guava", - (x) -> (x/2)); + long onUpdate = courses.updateAndGet("Guava", (x) -> (x / 2)); - long afterUpdate = courses.get(SPRING_COURSE_KEY); + long afterUpdate = courses.get(SPRING_COURSE_KEY); - assertEquals(onUpdate, afterUpdate); - assertEquals(afterUpdate, beforeUpdate/2); -} + assertEquals(onUpdate, afterUpdate); + assertEquals(afterUpdate, beforeUpdate / 2); + } } diff --git a/guava21/src/test/java/ComparatorsUnitTests.java b/guava21/src/test/java/ComparatorsUnitTests.java index f196c41a1b..8aaae1e14e 100644 --- a/guava21/src/test/java/ComparatorsUnitTests.java +++ b/guava21/src/test/java/ComparatorsUnitTests.java @@ -2,7 +2,9 @@ import com.google.common.collect.Comparators; import org.junit.Assert; import org.junit.Test; -import java.util.*; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; import java.util.function.Function; import java.util.function.ToDoubleFunction; import java.util.function.ToIntFunction; @@ -11,9 +13,9 @@ import java.util.function.ToLongFunction; public class ComparatorsUnitTests { @Test - public void isInOrderTest(){ + public void isInOrderTest() { - List numbers = Arrays.asList(1,2,3,4,4,6,7,8,9,10); + List numbers = Arrays.asList(1, 2, 3, 4, 4, 6, 7, 8, 9, 10); boolean isInAscendingOrder = Comparators.isInOrder(numbers, new AscendingOrderComparator()); @@ -21,17 +23,16 @@ public class ComparatorsUnitTests { } @Test - public void isInStrictOrderTest(){ + public void isInStrictOrderTest() { - List numbers = Arrays.asList(1,2,3,4,3,6,7,8,9,10); + List numbers = Arrays.asList(1, 2, 3, 4, 3, 6, 7, 8, 9, 10); boolean isInAscendingOrder = Comparators.isInOrder(numbers, new AscendingOrderComparator()); Assert.assertFalse(isInAscendingOrder); } - - private class AscendingOrderComparator implements Comparator{ + private class AscendingOrderComparator implements Comparator { @Override public int compare(Integer o1, Integer o2) { diff --git a/guava21/src/test/java/GauavaStreamsTests.java b/guava21/src/test/java/GauavaStreamsTests.java index 09e3e29b47..1482d685cf 100644 --- a/guava21/src/test/java/GauavaStreamsTests.java +++ b/guava21/src/test/java/GauavaStreamsTests.java @@ -14,13 +14,12 @@ public class GauavaStreamsTests { List numbers; @Before - public void setUp(){ - numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,11,12,13,14,15,16,17,18,19,20); + public void setUp() { + numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); } - @Test - public void createStreamsWithCollection(){ + public void createStreamsWithCollection() { //Deprecated API to create stream from collection Stream streamFromCollection = Streams.stream(numbers); @@ -29,7 +28,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithIterable(){ + public void createStreamsWithIterable() { Iterable numbersIterable = (Iterable) numbers; Stream streamFromIterable = Streams.stream(numbersIterable); @@ -39,7 +38,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithIterator(){ + public void createStreamsWithIterator() { Iterator numbersIterator = numbers.iterator(); Stream streamFromIterator = Streams.stream(numbersIterator); @@ -49,7 +48,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithOptional(){ + public void createStreamsWithOptional() { Stream streamFromOptional = Streams.stream(Optional.of(1)); @@ -58,7 +57,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithOptionalLong(){ + public void createStreamsWithOptionalLong() { LongStream streamFromOptionalLong = Streams.stream(OptionalLong.of(1)); @@ -67,7 +66,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithOptionalInt(){ + public void createStreamsWithOptionalInt() { IntStream streamFromOptionalInt = Streams.stream(OptionalInt.of(1)); @@ -76,7 +75,7 @@ public class GauavaStreamsTests { } @Test - public void createStreamsWithOptionalDouble(){ + public void createStreamsWithOptionalDouble() { DoubleStream streamFromOptionalDouble = Streams.stream(OptionalDouble.of(1.0)); @@ -86,41 +85,44 @@ public class GauavaStreamsTests { } @Test - public void concatStreamsOfSameType(){ - Stream oddNumbers = Arrays.asList(1,3,5,7,9,11,13,15,17,19).stream(); - Stream evenNumbers = Arrays.asList(2,4,6,8,10,12,14,16,18,20).stream(); + public void concatStreamsOfSameType() { + Stream oddNumbers = Arrays + .asList(1, 3, 5, 7, 9, 11, 13, 15, 17, 19) + .stream(); + Stream evenNumbers = Arrays + .asList(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) + .stream(); - Stream combinedStreams = Streams.concat(oddNumbers,evenNumbers); + Stream combinedStreams = Streams.concat(oddNumbers, evenNumbers); //Assert.assertNotNull(combinedStreams); StreamUtility.assertStreamEquals(combinedStreams, Stream.concat(oddNumbers, evenNumbers)); } @Test - public void concatStreamsOfTypeLongStream(){ - LongStream firstTwenty = LongStream.range(1,20); - LongStream nextTwenty = LongStream.range(21,40); + public void concatStreamsOfTypeLongStream() { + LongStream firstTwenty = LongStream.range(1, 20); + LongStream nextTwenty = LongStream.range(21, 40); - LongStream combinedStreams = Streams.concat(firstTwenty,nextTwenty); + LongStream combinedStreams = Streams.concat(firstTwenty, nextTwenty); Assert.assertNotNull(combinedStreams); StreamUtility.assertStreamEquals(combinedStreams, LongStream.concat(firstTwenty, nextTwenty)); } @Test - public void concatStreamsOfTypeIntStream(){ - IntStream firstTwenty = IntStream.range(1,20); - IntStream nextTwenty = IntStream.range(21,40); + public void concatStreamsOfTypeIntStream() { + IntStream firstTwenty = IntStream.range(1, 20); + IntStream nextTwenty = IntStream.range(21, 40); - IntStream combinedStreams = Streams.concat(firstTwenty,nextTwenty); + IntStream combinedStreams = Streams.concat(firstTwenty, nextTwenty); Assert.assertNotNull(combinedStreams); StreamUtility.assertStreamEquals(combinedStreams, IntStream.concat(firstTwenty, nextTwenty)); } - @Test - public void findLastOfStream(){ + public void findLastOfStream() { Optional lastElement = Streams.findLast(numbers.stream()); Assert.assertNotNull(lastElement.get()); @@ -128,28 +130,29 @@ public class GauavaStreamsTests { } @Test - public void mapWithIndexTest(){ - Stream stringSream = Stream.of("a","b","c"); + public void mapWithIndexTest() { + Stream stringSream = Stream.of("a", "b", "c"); - Stream mappedStream = Streams.mapWithIndex(stringSream,(str,index) -> str +":"+ index); + Stream mappedStream = Streams.mapWithIndex(stringSream, (str, index) -> str + ":" + index); //Assert.assertNotNull(mappedStream); - Assert.assertEquals(mappedStream.findFirst().get(), "a:0"); + Assert.assertEquals(mappedStream + .findFirst() + .get(), "a:0"); } @Test - public void streamsZipTest(){ - Stream stringSream = Stream.of("a","b","c"); - Stream intStream = Stream.of(1,2,3); - Stream mappedStream = Streams.zip(stringSream,intStream, (str,index) -> str +":"+ index); + public void streamsZipTest() { + Stream stringSream = Stream.of("a", "b", "c"); + Stream intStream = Stream.of(1, 2, 3); + Stream mappedStream = Streams.zip(stringSream, intStream, (str, index) -> str + ":" + index); //Assert.assertNotNull(mappedStream); - Assert.assertEquals(mappedStream.findFirst().get(), "a:1"); + Assert.assertEquals(mappedStream + .findFirst() + .get(), "a:1"); } - - - } diff --git a/guava21/src/test/java/InternBuilderUnitTests.java b/guava21/src/test/java/InternBuilderUnitTests.java index 513b44f249..b569b59978 100644 --- a/guava21/src/test/java/InternBuilderUnitTests.java +++ b/guava21/src/test/java/InternBuilderUnitTests.java @@ -6,12 +6,11 @@ import org.junit.Test; public class InternBuilderUnitTests { @Test - public void interBuilderTest(){ + public void interBuilderTest() { - Interner interners = Interners.newBuilder() - .concurrencyLevel(2) - .strong() - .build(); + Interner interners = Interners. newBuilder() + .concurrencyLevel(2) + .strong(). build(); Assert.assertNotNull(interners); } diff --git a/guava21/src/test/java/MonitorUnitTests.java b/guava21/src/test/java/MonitorUnitTests.java index 6427072db9..7b52c48d8f 100644 --- a/guava21/src/test/java/MonitorUnitTests.java +++ b/guava21/src/test/java/MonitorUnitTests.java @@ -2,14 +2,6 @@ import com.google.common.util.concurrent.Monitor; import org.junit.Assert; import org.junit.Test; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - public class MonitorUnitTests { @Test @@ -19,11 +11,11 @@ public class MonitorUnitTests { Monitor.Guard gaurdCondition = monitor.newGuard(this::returnTrue); - if(monitor.enterIf(gaurdCondition)){ - try{ + if (monitor.enterIf(gaurdCondition)) { + try { System.out.println("Entered in critical section"); enteredInCriticalSection = true; - }finally { + } finally { monitor.leave(); } } @@ -39,11 +31,11 @@ public class MonitorUnitTests { Monitor.Guard gaurdCondition = monitor.newGuard(this::returnFalse); - if(monitor.enterIf(gaurdCondition)){ - try{ + if (monitor.enterIf(gaurdCondition)) { + try { System.out.println("Entered in critical section"); enteredInCriticalSection = true; - }finally { + } finally { monitor.leave(); } } @@ -51,11 +43,11 @@ public class MonitorUnitTests { Assert.assertFalse(enteredInCriticalSection); } - private boolean returnTrue(){ + private boolean returnTrue() { return true; } - private boolean returnFalse(){ + private boolean returnFalse() { return false; } } diff --git a/guava21/src/test/java/MoreCollectorsUnitTests.java b/guava21/src/test/java/MoreCollectorsUnitTests.java index 956331e25f..a205337450 100644 --- a/guava21/src/test/java/MoreCollectorsUnitTests.java +++ b/guava21/src/test/java/MoreCollectorsUnitTests.java @@ -9,28 +9,28 @@ import java.util.Optional; public class MoreCollectorsUnitTests { @Test - public void toOptionalTest(){ + public void toOptionalTest() { List numbers = Arrays.asList(1); - Optional number = numbers.stream() - .map( e -> e*2) - .collect(MoreCollectors.toOptional()); + Optional number = numbers + .stream() + .map(e -> e * 2) + .collect(MoreCollectors.toOptional()); - Assert.assertEquals(number.get(),new Integer(2)); + Assert.assertEquals(number.get(), new Integer(2)); } - @Test - public void onlyElementTest(){ + public void onlyElementTest() { List numbers = Arrays.asList(1); - Integer number = numbers.stream() - .map( e -> e*2) - .collect(MoreCollectors.onlyElement()); + Integer number = numbers + .stream() + .map(e -> e * 2) + .collect(MoreCollectors.onlyElement()); - Assert.assertEquals(number,new Integer(2)); + Assert.assertEquals(number, new Integer(2)); } - } diff --git a/guava21/src/test/java/StreamUtility.java b/guava21/src/test/java/StreamUtility.java index 91a03be48d..1eb866fb88 100644 --- a/guava21/src/test/java/StreamUtility.java +++ b/guava21/src/test/java/StreamUtility.java @@ -8,12 +8,12 @@ import java.util.stream.Stream; public class StreamUtility { - public static boolean assertStreamEquals(Stream stream1, Stream stream2){ + public static boolean assertStreamEquals(Stream stream1, Stream stream2) { Iterator iterator1 = stream1.iterator(); Iterator iterator2 = stream2.iterator(); - while (iterator1.hasNext()){ + while (iterator1.hasNext()) { Assert.assertEquals(iterator1.next(), iterator2.next()); } @@ -27,7 +27,7 @@ public class StreamUtility { Iterator iterator1 = stream1.iterator(); Iterator iterator2 = stream2.iterator(); - while (iterator1.hasNext()){ + while (iterator1.hasNext()) { Assert.assertEquals(iterator1.next(), iterator2.next()); } @@ -41,7 +41,7 @@ public class StreamUtility { Iterator iterator1 = stream1.iterator(); Iterator iterator2 = stream2.iterator(); - while (iterator1.hasNext()){ + while (iterator1.hasNext()) { Assert.assertEquals(iterator1.next(), iterator2.next()); } @@ -55,7 +55,7 @@ public class StreamUtility { Iterator iterator1 = stream1.iterator(); Iterator iterator2 = stream2.iterator(); - while (iterator1.hasNext()){ + while (iterator1.hasNext()) { Assert.assertEquals(iterator1.next(), iterator2.next()); } From 99688e9b19bc1bc2f5c06996b3d988ce42d5ec34 Mon Sep 17 00:00:00 2001 From: Danil Kornishev Date: Thu, 30 Mar 2017 16:27:22 -0400 Subject: [PATCH 113/149] Spring State Machine x3 (#1538) * [Fix] Move stateDo onto a separate state * Change tests to spring runner --- spring-state-machine/pom.xml | 7 +++- .../SimpleStateMachineConfiguration.java | 21 ++++++++--- .../ForkJoinStateMachineTest.java | 29 +++++++++++---- .../HierarchicalStateMachineTest.java | 27 +++++++++++--- .../JunctionStateMachineTest.java | 26 +++++++++++-- .../statemachine/StateEnumMachineTest.java | 17 ++++++++- .../StateMachineIntegrationTest.java | 37 ++++++++++++++----- 7 files changed, 130 insertions(+), 34 deletions(-) diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml index bec03c39e8..f04d706d47 100644 --- a/spring-state-machine/pom.xml +++ b/spring-state-machine/pom.xml @@ -21,10 +21,15 @@ spring-statemachine-core 1.2.3.RELEASE + + org.springframework + spring-test + 4.3.7.RELEASE + junit junit - 4.11 + 4.12 test diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java index e9b448f6e7..f6c7991cf6 100644 --- a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java +++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java @@ -36,10 +36,10 @@ public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapt .initial("SI") .end("SF") .states(new HashSet<>(Arrays.asList("S1", "S2"))) - .state("S4", executeAction(), errorAction()) .stateEntry("S3", entryAction()) - .stateDo("S3", executeAction()) - .stateExit("S3", exitAction()); + .stateExit("S3", exitAction()) + .state("S4", executeAction(), errorAction()) + .stateDo("S5", executeAction()); } @@ -52,9 +52,11 @@ public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapt .and().withExternal() .source("SI").target("S3").event("E3") .and().withExternal() - .source("S3").target("S4").event("E4").and().withExternal().source("S4").target("SF").event("end").guard(simpleGuard()) + .source("S3").target("S4").event("E4") .and().withExternal() - .source("S2").target("SF").event("end"); + .source("S4").target("S5").event("E5") + .and().withExternal() + .source("S5").target("SF").event("end").guard(simpleGuard()); } @Bean @@ -73,9 +75,16 @@ public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapt } @Bean - public Action executeAction() { + public Action doAction() { return (ctx) -> { LOGGER.info("Do " + ctx.getTarget().getId()); + }; + } + + @Bean + public Action executeAction() { + return (ctx) -> { + LOGGER.info("Execute " + ctx.getTarget().getId()); int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0); approvals++; ctx.getExtendedState().getVariables().put("approvalCount", approvals); diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java index 7b4b1928ea..03cb101a9d 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java @@ -1,23 +1,36 @@ package com.baeldung.spring.statemachine; import com.baeldung.spring.statemachine.config.ForkJoinStateMachineConfiguration; +import com.baeldung.spring.statemachine.config.JunctionStateMachineConfiguration; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import javax.annotation.Resource; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = ForkJoinStateMachineConfiguration.class) public class ForkJoinStateMachineTest { + @Resource + private StateMachine stateMachine; + + @Before + public void setUp() { + stateMachine.start(); + } + @Test public void whenForkStateEntered_thenMultipleSubStatesEntered() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - boolean success = stateMachine.sendEvent("E1"); assertTrue(success); @@ -27,9 +40,6 @@ public class ForkJoinStateMachineTest { @Test public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); boolean success = stateMachine.sendEvent("E1"); @@ -41,4 +51,9 @@ public class ForkJoinStateMachineTest { assertTrue(stateMachine.sendEvent("sub2")); assertEquals("SJoin", stateMachine.getState().getId()); } + + @After + public void tearDown() { + stateMachine.stop(); + } } diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java index d2944be173..950414bb0e 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java @@ -1,23 +1,35 @@ package com.baeldung.spring.statemachine; import com.baeldung.spring.statemachine.config.HierarchicalStateMachineConfiguration; +import com.baeldung.spring.statemachine.config.JunctionStateMachineConfiguration; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import javax.annotation.Resource; import java.util.Arrays; import static org.junit.Assert.assertEquals; - +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = HierarchicalStateMachineConfiguration.class) public class HierarchicalStateMachineTest { + @Resource + private StateMachine stateMachine; + + @Before + public void setUp() { + stateMachine.start(); + } + @Test public void whenTransitionToSubMachine_thenSubStateIsEntered() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); - assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds()); @@ -34,4 +46,9 @@ public class HierarchicalStateMachineTest { assertEquals(1, stateMachine.getState().getIds().size()); assertEquals("SF", stateMachine.getState().getId()); } + + @After + public void tearDown() { + stateMachine.stop(); + } } diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java index f01683638b..64930162fd 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java @@ -1,18 +1,33 @@ package com.baeldung.spring.statemachine; +import com.baeldung.spring.statemachine.config.JunctionStateMachineConfiguration; +import com.baeldung.spring.statemachine.config.SimpleEnumStateMachineConfiguration; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import javax.annotation.Resource; +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = JunctionStateMachineConfiguration.class) public class JunctionStateMachineTest { + @Resource + private StateMachine stateMachine; + + @Before + public void setUp() { + stateMachine.start(); + } + @Test public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(com.baeldung.spring.statemachine.config.JunctionStateMachineConfiguration.class); - StateMachine stateMachine = ctx.getBean(StateMachine.class); - stateMachine.start(); stateMachine.sendEvent("E1"); Assert.assertEquals("low", stateMachine.getState().getId()); @@ -20,4 +35,9 @@ public class JunctionStateMachineTest { stateMachine.sendEvent("end"); Assert.assertEquals("SF", stateMachine.getState().getId()); } + + @After + public void tearDown() { + stateMachine.stop(); + } } diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java index 257ed8768c..b7cbebe145 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java @@ -3,22 +3,30 @@ package com.baeldung.spring.statemachine; import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewEvents; import com.baeldung.spring.statemachine.applicationreview.ApplicationReviewStates; import com.baeldung.spring.statemachine.config.SimpleEnumStateMachineConfiguration; +import com.baeldung.spring.statemachine.config.SimpleStateMachineConfiguration; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.statemachine.StateMachine; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SimpleEnumStateMachineConfiguration.class) public class StateEnumMachineTest { + @Resource private StateMachine stateMachine; @Before public void setUp() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class); - stateMachine = ctx.getBean(StateMachine.class); stateMachine.start(); } @@ -29,4 +37,9 @@ public class StateEnumMachineTest { assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT)); assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId()); } + + @After + public void tearDown() { + stateMachine.stop(); + } } diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java index d7b26eeb97..8f61d93105 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java @@ -1,23 +1,31 @@ package com.baeldung.spring.statemachine; -import com.baeldung.spring.statemachine.config.SimpleStateMachineConfiguration; -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.statemachine.StateMachine; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.statemachine.StateMachine; + +import com.baeldung.spring.statemachine.config.SimpleStateMachineConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SimpleStateMachineConfiguration.class) public class StateMachineIntegrationTest { - private AnnotationConfigApplicationContext ctx; + @Resource private StateMachine stateMachine; @Before public void setUp() { - ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class); - stateMachine = ctx.getBean(StateMachine.class); stateMachine.start(); } @@ -42,9 +50,18 @@ public class StateMachineIntegrationTest { assertTrue(acceptedE4); assertEquals("S4", stateMachine.getState().getId()); - assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount")); + + stateMachine.sendEvent("E5"); + assertEquals("S5", stateMachine.getState().getId()); stateMachine.sendEvent("end"); assertEquals("SF", stateMachine.getState().getId()); + + assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount")); + } + + @After + public void tearDown() { + stateMachine.stop(); } } From d3590de13cb75a3d39553f2d78ecf1afe2b65e98 Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Fri, 31 Mar 2017 02:40:45 +0530 Subject: [PATCH 114/149] ratpack with google guice (#1542) * rest with spark java * 4 * Update Application.java * indentation changes * spring @requestmapping shortcuts * removing spring requestmapping and pushing spring-mvc-java * Joining/Splitting Strings with Java and Stream API * adding more join/split functionality * changing package name * testcase change * adding webutils * adding testcase for WebUtils and ServletRequestUtils * adding testcase * spring-security-stormpath * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial * changing banner format in plain text * Delete banner.txt~ * Delete b.txt~ * CORS in JAX-RS * ratpack with google guice --- .../java/com/baeldung/guice/Application.java | 31 ++++ .../guice/config/DependencyModule.java | 16 ++ .../guice/service/DataPumpService.java | 11 ++ .../service/impl/DataPumpServiceImpl.java | 14 ++ .../java/com/baeldung/ApplicationTest.java | 13 +- resteasy/bin/README.md | 8 + resteasy/bin/pom.xml | 171 ++++++++++++++++++ .../main/webapp/WEB-INF/classes/logback.xml | 3 + .../WEB-INF/jboss-deployment-structure.xml | 13 ++ .../bin/src/main/webapp/WEB-INF/jboss-web.xml | 4 + resteasy/bin/src/main/webapp/WEB-INF/web.xml | 13 ++ resteasy/bin/src/main/webapp/script.js | 16 ++ .../com/baeldung/server/movies/batman.json | 4 + .../baeldung/server/movies/transformer.json | 4 + 14 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 ratpack/src/main/java/com/baeldung/guice/Application.java create mode 100644 ratpack/src/main/java/com/baeldung/guice/config/DependencyModule.java create mode 100644 ratpack/src/main/java/com/baeldung/guice/service/DataPumpService.java create mode 100644 ratpack/src/main/java/com/baeldung/guice/service/impl/DataPumpServiceImpl.java create mode 100644 resteasy/bin/README.md create mode 100644 resteasy/bin/pom.xml create mode 100644 resteasy/bin/src/main/webapp/WEB-INF/classes/logback.xml create mode 100644 resteasy/bin/src/main/webapp/WEB-INF/jboss-deployment-structure.xml create mode 100644 resteasy/bin/src/main/webapp/WEB-INF/jboss-web.xml create mode 100644 resteasy/bin/src/main/webapp/WEB-INF/web.xml create mode 100644 resteasy/bin/src/main/webapp/script.js create mode 100644 resteasy/bin/src/test/resources/com/baeldung/server/movies/batman.json create mode 100644 resteasy/bin/src/test/resources/com/baeldung/server/movies/transformer.json diff --git a/ratpack/src/main/java/com/baeldung/guice/Application.java b/ratpack/src/main/java/com/baeldung/guice/Application.java new file mode 100644 index 0000000000..39d29b9b2b --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/guice/Application.java @@ -0,0 +1,31 @@ +package com.baeldung.guice; + +import com.baeldung.guice.config.DependencyModule; +import com.baeldung.guice.service.DataPumpService; +import com.baeldung.guice.service.impl.DataPumpServiceImpl; + +import ratpack.guice.Guice; +import ratpack.server.RatpackServer; + +public class Application { + + public static void main(String[] args) throws Exception { + + RatpackServer + .start(server -> server.registry(Guice.registry(bindings -> bindings.module(DependencyModule.class))) + .handlers(chain -> chain.get("randomString", ctx -> { + DataPumpService dataPumpService = ctx.get(DataPumpService.class); + ctx.render(dataPumpService.generate().length()); + }))); + +// RatpackServer.start(server -> server +// .registry(Guice +// .registry(bindings -> bindings.bindInstance(DataPumpService.class, new DataPumpServiceImpl()))) +// .handlers(chain -> chain.get("randomString", ctx -> { +// DataPumpService dataPumpService = ctx.get(DataPumpService.class); +// ctx.render(dataPumpService.generate()); +// }))); + + } + +} diff --git a/ratpack/src/main/java/com/baeldung/guice/config/DependencyModule.java b/ratpack/src/main/java/com/baeldung/guice/config/DependencyModule.java new file mode 100644 index 0000000000..1824578501 --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/guice/config/DependencyModule.java @@ -0,0 +1,16 @@ +package com.baeldung.guice.config; + +import com.baeldung.guice.service.DataPumpService; +import com.baeldung.guice.service.impl.DataPumpServiceImpl; +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; + +public class DependencyModule extends AbstractModule { + + @Override + protected void configure() { + bind(DataPumpService.class).to(DataPumpServiceImpl.class) + .in(Scopes.SINGLETON); + } + +} diff --git a/ratpack/src/main/java/com/baeldung/guice/service/DataPumpService.java b/ratpack/src/main/java/com/baeldung/guice/service/DataPumpService.java new file mode 100644 index 0000000000..6adfec2365 --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/guice/service/DataPumpService.java @@ -0,0 +1,11 @@ +package com.baeldung.guice.service; + +import com.baeldung.guice.service.impl.DataPumpServiceImpl; +import com.google.inject.ImplementedBy; + +@ImplementedBy(DataPumpServiceImpl.class) +public interface DataPumpService { + + String generate(); + +} diff --git a/ratpack/src/main/java/com/baeldung/guice/service/impl/DataPumpServiceImpl.java b/ratpack/src/main/java/com/baeldung/guice/service/impl/DataPumpServiceImpl.java new file mode 100644 index 0000000000..88f171f8a2 --- /dev/null +++ b/ratpack/src/main/java/com/baeldung/guice/service/impl/DataPumpServiceImpl.java @@ -0,0 +1,14 @@ +package com.baeldung.guice.service.impl; + +import java.util.UUID; + +import com.baeldung.guice.service.DataPumpService; + +public class DataPumpServiceImpl implements DataPumpService { + + @Override + public String generate() { + return UUID.randomUUID().toString(); + } + +} diff --git a/ratpack/src/test/java/com/baeldung/ApplicationTest.java b/ratpack/src/test/java/com/baeldung/ApplicationTest.java index 0333441928..047575ca6e 100644 --- a/ratpack/src/test/java/com/baeldung/ApplicationTest.java +++ b/ratpack/src/test/java/com/baeldung/ApplicationTest.java @@ -24,22 +24,27 @@ public class ApplicationTest { public void givenDefaultUrl_getStaticText() { assertEquals("Welcome to baeldung ratpack!!!", appUnderTest.getHttpClient().getText("/")); } - + @Test public void givenDynamicUrl_getDynamicText() { assertEquals("Hello dummybot!!!", appUnderTest.getHttpClient().getText("/dummybot")); } - + @Test public void givenUrl_getListOfEmployee() throws JsonProcessingException { List employees = new ArrayList(); ObjectMapper mapper = new ObjectMapper(); employees.add(new Employee(1L, "Mr", "John Doe")); employees.add(new Employee(2L, "Mr", "White Snow")); - + assertEquals(mapper.writeValueAsString(employees), appUnderTest.getHttpClient().getText("/data/employees")); } - + + @Test + public void givenStaticUrl_getDynamicText() { + assertEquals(21, appUnderTest.getHttpClient().getText("/randomString").length()); + } + @After public void shutdown() { appUnderTest.close(); diff --git a/resteasy/bin/README.md b/resteasy/bin/README.md new file mode 100644 index 0000000000..722f1dfe93 --- /dev/null +++ b/resteasy/bin/README.md @@ -0,0 +1,8 @@ +========= + +## A Guide to RESTEasy + + +### Relevant Articles: +- [A Guide to RESTEasy](http://www.baeldung.com/resteasy-tutorial) +- [RESTEasy Client API](http://www.baeldung.com/resteasy-client-tutorial) diff --git a/resteasy/bin/pom.xml b/resteasy/bin/pom.xml new file mode 100644 index 0000000000..f0bd8298f5 --- /dev/null +++ b/resteasy/bin/pom.xml @@ -0,0 +1,171 @@ + + + 4.0.0 + + com.baeldung + resteasy-tutorial + 1.0 + war + + + 3.0.19.Final + 4.12 + 2.5 + 2.19.1 + 1.6.1 + + + + RestEasyTutorial + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + + + org.codehaus.cargo + cargo-maven2-plugin + ${cargo-maven2-plugin.version} + + true + + jetty8x + embedded + + + + 8082 + + + + + + + + + + + + + org.jboss.resteasy + resteasy-servlet-initializer + ${resteasy.version} + + + + + org.jboss.resteasy + resteasy-client + ${resteasy.version} + + + + + + org.jboss.resteasy + resteasy-jaxb-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jackson-provider + ${resteasy.version} + + + + + + junit + junit + ${junit.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + + + + live + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*IntegrationTest.java + + + **/*LiveTest.java + + + + + + + json + + + + + org.codehaus.cargo + cargo-maven2-plugin + ${cargo-maven2-plugin.version} + + false + + + + start-server + pre-integration-test + + start + + + + stop-server + post-integration-test + + stop + + + + + + + + + + + \ No newline at end of file diff --git a/resteasy/bin/src/main/webapp/WEB-INF/classes/logback.xml b/resteasy/bin/src/main/webapp/WEB-INF/classes/logback.xml new file mode 100644 index 0000000000..d94e9f71ab --- /dev/null +++ b/resteasy/bin/src/main/webapp/WEB-INF/classes/logback.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resteasy/bin/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/resteasy/bin/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100644 index 0000000000..cb258374a1 --- /dev/null +++ b/resteasy/bin/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/resteasy/bin/src/main/webapp/WEB-INF/jboss-web.xml b/resteasy/bin/src/main/webapp/WEB-INF/jboss-web.xml new file mode 100644 index 0000000000..694bb71332 --- /dev/null +++ b/resteasy/bin/src/main/webapp/WEB-INF/jboss-web.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resteasy/bin/src/main/webapp/WEB-INF/web.xml b/resteasy/bin/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..d5f00293f4 --- /dev/null +++ b/resteasy/bin/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,13 @@ + + + + RestEasy Example + + + resteasy.servlet.mapping.prefix + /rest + + + \ No newline at end of file diff --git a/resteasy/bin/src/main/webapp/script.js b/resteasy/bin/src/main/webapp/script.js new file mode 100644 index 0000000000..88198887b0 --- /dev/null +++ b/resteasy/bin/src/main/webapp/script.js @@ -0,0 +1,16 @@ +function call(url, type, data) { + var request = $.ajax({ + url : url, + method : "GET", + data : (data) ? JSON.stringify(data) : "", + dataType : type + }); + + request.done(function(resp) { + console.log(resp); + }); + + request.fail(function(jqXHR, textStatus) { + console.log("Request failed: " + textStatus); + }); +}; \ No newline at end of file diff --git a/resteasy/bin/src/test/resources/com/baeldung/server/movies/batman.json b/resteasy/bin/src/test/resources/com/baeldung/server/movies/batman.json new file mode 100644 index 0000000000..82aaaa8f40 --- /dev/null +++ b/resteasy/bin/src/test/resources/com/baeldung/server/movies/batman.json @@ -0,0 +1,4 @@ +{ + "title": "Batman", + "imdbId": "tt0096895" +} \ No newline at end of file diff --git a/resteasy/bin/src/test/resources/com/baeldung/server/movies/transformer.json b/resteasy/bin/src/test/resources/com/baeldung/server/movies/transformer.json new file mode 100644 index 0000000000..634cefc73c --- /dev/null +++ b/resteasy/bin/src/test/resources/com/baeldung/server/movies/transformer.json @@ -0,0 +1,4 @@ +{ + "title": "Transformers", + "imdbId": "tt0418279" +} \ No newline at end of file From 626db733b9c161e68a3fcaf0728f4b7a26cf9634 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Fri, 31 Mar 2017 12:25:37 +0200 Subject: [PATCH 115/149] Corrected Indentation using formatter. (#1541) (#1544) --- .../com/baeldung/SimpleServerVerticle.java | 6 ++-- ...> RestServiceVerticleIntegrationTest.java} | 2 +- ... SimpleServerVerticleIntegrationTest.java} | 33 +++++++++---------- 3 files changed, 18 insertions(+), 23 deletions(-) rename vertx/src/test/java/com/baeldung/{RestServiceVerticleTest.java => RestServiceVerticleIntegrationTest.java} (96%) rename vertx/src/test/java/com/baeldung/{SimpleServerVerticleTest.java => SimpleServerVerticleIntegrationTest.java} (58%) diff --git a/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java index 6b56896860..96aa058ce7 100644 --- a/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java +++ b/vertx/src/main/java/com/baeldung/SimpleServerVerticle.java @@ -8,10 +8,8 @@ public class SimpleServerVerticle extends AbstractVerticle { @Override public void start(Future future) { vertx.createHttpServer() - .requestHandler(request -> { - request.response() - .end("Welcome to Vert.x Intro"); - }) + .requestHandler( + r -> r.response().end("Welcome to Vert.x Intro")) .listen(config().getInteger("http.port", 8080), result -> { if (result.succeeded()) { future.complete(); diff --git a/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java b/vertx/src/test/java/com/baeldung/RestServiceVerticleIntegrationTest.java similarity index 96% rename from vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java rename to vertx/src/test/java/com/baeldung/RestServiceVerticleIntegrationTest.java index b5be0734f4..f08d9ffde1 100644 --- a/vertx/src/test/java/com/baeldung/RestServiceVerticleTest.java +++ b/vertx/src/test/java/com/baeldung/RestServiceVerticleIntegrationTest.java @@ -13,7 +13,7 @@ import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; @RunWith(VertxUnitRunner.class) -public class RestServiceVerticleTest { +public class RestServiceVerticleIntegrationTest { private Vertx vertx; diff --git a/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java b/vertx/src/test/java/com/baeldung/SimpleServerVerticleIntegrationTest.java similarity index 58% rename from vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java rename to vertx/src/test/java/com/baeldung/SimpleServerVerticleIntegrationTest.java index 189d2f6604..194f403e25 100644 --- a/vertx/src/test/java/com/baeldung/SimpleServerVerticleTest.java +++ b/vertx/src/test/java/com/baeldung/SimpleServerVerticleIntegrationTest.java @@ -1,27 +1,23 @@ package com.baeldung; +import io.vertx.core.Vertx; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.unit.Async; -import io.vertx.ext.unit.TestContext; -import io.vertx.ext.unit.junit.VertxUnitRunner; - @RunWith(VertxUnitRunner.class) -public class SimpleServerVerticleTest { +public class SimpleServerVerticleIntegrationTest { private Vertx vertx; @Before public void setup(TestContext testContext) { vertx = Vertx.vertx(); - vertx.deployVerticle(SimpleServerVerticle.class.getName(), - testContext.asyncAssertSuccess()); + vertx.deployVerticle(SimpleServerVerticle.class.getName(), testContext.asyncAssertSuccess()); } @After @@ -33,14 +29,15 @@ public class SimpleServerVerticleTest { public void whenReceivedResponse_thenSuccess(TestContext testContext) { final Async async = testContext.async(); - vertx.createHttpClient() - .getNow(8080, "localhost", "/", response -> { - response.handler(responseBody -> { - testContext.assertTrue(responseBody.toString() - .contains("Welcome")); - async.complete(); - }); - }); + vertx + .createHttpClient() + .getNow(8080, "localhost", "/", + response -> response.handler(responseBody -> { + testContext.assertTrue(responseBody + .toString() + .contains("Welcome")); + async.complete(); + })); } } From c73c25292424574d09daef7dc588446a170aeec2 Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Fri, 31 Mar 2017 06:54:17 -0500 Subject: [PATCH 116/149] BAEL-680 and BAEL-756 README files (#1539) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md * BAEL-680: renamed test methods * BAEL-714: Updated README.md * BAEL-737: Updated README.md * BAEL-680 and BAEL-756 README.md updates --- core-java/README.md | 1 + kotlin/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/core-java/README.md b/core-java/README.md index 63e3748ec6..0861ee7c5e 100644 --- a/core-java/README.md +++ b/core-java/README.md @@ -84,4 +84,5 @@ - [Guide to java.util.concurrent.Locks](http://www.baeldung.com/java-concurrent-locks) - [Java Primitive Conversions](http://www.baeldung.com/java-primitive-conversions) - [Java Money and the Currency API](http://www.baeldung.com/java-money-and-currency) +- [Guide to Java 8 Comparator.comparing()](http://www.baeldung.com/java-8-comparator-comparing) diff --git a/kotlin/README.md b/kotlin/README.md index ceebde4573..309aafa4b6 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -2,3 +2,4 @@ - [Introduction to the Kotlin Language](http://www.baeldung.com/kotlin) - [A guide to the “when{}†block in Kotlin](http://www.baeldung.com/kotlin-when) +- [Comprehensive Guide to Null Safety in Kotlin](http://www.baeldung.com/kotlin-null-safety) From 4daef51161c2903f7b826278406e6ac649e920d8 Mon Sep 17 00:00:00 2001 From: Nikhil Khatwani Date: Fri, 31 Mar 2017 22:17:58 +0530 Subject: [PATCH 117/149] BAEL-711 Guide_to_Microservices_using_lagom_framework Changes (#1495) * BAEL_711_Guide_to_Microservices_using_lagom_framework Changes * BAEL_711_Guide_to_Microservices_using_lagom_framework_v2 * BAEL_711_Guide_to_Microservices_using_lagom-Review Comments --- lagom/.gitignore | 4 ++ lagom/README | 40 ++++++++++++++++ lagom/build.sbt | 41 ++++++++++++++++ .../greeting/api/GreetingService.java | 23 +++++++++ lagom/greeting-impl/bin/application.conf | 1 + .../bin/classes/application.conf | 1 + .../greeting/impl/GreetingCommand.java | 29 +++++++++++ .../greeting/impl/GreetingEntity.java | 32 +++++++++++++ .../greeting/impl/GreetingEvent.java | 23 +++++++++ .../greeting/impl/GreetingServiceImpl.java | 48 +++++++++++++++++++ .../greeting/impl/GreetingServiceModule.java | 18 +++++++ .../greeting/impl/GreetingState.java | 24 ++++++++++ .../src/main/resources/application.conf | 1 + lagom/project/build.properties | 1 + lagom/project/plugins.sbt | 2 + .../weather/api/WeatherService.java | 28 +++++++++++ .../helloworld/weather/api/WeatherStats.java | 32 +++++++++++++ lagom/weather-impl/bin/application.conf | 1 + .../weather/impl/WeatherServiceImpl.java | 19 ++++++++ .../weather/impl/WeatherServiceModule.java | 16 +++++++ .../src/main/resources/application.conf | 1 + 21 files changed, 385 insertions(+) create mode 100644 lagom/.gitignore create mode 100644 lagom/README create mode 100644 lagom/build.sbt create mode 100644 lagom/greeting-api/src/main/java/org/baeldung/lagom/helloworld/greeting/api/GreetingService.java create mode 100644 lagom/greeting-impl/bin/application.conf create mode 100644 lagom/greeting-impl/bin/classes/application.conf create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingCommand.java create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEntity.java create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEvent.java create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceImpl.java create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceModule.java create mode 100644 lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingState.java create mode 100644 lagom/greeting-impl/src/main/resources/application.conf create mode 100644 lagom/project/build.properties create mode 100644 lagom/project/plugins.sbt create mode 100644 lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherService.java create mode 100644 lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherStats.java create mode 100644 lagom/weather-impl/bin/application.conf create mode 100644 lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceImpl.java create mode 100644 lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceModule.java create mode 100644 lagom/weather-impl/src/main/resources/application.conf diff --git a/lagom/.gitignore b/lagom/.gitignore new file mode 100644 index 0000000000..5e521241aa --- /dev/null +++ b/lagom/.gitignore @@ -0,0 +1,4 @@ +greeting-impl/logs/* +lagom-hello-world/greeting-impl/bin/classes/* +lagom-hello-world/logs/application.log +lagom-hello-world/weather-impl/logs/application.log diff --git a/lagom/README b/lagom/README new file mode 100644 index 0000000000..0d81a4b3c1 --- /dev/null +++ b/lagom/README @@ -0,0 +1,40 @@ +Steps to setup from scratch + +1) Create sbt build file "build.sbt" +2) Create plugins file project/plugins.sbt +3) Create build properties file project/build.properties +4) Run sbt command from project root to generate project directories, generated projects at target/lagom-dynamic-projects + Service Locator project: lagom-internal-meta-project-service-locator + cassandra project: lagom-internal-meta-project-cassandra +5) Create folders in all projects to follow maven like directory structure layout for project source code: src/main/java and src/main/java/resources and test folders. +6) Weather microservice + a) WeatherService Interface with method: + weatherStatsForToday() + b) WeatherServiceImpl to return weatherstats from a random list of weather stats + +7) Greeting Microservice + GreetingService Interface: + handleGreetFrom(String user) + + a) Reply back to user with greeting message("Hello") along with weather stats fetched from weather microservice + b) Update the greeting message("Hello Again") for an existing user. + +8) GreetingEntity: PersistentEntity + a) handles ReceivedGreetingCommand + b) Stores ReceivedGreetingEvent events to cassandra + c) Processes ReceivedGreetingEvent to update GreetingState("Hello" to "Hello Again") if required. + +9) Register modules with lagom using application.conf files. +10) Run project: sbt lagom:runAll + + +Sample Run: +curl http://localhost:9000/api/greeting/Nikhil; +Hello Nikhil! Today's weather stats: Going to be very humid, Take Water + +curl http://localhost:9000/api/greeting/Nikhil; +Hello Again Nikhil! Today's weather stats: Going to Rain, Take Umbrella + + + + diff --git a/lagom/build.sbt b/lagom/build.sbt new file mode 100644 index 0000000000..064d67468e --- /dev/null +++ b/lagom/build.sbt @@ -0,0 +1,41 @@ +organization in ThisBuild := "org.baeldung" + +// the Scala version that will be used for cross-compiled libraries +scalaVersion in ThisBuild := "2.11.7" + +lagomKafkaEnabled in ThisBuild := false + +lazy val greetingApi = project("greeting-api") + .settings( + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + lagomJavadslApi + ) + ) + +lazy val greetingImpl = project("greeting-impl") + .enablePlugins(LagomJava) + .settings( + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + lagomJavadslPersistenceCassandra + ) + ) + .dependsOn(greetingApi, weatherApi) + +lazy val weatherApi = project("weather-api") + .settings( + version := "1.0-SNAPSHOT", + libraryDependencies ++= Seq( + lagomJavadslApi + ) + ) + +lazy val weatherImpl = project("weather-impl") + .enablePlugins(LagomJava) + .settings( + version := "1.0-SNAPSHOT" + ) + .dependsOn(weatherApi) + +def project(id: String) = Project(id, base = file(id)) \ No newline at end of file diff --git a/lagom/greeting-api/src/main/java/org/baeldung/lagom/helloworld/greeting/api/GreetingService.java b/lagom/greeting-api/src/main/java/org/baeldung/lagom/helloworld/greeting/api/GreetingService.java new file mode 100644 index 0000000000..93567f0185 --- /dev/null +++ b/lagom/greeting-api/src/main/java/org/baeldung/lagom/helloworld/greeting/api/GreetingService.java @@ -0,0 +1,23 @@ +package org.baeldung.lagom.helloworld.greeting.api; + +import static com.lightbend.lagom.javadsl.api.Service.named; +import static com.lightbend.lagom.javadsl.api.Service.restCall; + +import com.lightbend.lagom.javadsl.api.Descriptor; +import com.lightbend.lagom.javadsl.api.Service; +import com.lightbend.lagom.javadsl.api.ServiceCall; +import com.lightbend.lagom.javadsl.api.transport.Method; + +import akka.NotUsed; + +public interface GreetingService extends Service { + + public ServiceCall handleGreetFrom(String user); + + @Override + default Descriptor descriptor() { + return named("greetingservice").withCalls( + restCall(Method.GET, "/api/greeting/:fromUser", this::handleGreetFrom) + ).withAutoAcl(true); + } +} \ No newline at end of file diff --git a/lagom/greeting-impl/bin/application.conf b/lagom/greeting-impl/bin/application.conf new file mode 100644 index 0000000000..1e78ce4a79 --- /dev/null +++ b/lagom/greeting-impl/bin/application.conf @@ -0,0 +1 @@ +play.modules.enabled += org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule \ No newline at end of file diff --git a/lagom/greeting-impl/bin/classes/application.conf b/lagom/greeting-impl/bin/classes/application.conf new file mode 100644 index 0000000000..1e78ce4a79 --- /dev/null +++ b/lagom/greeting-impl/bin/classes/application.conf @@ -0,0 +1 @@ +play.modules.enabled += org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule \ No newline at end of file diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingCommand.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingCommand.java new file mode 100644 index 0000000000..be9e713ec9 --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingCommand.java @@ -0,0 +1,29 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.Preconditions; +import com.lightbend.lagom.javadsl.persistence.PersistentEntity; +import com.lightbend.lagom.serialization.CompressedJsonable; +import com.lightbend.lagom.serialization.Jsonable; + +public interface GreetingCommand extends Jsonable { + + @SuppressWarnings("serial") + @JsonDeserialize + public final class ReceivedGreetingCommand implements GreetingCommand, + CompressedJsonable, PersistentEntity.ReplyType { + private final String fromUser; + + @JsonCreator + public ReceivedGreetingCommand(String fromUser) { + this.fromUser = Preconditions.checkNotNull(fromUser, "fromUser"); + } + + public String getFromUser() { + return fromUser; + } + + } + +} diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEntity.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEntity.java new file mode 100644 index 0000000000..91fc74039d --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEntity.java @@ -0,0 +1,32 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import java.util.Optional; + +import org.baeldung.lagom.helloworld.greeting.impl.GreetingCommand.ReceivedGreetingCommand; +import org.baeldung.lagom.helloworld.greeting.impl.GreetingEvent.ReceivedGreetingEvent; + +import com.lightbend.lagom.javadsl.persistence.PersistentEntity; + +public class GreetingEntity extends PersistentEntity { + + @Override + public Behavior initialBehavior(Optional snapshotState) { + BehaviorBuilder b = newBehaviorBuilder(new GreetingState("Hello ")); + + b.setCommandHandler(ReceivedGreetingCommand.class, + (cmd, ctx) -> { + String fromUser = cmd.getFromUser(); + String currentGreeting = state().getMessage(); + return ctx.thenPersist( + new ReceivedGreetingEvent(fromUser), + evt -> ctx.reply(currentGreeting + fromUser + "!")); + }); + + b.setEventHandler(ReceivedGreetingEvent.class, + // We simply update the current state + evt -> state().withMessage("Hello Again ")); + + return b.build(); + } +} diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEvent.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEvent.java new file mode 100644 index 0000000000..e454f6cd1b --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingEvent.java @@ -0,0 +1,23 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.lightbend.lagom.serialization.Jsonable; + + +public interface GreetingEvent extends Jsonable { + + class ReceivedGreetingEvent implements GreetingEvent { + private final String fromUser; + + @JsonCreator + public ReceivedGreetingEvent(String fromUser) { + this.fromUser = fromUser; + } + + public String getFromUser() { + return fromUser; + } + + } + +} diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceImpl.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceImpl.java new file mode 100644 index 0000000000..c28687291e --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceImpl.java @@ -0,0 +1,48 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.baeldung.lagom.helloworld.greeting.api.GreetingService; +import org.baeldung.lagom.helloworld.greeting.impl.GreetingCommand.ReceivedGreetingCommand; +import org.baeldung.lagom.helloworld.weather.api.WeatherService; +import org.baeldung.lagom.helloworld.weather.api.WeatherStats; + +import com.google.inject.Inject; +import com.lightbend.lagom.javadsl.api.ServiceCall; +import com.lightbend.lagom.javadsl.persistence.PersistentEntityRef; +import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry; + +import akka.NotUsed; + +public class GreetingServiceImpl implements GreetingService { + + private final PersistentEntityRegistry persistentEntityRegistry; + private final WeatherService weatherService; + + @Inject + public GreetingServiceImpl(PersistentEntityRegistry persistentEntityRegistry, WeatherService weatherService) { + this.persistentEntityRegistry = persistentEntityRegistry; + this.weatherService = weatherService; + persistentEntityRegistry.register(GreetingEntity.class); + } + + @Override + public ServiceCall handleGreetFrom(String user) { + return request -> { + // Look up the hello world entity for the given ID. + PersistentEntityRef ref = persistentEntityRegistry.refFor(GreetingEntity.class, user); + CompletableFuture greetingResponse = ref.ask(new ReceivedGreetingCommand(user)) + .toCompletableFuture(); + CompletableFuture todaysWeatherInfo = + (CompletableFuture) weatherService.weatherStatsForToday().invoke(); + try { + return CompletableFuture.completedFuture(greetingResponse.get() + + " Today's weather stats: " + todaysWeatherInfo.get().getMessage()); + } catch (InterruptedException | ExecutionException e) { + return CompletableFuture.completedFuture("Sorry Some Error at our end, working on it"); + } + }; + } + +} diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceModule.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceModule.java new file mode 100644 index 0000000000..4813e91a7c --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingServiceModule.java @@ -0,0 +1,18 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import org.baeldung.lagom.helloworld.greeting.api.GreetingService; +import org.baeldung.lagom.helloworld.weather.api.WeatherService; + +import com.google.inject.AbstractModule; +import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; + +/** + * The module that binds the GreetingService so that it can be served. + */ +public class GreetingServiceModule extends AbstractModule implements ServiceGuiceSupport { + @Override + protected void configure() { + bindServices(serviceBinding(GreetingService.class, GreetingServiceImpl.class)); + bindClient(WeatherService.class); + } +} diff --git a/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingState.java b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingState.java new file mode 100644 index 0000000000..125795601e --- /dev/null +++ b/lagom/greeting-impl/src/main/java/org/baeldung/lagom/helloworld/greeting/impl/GreetingState.java @@ -0,0 +1,24 @@ +package org.baeldung.lagom.helloworld.greeting.impl; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize +public class GreetingState { + + private final String message; + + @JsonCreator + public GreetingState(String message) { + this.message = message; + } + + GreetingState withMessage(String message) { + return new GreetingState(message); + } + + public String getMessage() { + return message; + } + +} diff --git a/lagom/greeting-impl/src/main/resources/application.conf b/lagom/greeting-impl/src/main/resources/application.conf new file mode 100644 index 0000000000..1e78ce4a79 --- /dev/null +++ b/lagom/greeting-impl/src/main/resources/application.conf @@ -0,0 +1 @@ +play.modules.enabled += org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule \ No newline at end of file diff --git a/lagom/project/build.properties b/lagom/project/build.properties new file mode 100644 index 0000000000..43b8278c68 --- /dev/null +++ b/lagom/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.11 diff --git a/lagom/project/plugins.sbt b/lagom/project/plugins.sbt new file mode 100644 index 0000000000..8b8e36a743 --- /dev/null +++ b/lagom/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1") +addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0") diff --git a/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherService.java b/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherService.java new file mode 100644 index 0000000000..888109322b --- /dev/null +++ b/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherService.java @@ -0,0 +1,28 @@ +package org.baeldung.lagom.helloworld.weather.api; + +import static com.lightbend.lagom.javadsl.api.Service.named; +import static com.lightbend.lagom.javadsl.api.Service.*; + +import com.lightbend.lagom.javadsl.api.Descriptor; +import com.lightbend.lagom.javadsl.api.Service; +import com.lightbend.lagom.javadsl.api.ServiceCall; +import com.lightbend.lagom.javadsl.api.transport.Method; + +import akka.NotUsed; + +/** + * WeatherService Interface. + */ +public interface WeatherService extends Service { + + // Fetch Today's Weather Stats service call + public ServiceCall weatherStatsForToday(); + + @Override + default Descriptor descriptor() { + return named("weatherservice").withCalls( + restCall(Method.GET, "/api/weather", this::weatherStatsForToday) + ).withAutoAcl(true); + } + +} \ No newline at end of file diff --git a/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherStats.java b/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherStats.java new file mode 100644 index 0000000000..23530a297d --- /dev/null +++ b/lagom/weather-api/src/main/java/org/baeldung/lagom/helloworld/weather/api/WeatherStats.java @@ -0,0 +1,32 @@ +package org.baeldung.lagom.helloworld.weather.api; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public enum WeatherStats { + + STATS_RAINY("Going to Rain, Take Umbrella"), STATS_HUMID("Going to be very humid, Take Water"); + + private final String message; + + private static final List VALUES = Collections.unmodifiableList(Arrays.asList(values())); + + private static final int SIZE = VALUES.size(); + + private static final Random RANDOM = new Random(); + + WeatherStats(String msg) { + this.message = msg; + } + + public static WeatherStats forToday() { + return VALUES.get(RANDOM.nextInt(SIZE)); + } + + public String getMessage() { + return message; + } + +} diff --git a/lagom/weather-impl/bin/application.conf b/lagom/weather-impl/bin/application.conf new file mode 100644 index 0000000000..cf6cec2115 --- /dev/null +++ b/lagom/weather-impl/bin/application.conf @@ -0,0 +1 @@ +play.modules.enabled += org.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule \ No newline at end of file diff --git a/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceImpl.java b/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceImpl.java new file mode 100644 index 0000000000..d7f97f9105 --- /dev/null +++ b/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceImpl.java @@ -0,0 +1,19 @@ +package org.baeldung.lagom.helloworld.weather.impl; + +import java.util.concurrent.CompletableFuture; + +import org.baeldung.lagom.helloworld.weather.api.WeatherService; +import org.baeldung.lagom.helloworld.weather.api.WeatherStats; + +import com.lightbend.lagom.javadsl.api.ServiceCall; + +import akka.NotUsed; + +public class WeatherServiceImpl implements WeatherService { + + @Override + public ServiceCall weatherStatsForToday() { + return (req) -> CompletableFuture.completedFuture(WeatherStats.forToday()); + } + +} diff --git a/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceModule.java b/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceModule.java new file mode 100644 index 0000000000..ac2834ff5c --- /dev/null +++ b/lagom/weather-impl/src/main/java/org/baeldung/lagom/helloworld/weather/impl/WeatherServiceModule.java @@ -0,0 +1,16 @@ +package org.baeldung.lagom.helloworld.weather.impl; + +import org.baeldung.lagom.helloworld.weather.api.WeatherService; + +import com.google.inject.AbstractModule; +import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; + +/** + * The module that binds the GreetingService so that it can be served. + */ +public class WeatherServiceModule extends AbstractModule implements ServiceGuiceSupport { + @Override + protected void configure() { + bindServices(serviceBinding(WeatherService.class, WeatherServiceImpl.class)); + } +} diff --git a/lagom/weather-impl/src/main/resources/application.conf b/lagom/weather-impl/src/main/resources/application.conf new file mode 100644 index 0000000000..cf6cec2115 --- /dev/null +++ b/lagom/weather-impl/src/main/resources/application.conf @@ -0,0 +1 @@ +play.modules.enabled += org.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule \ No newline at end of file From 0975123b837608bf067466d6dfb09f0ad00bb20c Mon Sep 17 00:00:00 2001 From: Tomasz Sobala Date: Fri, 31 Mar 2017 19:33:34 +0200 Subject: [PATCH 118/149] Exploring the Spring Boot TestRestTemplate (#1550) * injecting beans * XML-based configuration replaced with Java Config. * [BAEL-431] Exploring TestRestTemplate. * Revert of evaluation task "XML-based configuration replaced with Java Config." This reverts commit 66471cf0574c85f8ff514ec4caf5ba44ebba1a74. * Revert of evaluation task "injecting beans" This reverts commit d2ac20185e636245bc0ae0b4ccb952965de88e28. * [BAEL-431] fix to the tests in TestRestTemplateBasicLiveTest. * [BAEL-431] added more meaningful user and password for auth. --- .../client/TestRestTemplateBasicLiveTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java b/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java index 9f4319d857..a4d8eb0908 100644 --- a/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java +++ b/spring-rest/src/test/java/org/baeldung/client/TestRestTemplateBasicLiveTest.java @@ -21,7 +21,7 @@ public class TestRestTemplateBasicLiveTest { private RestTemplate restTemplate; private static final String FOO_RESOURCE_URL = "http://localhost:" + APPLICATION_PORT + "/spring-rest/myfoos"; - private static final String URL_SECURED_BY_AUTHENTICATION = "http://browserspy.dk/password-ok.php"; + private static final String URL_SECURED_BY_AUTHENTICATION = "http://httpbin.org/basic-auth/user/passwd"; private static final String BASE_URL = "http://localhost:" + APPLICATION_PORT + "/spring-rest"; @Before @@ -55,16 +55,16 @@ public class TestRestTemplateBasicLiveTest { @Test public void givenRestTemplateWrapperWithCredentials_whenSendGetForEntity_thenStatusOk() { - TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplate, "test", "test"); - ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplate, "user", "passwd"); + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION, String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); } @Test public void givenTestRestTemplateWithCredentials_whenSendGetForEntity_thenStatusOk() { - TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); - ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + TestRestTemplate testRestTemplate = new TestRestTemplate("user", "passwd"); + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION, String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); } @@ -72,16 +72,16 @@ public class TestRestTemplateBasicLiveTest { @Test public void givenTestRestTemplateWithBasicAuth_whenSendGetForEntity_thenStatusOk() { TestRestTemplate testRestTemplate = new TestRestTemplate(); - ResponseEntity response = testRestTemplate.withBasicAuth("test", "test"). - getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", String.class); + ResponseEntity response = testRestTemplate.withBasicAuth("user", "passwd"). + getForEntity(URL_SECURED_BY_AUTHENTICATION, String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); } @Test public void givenTestRestTemplateWithCredentialsAndEnabledCookies_whenSendGetForEntity_thenStatusOk() { - TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test", TestRestTemplate. + TestRestTemplate testRestTemplate = new TestRestTemplate("user", "passwd", TestRestTemplate. HttpClientOption.ENABLE_COOKIES); - ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION + "/1", + ResponseEntity response = testRestTemplate.getForEntity(URL_SECURED_BY_AUTHENTICATION, String.class); assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); } @@ -97,18 +97,17 @@ public class TestRestTemplateBasicLiveTest { // POST @Test public void givenService_whenPostForObject_thenCreatedObjectIsReturned() { - TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); + TestRestTemplate testRestTemplate = new TestRestTemplate("user", "passwd"); final RequestBody body = RequestBody.create(okhttp3.MediaType.parse("text/html; charset=utf-8"), "{\"id\":1,\"name\":\"Jim\"}"); final Request request = new Request.Builder().url(BASE_URL + "/users/detail").post(body).build(); - Object response = testRestTemplate.postForObject(URL_SECURED_BY_AUTHENTICATION, request, String.class); - assertTrue(response.toString().contains("Success")); + testRestTemplate.postForObject(URL_SECURED_BY_AUTHENTICATION, request, String.class); } // PUT @Test public void givenService_whenPutForObject_thenCreatedObjectIsReturned() { - TestRestTemplate testRestTemplate = new TestRestTemplate("test", "test"); + TestRestTemplate testRestTemplate = new TestRestTemplate("user", "passwd"); final RequestBody body = RequestBody.create(okhttp3.MediaType.parse("text/html; charset=utf-8"), "{\"id\":1,\"name\":\"Jim\"}"); final Request request = new Request.Builder().url(BASE_URL + "/users/detail").post(body).build(); From 4c84fabd1af0f1d6e2ccf9cd69d3cd1fb39d16d2 Mon Sep 17 00:00:00 2001 From: azrairshad Date: Fri, 31 Mar 2017 10:54:12 -0700 Subject: [PATCH 119/149] Updated ArrayCopy assert statements. (#1540) --- .../baeldung/arraycopy/ArrayCopyUtilTest.java | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java index 060f3c3109..2235e55338 100644 --- a/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java +++ b/core-java/src/test/java/com/baeldung/arraycopy/ArrayCopyUtilTest.java @@ -15,14 +15,18 @@ public class ArrayCopyUtilTest { @BeforeClass public static void setup(){ - employees = new Employee[MAX]; - Employee employee; - for(int i = 0; i < MAX; i++) { - employee = new Employee(); - employee.setName("Emp"+i); - employee.setId(i); - employees[i] = employee; - } + createEmployeesArray(); + } + + private static void createEmployeesArray() { + employees = new Employee[MAX]; + Employee employee; + for(int i = 0; i < MAX; i++) { + employee = new Employee(); + employee.setName("Emp"+i); + employee.setId(i); + employees[i] = employee; + } } @Test @@ -32,10 +36,7 @@ public class ArrayCopyUtilTest { System.arraycopy(array, 0, copiedArray, 0, 3); - Assert.assertTrue(array.length == copiedArray.length); - Assert.assertTrue(copiedArray[0] == array[0]); - Assert.assertTrue(copiedArray[1] == array[1]); - Assert.assertTrue(copiedArray[2] == array[2]); + Assert.assertArrayEquals(copiedArray, array); } @Test @@ -70,12 +71,7 @@ public class ArrayCopyUtilTest { int[] copiedArray = Arrays.copyOf(array, newLength); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == array.length); - Assert.assertTrue(copiedArray[0] == array[0]); - Assert.assertTrue(copiedArray[1] == array[1]); - Assert.assertTrue(copiedArray[2] == array[2]); - Assert.assertTrue(copiedArray[3] == array[3]); + Assert.assertArrayEquals(copiedArray, array); array[0] = 9; Assert.assertTrue(copiedArray[0] != array[0]); copiedArray[1] = 12; @@ -86,8 +82,7 @@ public class ArrayCopyUtilTest { public void givenArrayOfNonPrimitiveType_whenCopiedViaArraysCopyOf_thenDoShallowCopy(){ Employee[] copiedArray = Arrays.copyOf(employees, employees.length); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == employees.length); + Assert.assertArrayEquals(copiedArray, employees); employees[0].setName(employees[0].getName()+"_Changed"); //change in employees' element caused change in the copied array Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); @@ -99,12 +94,7 @@ public class ArrayCopyUtilTest { int[] copiedArray = array.clone(); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == array.length); - Assert.assertTrue(copiedArray[0] == array[0]); - Assert.assertTrue(copiedArray[1] == array[1]); - Assert.assertTrue(copiedArray[2] == array[2]); - Assert.assertTrue(copiedArray[3] == array[3]); + Assert.assertArrayEquals(copiedArray, array); array[0] = 9; Assert.assertTrue(copiedArray[0] != array[0]); copiedArray[1] = 12; @@ -115,8 +105,7 @@ public class ArrayCopyUtilTest { public void givenArraysOfNonPrimitiveType_whenCopiedViaArrayClone_thenDoShallowCopy(){ Employee[] copiedArray = employees.clone(); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == employees.length); + Assert.assertArrayEquals(copiedArray, employees);; employees[0].setName(employees[0].getName()+"_Changed"); //change in employees' element changed the copied array Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); @@ -128,29 +117,25 @@ public class ArrayCopyUtilTest { Address[] copiedArray = addresses.clone(); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == addresses.length); addresses[0].setCity(addresses[0].getCity()+"_Changed"); - Assert.assertTrue(copiedArray[0].getCity().equals(addresses[0].getCity())); + Assert.assertArrayEquals(copiedArray, addresses); } @Test public void givenArraysOfSerializableNonPrimitiveType_whenCopiedViaSerializationUtils_thenDoDeepCopy(){ Employee[] copiedArray = SerializationUtils.clone(employees); - - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == employees.length); + employees[0].setName(employees[0].getName()+"_Changed"); //change in employees' element didn't change in the copied array - Assert.assertFalse(copiedArray[0].getName().equals(employees[0].getName())); + Assert.assertFalse( + copiedArray[0].getName().equals(employees[0].getName())); } @Test public void givenArraysOfNonPrimitiveType_whenCopiedViaStream_thenDoShallowCopy(){ Employee[] copiedArray = Arrays.stream(employees).toArray(Employee[]::new); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == employees.length); + Assert.assertArrayEquals(copiedArray, employees); employees[0].setName(employees[0].getName()+"_Changed"); //change in employees' element didn't change in the copied array Assert.assertTrue(copiedArray[0].getName().equals(employees[0].getName())); @@ -162,11 +147,7 @@ public class ArrayCopyUtilTest { String[] copiedArray = Arrays.stream(strArray).toArray(String[]::new); - Assert.assertNotNull(copiedArray); - Assert.assertTrue(copiedArray.length == strArray.length); - Assert.assertTrue(copiedArray[0] == strArray[0]); - Assert.assertTrue(copiedArray[1] == strArray[1]); - Assert.assertTrue(copiedArray[2] == strArray[2]); + Assert.assertArrayEquals(copiedArray, strArray); } private Address[] createAddressArray(){ From 3c334e6b56168d3118d384a9b1529ea7242434b4 Mon Sep 17 00:00:00 2001 From: Danil Kornishev Date: Fri, 31 Mar 2017 16:01:27 -0400 Subject: [PATCH 120/149] Spring State Machine x4 (#1547) * [Fix] Move stateDo onto a separate state * Change tests to spring runner * Add generic params to StateMachine variables --- .../baeldung/spring/statemachine/ForkJoinStateMachineTest.java | 2 +- .../spring/statemachine/HierarchicalStateMachineTest.java | 2 +- .../baeldung/spring/statemachine/JunctionStateMachineTest.java | 2 +- .../com/baeldung/spring/statemachine/StateEnumMachineTest.java | 2 +- .../spring/statemachine/StateMachineIntegrationTest.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java index 03cb101a9d..0de61da3bd 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertTrue; public class ForkJoinStateMachineTest { @Resource - private StateMachine stateMachine; + private StateMachine stateMachine; @Before public void setUp() { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java index 950414bb0e..76105368d8 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; public class HierarchicalStateMachineTest { @Resource - private StateMachine stateMachine; + private StateMachine stateMachine; @Before public void setUp() { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java index 64930162fd..b91e6af5c5 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java @@ -19,7 +19,7 @@ import javax.annotation.Resource; public class JunctionStateMachineTest { @Resource - private StateMachine stateMachine; + private StateMachine stateMachine; @Before public void setUp() { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java index b7cbebe145..cd9e58eb8e 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; public class StateEnumMachineTest { @Resource - private StateMachine stateMachine; + private StateMachine stateMachine; @Before public void setUp() { diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java index 8f61d93105..1c1ab58917 100644 --- a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java +++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineIntegrationTest.java @@ -22,7 +22,7 @@ import javax.annotation.Resource; public class StateMachineIntegrationTest { @Resource - private StateMachine stateMachine; + private StateMachine stateMachine; @Before public void setUp() { From aaf5a3e7b8cacd6ff7a6523f8a33d3aa4b4b8819 Mon Sep 17 00:00:00 2001 From: gitterjim-I Date: Fri, 31 Mar 2017 21:05:23 +0100 Subject: [PATCH 121/149] Latest review changes: more concise code and suggested refactoring. (#1549) * article Bael-667 initial commit. * Switch to use logging framework for output. * Make code more concise. Refactor as suggested. --- .../FlattenNestedListTest.java | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java index fdf4934cb7..b7939d09da 100644 --- a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java +++ b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java @@ -1,51 +1,29 @@ package com.baeldung.list.flattennestedlist; -import static org.junit.Assert.assertEquals; +import static java.util.Arrays.asList; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -import org.junit.After; -import org.junit.Before; +import org.hamcrest.collection.IsIterableContainingInOrder; import org.junit.Test; public class FlattenNestedListTest { - private List> lol = new ArrayList<>(); - List ls1 = Arrays.asList("one:one", "one:two", "one:three"); - List ls2 = Arrays.asList("two:one", "two:two", "two:three"); - List ls3 = Arrays.asList("three:one", "three:two", "three:three"); - - @Before - public void setup() { - lol.addAll(Arrays.asList(ls1, ls2, ls3)); - } - - @After - public void tearDown() { - lol = null; - } + List> lol = asList(asList("one:one"), asList("two:one", "two:two", "two:three"), asList("three:one", "three:two", "three:three", "three:four")); @Test public void givenString_flattenNestedList1() { List ls = flattenListOfListsImperatively(lol); assertNotNull(ls); - assertTrue(ls.size() == 9); + assertTrue(ls.size() == 8); // assert content - assertEquals(ls.get(0), "one:one"); - assertEquals(ls.get(1), "one:two"); - assertEquals(ls.get(2), "one:three"); - assertEquals(ls.get(3), "two:one"); - assertEquals(ls.get(4), "two:two"); - assertEquals(ls.get(5), "two:three"); - assertEquals(ls.get(6), "three:one"); - assertEquals(ls.get(7), "three:two"); - assertEquals(ls.get(8), "three:three"); + assertThat(ls, IsIterableContainingInOrder.contains("one:one", "two:one", "two:two", "two:three", "three:one", "three:two", "three:three", "three:four")); } @Test @@ -53,17 +31,9 @@ public class FlattenNestedListTest { List ls = flattenListOfListsStream(lol); assertNotNull(ls); - assertTrue(ls.size() == 9); + assertTrue(ls.size() == 8); // assert content - assertEquals(ls.get(0), "one:one"); - assertEquals(ls.get(1), "one:two"); - assertEquals(ls.get(2), "one:three"); - assertEquals(ls.get(3), "two:one"); - assertEquals(ls.get(4), "two:two"); - assertEquals(ls.get(5), "two:three"); - assertEquals(ls.get(6), "three:one"); - assertEquals(ls.get(7), "three:two"); - assertEquals(ls.get(8), "three:three"); + assertThat(ls, IsIterableContainingInOrder.contains("one:one", "two:one", "two:two", "two:three", "three:one", "three:two", "three:three", "three:four")); } public List flattenListOfListsImperatively(List> list) { From cc0c7d54a9b250c00d9efcfaac4119fe85f9cb72 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 1 Apr 2017 08:18:52 +0200 Subject: [PATCH 122/149] Enable hbase (#1545) --- hbase/pom.xml | 1 + pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hbase/pom.xml b/hbase/pom.xml index 2382e47af2..9d523abf1a 100644 --- a/hbase/pom.xml +++ b/hbase/pom.xml @@ -33,6 +33,7 @@ org.apache.hbase hbase ${hbase.version} + pom junit diff --git a/pom.xml b/pom.xml index 6b4511e45b..b26ada6f24 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ handling-spring-static-resources hazelcast - + hbase httpclient hystrix From 4b98413c4ad1f82bb9d95aeeec780dc2bd47bad1 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sat, 1 Apr 2017 07:53:56 +0100 Subject: [PATCH 123/149] Moved AOP module and removed Spring Boot starter (#1506) * Moved AOP module and removed Spring Boot starter * Fixed accidentally committed git message * Fixed pom --- spring-aop/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spring-aop/pom.xml b/spring-aop/pom.xml index 1c3b5bdccd..bd86839742 100644 --- a/spring-aop/pom.xml +++ b/spring-aop/pom.xml @@ -15,11 +15,6 @@ - - org.springframework.boot - spring-boot-starter - - org.springframework.boot spring-boot-starter-aop @@ -28,7 +23,6 @@ org.springframework.boot spring-boot-starter-test - test From 760692b8eb5e66ef20be5c183ab0e00e98c470df Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Sat, 1 Apr 2017 09:06:59 +0200 Subject: [PATCH 124/149] Build time optimization (#1553) * Add integration tests * Optimize build --- apache-poi/.gitignore | 2 ++ jee7/pom.xml | 2 +- .../ScheduleTimerBeanIntegrationTest.java | 36 +++++++++++-------- jooq/src/test/java/com/baeldung/JOOLTest.java | 3 -- pom.xml | 13 +++++++ querydsl/pom.xml | 1 - ...Test.java => QueryDSLIntegrationTest.java} | 20 +++++------ rxjava/pom.xml | 13 +++++++ ...xJavaBackpressureLongRunningUnitTest.java} | 2 +- ... => MessageControllerIntegrationTest.java} | 5 +-- spring-jooq/pom.xml | 2 +- 11 files changed, 66 insertions(+), 33 deletions(-) rename querydsl/src/test/java/org/baeldung/querydsl/intro/{QueryDSLTest.java => QueryDSLIntegrationTest.java} (99%) rename rxjava/src/test/java/com/baeldung/rxjava/{RxJavaBackpressureTest.java => RxJavaBackpressureLongRunningUnitTest.java} (98%) rename spring-amqp-simple/src/test/java/com/baeldung/springamqpsimple/{MessageControllerTest.java => MessageControllerIntegrationTest.java} (88%) diff --git a/apache-poi/.gitignore b/apache-poi/.gitignore index e05054868c..9552c1e63d 100644 --- a/apache-poi/.gitignore +++ b/apache-poi/.gitignore @@ -1 +1,3 @@ *.docx +temp.xls +temp.xlsx diff --git a/jee7/pom.xml b/jee7/pom.xml index f275f56d58..26d433df78 100644 --- a/jee7/pom.xml +++ b/jee7/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.baeldung - jee7schedule + jee7 1.0-SNAPSHOT JavaEE 7 Arquillian Archetype Sample war diff --git a/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java b/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java index c3c5ad44de..580edade77 100644 --- a/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java +++ b/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java @@ -19,25 +19,27 @@ import static com.jayway.awaitility.Awaitility.to; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; - @RunWith(Arquillian.class) public class ScheduleTimerBeanIntegrationTest { - final static long TIMEOUT = 5000l; - final static long TOLERANCE = 1000l; + private final static long TIMEOUT = 5000l; + private final static long TOLERANCE = 1000l; - @Inject - TimerEventListener timerEventListener; + @Inject TimerEventListener timerEventListener; @Deployment public static WebArchive deploy() { - File[] jars = Maven.resolver().loadPomFromFile("pom.xml") - .resolve("com.jayway.awaitility:awaitility") - .withTransitivity().asFile(); + File[] jars = Maven + .resolver() + .loadPomFromFile("pom.xml") + .resolve("com.jayway.awaitility:awaitility") + .withTransitivity() + .asFile(); - return ShrinkWrap.create(WebArchive.class) - .addAsLibraries(jars) - .addClasses(WithinWindowMatcher.class, TimerEvent.class, TimerEventListener.class, ScheduleTimerBean.class); + return ShrinkWrap + .create(WebArchive.class) + .addAsLibraries(jars) + .addClasses(WithinWindowMatcher.class, TimerEvent.class, TimerEventListener.class, ScheduleTimerBean.class); } @Test @@ -46,9 +48,15 @@ public class ScheduleTimerBeanIntegrationTest { Awaitility.setDefaultTimeout(30, TimeUnit.SECONDS); await().untilCall(to(timerEventListener.getEvents()).size(), equalTo(3)); - TimerEvent firstEvent = timerEventListener.getEvents().get(0); - TimerEvent secondEvent = timerEventListener.getEvents().get(1); - TimerEvent thirdEvent = timerEventListener.getEvents().get(2); + TimerEvent firstEvent = timerEventListener + .getEvents() + .get(0); + TimerEvent secondEvent = timerEventListener + .getEvents() + .get(1); + TimerEvent thirdEvent = timerEventListener + .getEvents() + .get(2); long delay = secondEvent.getTime() - firstEvent.getTime(); assertThat(delay, Matchers.is(WithinWindowMatcher.withinWindow(TIMEOUT, TOLERANCE))); diff --git a/jooq/src/test/java/com/baeldung/JOOLTest.java b/jooq/src/test/java/com/baeldung/JOOLTest.java index 13bf1a3ec3..a0d5f4037f 100644 --- a/jooq/src/test/java/com/baeldung/JOOLTest.java +++ b/jooq/src/test/java/com/baeldung/JOOLTest.java @@ -1,12 +1,9 @@ package com.baeldung; - -import junit.framework.Assert; import org.jooq.lambda.Seq; import org.jooq.lambda.Unchecked; import org.jooq.lambda.function.Function1; import org.jooq.lambda.function.Function2; -import org.jooq.lambda.tuple.Tuple; import org.jooq.lambda.tuple.Tuple2; import org.jooq.lambda.tuple.Tuple3; import org.jooq.lambda.tuple.Tuple4; diff --git a/pom.xml b/pom.xml index b26ada6f24..76f7247f51 100644 --- a/pom.xml +++ b/pom.xml @@ -214,6 +214,7 @@ vertx spring-data-gemfire + @@ -226,6 +227,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LongRunningUnitTest.java + **/*ManualTest.java + + true + + @@ -280,6 +286,7 @@ 4.3.4.RELEASE 4.2.0.RELEASE 1.1.5.RELEASE + 1.2.0.RELEASE 5.2.5.Final diff --git a/spring-all/src/main/java/org/baeldung/shell/Main.java b/spring-all/src/main/java/org/baeldung/shell/Main.java new file mode 100644 index 0000000000..3d9f7a5860 --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/Main.java @@ -0,0 +1,10 @@ +package org.baeldung.shell; + +import java.io.IOException; +import org.springframework.shell.Bootstrap; + +public class Main { + public static void main(String[] args) throws IOException { + Bootstrap.main(args); + } +} diff --git a/spring-all/src/main/java/org/baeldung/shell/simple/SimpleBannerProvider.java b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleBannerProvider.java new file mode 100644 index 0000000000..df7a48cd32 --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleBannerProvider.java @@ -0,0 +1,34 @@ +package org.baeldung.shell.simple; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.support.DefaultBannerProvider; +import org.springframework.shell.support.util.OsUtils; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SimpleBannerProvider extends DefaultBannerProvider { + + public String getBanner() { + StringBuffer buf = new StringBuffer(); + buf.append("=======================================").append(OsUtils.LINE_SEPARATOR); + buf.append("* Baeldung Shell *").append(OsUtils.LINE_SEPARATOR); + buf.append("=======================================").append(OsUtils.LINE_SEPARATOR); + buf.append("Version:").append(this.getVersion()); + return buf.toString(); + } + + public String getVersion() { + return "1.0.1"; + } + + public String getWelcomeMessage() { + return "Welcome to Baeldung CLI"; + } + + @Override + public String getProviderName() { + return "Baeldung Banner"; + } +} diff --git a/spring-all/src/main/java/org/baeldung/shell/simple/SimpleCLI.java b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleCLI.java new file mode 100644 index 0000000000..0bbc62cf2c --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleCLI.java @@ -0,0 +1,81 @@ +package org.baeldung.shell.simple; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.springframework.shell.Bootstrap; +import org.springframework.shell.core.CommandMarker; +import org.springframework.shell.core.annotation.CliAvailabilityIndicator; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; +import org.springframework.stereotype.Component; + +@Component +public class SimpleCLI implements CommandMarker { + + private String getContentsOfUrlAsString(URL url) { + StringBuilder sb = new StringBuilder(); + try { + try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) { + String inputLine; + while ((inputLine = in.readLine()) != null) { + sb.append(inputLine); + } + } + } catch (IOException ex) { + sb.append("ERROR"); + } + return sb.toString(); + } + + @CliCommand( + value = {"web-get", "wg"}, + help = "Displays the contents of a URL." + ) + public String webGet( + @CliOption( + key = {"", "url"}, + help = "URL whose contents will be displayed." + ) URL url) { + return getContentsOfUrlAsString(url); + } + + @CliCommand( + value = {"web-save", "ws"}, + help = "Saves the contents of a URL.") + public String webSave( + @CliOption(key = {"", "url"}, help = "URL whose contents will be saved.") URL url, + @CliOption(key = {"out", "file"}, mandatory = true, help = "The name of the file.") String file) { + String contents = getContentsOfUrlAsString(url); + try (PrintWriter out = new PrintWriter(file)) { + out.write(contents); + } catch (FileNotFoundException ex) { + //Ignore + } + return "Done."; + } + + private boolean adminEnableExecuted = false; + + @CliAvailabilityIndicator(value = {"web-save"}) + public boolean isAdminEnabled() { + return adminEnableExecuted; + } + + @CliCommand(value = "admin-enable") + public String adminEnable() { + adminEnableExecuted = true; + return "Admin commands enabled."; + } + + @CliCommand(value = "admin-disable") + public String adminDisable() { + adminEnableExecuted = false; + return "Admin commands disabled."; + } +} diff --git a/spring-all/src/main/java/org/baeldung/shell/simple/SimpleHistoryFileNameProvider.java b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleHistoryFileNameProvider.java new file mode 100644 index 0000000000..cef53adc69 --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleHistoryFileNameProvider.java @@ -0,0 +1,22 @@ +package org.baeldung.shell.simple; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.support.DefaultHistoryFileNameProvider; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SimpleHistoryFileNameProvider extends DefaultHistoryFileNameProvider { + + @Override + public String getHistoryFileName() { + return "baeldung-shell.log"; + } + + @Override + public String getProviderName() { + return "Baeldung History"; + } + +} diff --git a/spring-all/src/main/java/org/baeldung/shell/simple/SimplePromptProvider.java b/spring-all/src/main/java/org/baeldung/shell/simple/SimplePromptProvider.java new file mode 100644 index 0000000000..9a84954e05 --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/simple/SimplePromptProvider.java @@ -0,0 +1,21 @@ +package org.baeldung.shell.simple; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.shell.plugin.support.DefaultPromptProvider; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SimplePromptProvider extends DefaultPromptProvider { + + @Override + public String getPrompt() { + return "baeldung-shell>"; + } + + @Override + public String getProviderName() { + return "Baeldung Prompt"; + } +} diff --git a/spring-all/src/main/java/org/baeldung/shell/simple/SimpleURLConverter.java b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleURLConverter.java new file mode 100644 index 0000000000..66bab5c488 --- /dev/null +++ b/spring-all/src/main/java/org/baeldung/shell/simple/SimpleURLConverter.java @@ -0,0 +1,35 @@ +package org.baeldung.shell.simple; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import org.springframework.shell.core.Completion; +import org.springframework.shell.core.Converter; +import org.springframework.shell.core.MethodTarget; +import org.springframework.stereotype.Component; + +@Component +public class SimpleURLConverter implements Converter { + + @Override + public URL convertFromText(String value, Class requiredType, String optionContext) { + try { + return new URL(value); + } catch (MalformedURLException ex) { + //Ignore + } + return null; + } + + @Override + public boolean getAllPossibleValues(List completions, Class requiredType, + String existingData, String optionContext, MethodTarget target) { + return false; + } + + @Override + public boolean supports(Class requiredType, String optionContext) { + return URL.class.isAssignableFrom(requiredType); + } + +} diff --git a/spring-all/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/spring-all/src/main/resources/META-INF/spring/spring-shell-plugin.xml new file mode 100644 index 0000000000..aea1a663c1 --- /dev/null +++ b/spring-all/src/main/resources/META-INF/spring/spring-shell-plugin.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/spring-all/src/test/java/org/baeldung/shell/simple/SimpleCLIUnitTest.java b/spring-all/src/test/java/org/baeldung/shell/simple/SimpleCLIUnitTest.java new file mode 100644 index 0000000000..0353083943 --- /dev/null +++ b/spring-all/src/test/java/org/baeldung/shell/simple/SimpleCLIUnitTest.java @@ -0,0 +1,86 @@ +package org.baeldung.shell.simple; + +import java.io.File; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.shell.Bootstrap; +import org.springframework.shell.core.CommandResult; +import org.springframework.shell.core.JLineShellComponent; + +public class SimpleCLIUnitTest { + + static JLineShellComponent shell; + + @BeforeClass + public static void startUp() throws InterruptedException { + Bootstrap bootstrap = new Bootstrap(); + shell = bootstrap.getJLineShellComponent(); + } + + @AfterClass + public static void shutdown() { + shell.stop(); + } + + public static JLineShellComponent getShell() { + return shell; + } + + @Test + public void givenCommandConfig_whenExecutingWebGetCommand_thenCorrectResult() { + + CommandResult resultWebSave = shell.executeCommand("web-get --url https://www.google.com"); + + Assert.assertTrue(resultWebSave.isSuccess()); + } + + @Test + public void givenCommandConfig_whenExecutingWebSaveCommand_thenCorrectResult() { + + shell.executeCommand("admin-enable"); + CommandResult result = shell.executeCommand("web-save --url https://www.google.com --out contents.txt"); + + Assert.assertArrayEquals( + new boolean[]{ + result.isSuccess(), + new File("contents.txt").exists()}, + new boolean[]{true, true}); + } + + @Test + public void givenCommandConfig_whenAdminEnableCommandExecuted_thenCorrectAvailability() { + + CommandResult resultAdminDisable = shell.executeCommand("admin-disable"); + CommandResult resultWebSaveUnavailable = shell.executeCommand("web-save --url https://www.google.com --out contents.txt"); + CommandResult resultAdminEnable = shell.executeCommand("admin-enable"); + CommandResult resultWebSaveAvailable = shell.executeCommand("web-save --url https://www.google.com --out contents.txt"); + + Assert.assertArrayEquals( + new boolean[]{ + resultAdminDisable.isSuccess(), + resultWebSaveUnavailable.isSuccess(), + resultAdminEnable.isSuccess(), + resultWebSaveAvailable.isSuccess()}, + new boolean[]{true, false, true, true}); + } + + @Test + public void givenCommandConfig_whenWebSaveCommandExecutedNoOutArgument_thenError() { + + shell.executeCommand("admin-enable"); + CommandResult resultWebSave = shell.executeCommand("web-save --url https://www.google.com"); + + Assert.assertEquals(resultWebSave.isSuccess(), false); + } + + @Test + public void givenCommandConfig_whenExecutingWebGetCommandWithDefaultArgument_thenCorrectResult() { + + CommandResult result = shell.executeCommand("web-get https://www.google.com"); + + Assert.assertEquals(result.isSuccess(), true); + } + +} From 077d745b8d2bb33a48d82d14cdf378bc7eca1eab Mon Sep 17 00:00:00 2001 From: Wim Deblauwe Date: Sat, 1 Apr 2017 23:02:19 +0200 Subject: [PATCH 131/149] BAEL-75 - Spring Boot Audit Support (#1561) Rework to avoid the separate spring-boot-auditing module by moving the code to the spring-security-core module --- spring-boot-auditing/.gitignore | 6 - spring-boot-auditing/pom.xml | 198 ------------------ .../main/java/org/baeldung/Application.java | 13 -- .../src/main/java/org/baeldung/MvcConfig.java | 18 -- .../java/org/baeldung/WebSecurityConfig.java | 34 --- .../src/main/resources/application.properties | 1 - .../src/main/resources/logback.xml | 14 -- .../src/main/resources/templates/hello.html | 13 -- .../src/main/resources/templates/home.html | 11 - .../src/main/resources/templates/login.html | 20 -- spring-security-core/pom.xml | 4 + ...temptedPathAuthorizationAuditListener.java | 0 .../auditing/LoginAttemptsLogger.java | 8 +- .../baeldung/config/WebSecurityConfig.java | 5 +- 14 files changed, 12 insertions(+), 333 deletions(-) delete mode 100644 spring-boot-auditing/.gitignore delete mode 100644 spring-boot-auditing/pom.xml delete mode 100755 spring-boot-auditing/src/main/java/org/baeldung/Application.java delete mode 100755 spring-boot-auditing/src/main/java/org/baeldung/MvcConfig.java delete mode 100755 spring-boot-auditing/src/main/java/org/baeldung/WebSecurityConfig.java delete mode 100644 spring-boot-auditing/src/main/resources/application.properties delete mode 100644 spring-boot-auditing/src/main/resources/logback.xml delete mode 100755 spring-boot-auditing/src/main/resources/templates/hello.html delete mode 100755 spring-boot-auditing/src/main/resources/templates/home.html delete mode 100755 spring-boot-auditing/src/main/resources/templates/login.html rename {spring-boot-auditing => spring-security-core}/src/main/java/org/baeldung/auditing/ExposeAttemptedPathAuthorizationAuditListener.java (100%) rename {spring-boot-auditing => spring-security-core}/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java (72%) diff --git a/spring-boot-auditing/.gitignore b/spring-boot-auditing/.gitignore deleted file mode 100644 index 31ce405488..0000000000 --- a/spring-boot-auditing/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target/ -.settings/ -.classpath -.project -*.iml -.idea \ No newline at end of file diff --git a/spring-boot-auditing/pom.xml b/spring-boot-auditing/pom.xml deleted file mode 100644 index 0afbe0becc..0000000000 --- a/spring-boot-auditing/pom.xml +++ /dev/null @@ -1,198 +0,0 @@ - - 4.0.0 - com.baeldung - spring-boot-auditing - 0.0.1-SNAPSHOT - war - spring-boot-auditing - This is simple boot application for Spring boot auditing test - - - - org.springframework.boot - spring-boot-starter-parent - 1.5.2.RELEASE - - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-security - - - - io.dropwizard.metrics - metrics-core - - - - com.h2database - h2 - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter - - - com.jayway.jsonpath - json-path - test - - - org.springframework.boot - spring-boot-starter-mail - - - org.subethamail - subethasmtp - ${subethasmtp.version} - test - - - - org.webjars - bootstrap - ${bootstrap.version} - - - org.webjars - jquery - ${jquery.version} - - - - org.apache.tomcat - tomcat-servlet-api - ${tomee-servlet-api.version} - provided - - - - - - spring-boot - - - src/main/resources - true - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - - org.apache.maven.plugins - maven-war-plugin - - - - pl.project13.maven - git-commit-id-plugin - ${git-commit-id-plugin.version} - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*IntegrationTest.java - **/*LiveTest.java - - - - - - - - - - - integration - - - - org.apache.maven.plugins - maven-surefire-plugin - - - integration-test - - test - - - - **/*LiveTest.java - - - **/*IntegrationTest.java - - - - - - - json - - - - - - - - - - - UTF-8 - 1.8 - 4.3.7.RELEASE - 2.2.1 - 3.1.1 - 3.3.7-1 - 3.1.7 - 8.5.11 - - - diff --git a/spring-boot-auditing/src/main/java/org/baeldung/Application.java b/spring-boot-auditing/src/main/java/org/baeldung/Application.java deleted file mode 100755 index bf7b7bd1a6..0000000000 --- a/spring-boot-auditing/src/main/java/org/baeldung/Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.baeldung; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Application { - - public static void main(String[] args) throws Throwable { - SpringApplication.run(Application.class, args); - } - -} diff --git a/spring-boot-auditing/src/main/java/org/baeldung/MvcConfig.java b/spring-boot-auditing/src/main/java/org/baeldung/MvcConfig.java deleted file mode 100755 index fecb8c5c0b..0000000000 --- a/spring-boot-auditing/src/main/java/org/baeldung/MvcConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.baeldung; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -@Configuration -public class MvcConfig extends WebMvcConfigurerAdapter { - - @Override - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/home").setViewName("home"); - registry.addViewController("/").setViewName("home"); - registry.addViewController("/hello").setViewName("hello"); - registry.addViewController("/login").setViewName("login"); - } - -} diff --git a/spring-boot-auditing/src/main/java/org/baeldung/WebSecurityConfig.java b/spring-boot-auditing/src/main/java/org/baeldung/WebSecurityConfig.java deleted file mode 100755 index 9339d8e804..0000000000 --- a/spring-boot-auditing/src/main/java/org/baeldung/WebSecurityConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.baeldung; - -import org.springframework.beans.factory.annotation.Autowired; -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.WebSecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; - -@Configuration -@EnableWebSecurity -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .antMatchers("/", "/home").permitAll() - .anyRequest().authenticated() - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .and() - .logout() - .permitAll(); - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER", "ACTUATOR"); - } -} diff --git a/spring-boot-auditing/src/main/resources/application.properties b/spring-boot-auditing/src/main/resources/application.properties deleted file mode 100644 index cf09473b60..0000000000 --- a/spring-boot-auditing/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -logging.level.org.springframework=INFO \ No newline at end of file diff --git a/spring-boot-auditing/src/main/resources/logback.xml b/spring-boot-auditing/src/main/resources/logback.xml deleted file mode 100644 index 78913ee76f..0000000000 --- a/spring-boot-auditing/src/main/resources/logback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - web - %date [%thread] %-5level %logger{36} - %message%n - - - - - - - - - \ No newline at end of file diff --git a/spring-boot-auditing/src/main/resources/templates/hello.html b/spring-boot-auditing/src/main/resources/templates/hello.html deleted file mode 100755 index 46feef7e2c..0000000000 --- a/spring-boot-auditing/src/main/resources/templates/hello.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Hello World! - - -

Hello [[${#httpServletRequest.remoteUser}]]!

-
- -
- - \ No newline at end of file diff --git a/spring-boot-auditing/src/main/resources/templates/home.html b/spring-boot-auditing/src/main/resources/templates/home.html deleted file mode 100755 index fe4e8b337e..0000000000 --- a/spring-boot-auditing/src/main/resources/templates/home.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Spring Security Example - - -

Welcome!

- -

Click here to see a greeting.

- - \ No newline at end of file diff --git a/spring-boot-auditing/src/main/resources/templates/login.html b/spring-boot-auditing/src/main/resources/templates/login.html deleted file mode 100755 index a1785313f5..0000000000 --- a/spring-boot-auditing/src/main/resources/templates/login.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - Spring Security Example - - -
- Invalid username and password. -
-
- You have been logged out. -
-
-
-
-
-
- - \ No newline at end of file diff --git a/spring-security-core/pom.xml b/spring-security-core/pom.xml index a8ffce84b7..971f5a9d0f 100644 --- a/spring-security-core/pom.xml +++ b/spring-security-core/pom.xml @@ -32,6 +32,10 @@ org.springframework.boot spring-boot-starter-thymeleaf
+ + org.springframework.boot + spring-boot-starter-actuator + com.h2database h2 diff --git a/spring-boot-auditing/src/main/java/org/baeldung/auditing/ExposeAttemptedPathAuthorizationAuditListener.java b/spring-security-core/src/main/java/org/baeldung/auditing/ExposeAttemptedPathAuthorizationAuditListener.java similarity index 100% rename from spring-boot-auditing/src/main/java/org/baeldung/auditing/ExposeAttemptedPathAuthorizationAuditListener.java rename to spring-security-core/src/main/java/org/baeldung/auditing/ExposeAttemptedPathAuthorizationAuditListener.java diff --git a/spring-boot-auditing/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java b/spring-security-core/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java similarity index 72% rename from spring-boot-auditing/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java rename to spring-security-core/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java index 5be8cebfd3..bf0781bced 100644 --- a/spring-boot-auditing/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java +++ b/spring-security-core/src/main/java/org/baeldung/auditing/LoginAttemptsLogger.java @@ -15,11 +15,11 @@ public class LoginAttemptsLogger { @EventListener public void auditEventHappened(AuditApplicationEvent auditApplicationEvent) { AuditEvent auditEvent = auditApplicationEvent.getAuditEvent(); - LOGGER.debug("Principal " + auditEvent.getPrincipal() + " - " + auditEvent.getType()); + LOGGER.info("Principal " + auditEvent.getPrincipal() + " - " + auditEvent.getType()); WebAuthenticationDetails details = (WebAuthenticationDetails) auditEvent.getData().get("details"); - LOGGER.debug(" Remote IP address: " + details.getRemoteAddress()); - LOGGER.debug(" Session Id: " + details.getSessionId()); - LOGGER.debug(" Request URL: " + auditEvent.getData().get("requestUrl")); + LOGGER.info(" Remote IP address: " + details.getRemoteAddress()); + LOGGER.info(" Session Id: " + details.getSessionId()); + LOGGER.info(" Request URL: " + auditEvent.getData().get("requestUrl")); } } diff --git a/spring-security-core/src/main/java/org/baeldung/config/WebSecurityConfig.java b/spring-security-core/src/main/java/org/baeldung/config/WebSecurityConfig.java index 5441dac7f7..0b6cd34f3e 100644 --- a/spring-security-core/src/main/java/org/baeldung/config/WebSecurityConfig.java +++ b/spring-security-core/src/main/java/org/baeldung/config/WebSecurityConfig.java @@ -20,6 +20,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication().withUser("jim").password("jim").roles("USER").and().withUser("pam").password("pam").roles("USER").and().withUser("michael").password("michael").roles("MANAGER"); + auth.inMemoryAuthentication() + .withUser("jim").password("jim").roles("USER", "ACTUATOR") + .and().withUser("pam").password("pam").roles("USER") + .and().withUser("michael").password("michael").roles("MANAGER"); } } From 8fc519538dff4ddb6ed83164bdd8759759435120 Mon Sep 17 00:00:00 2001 From: slavisa-baeldung Date: Sun, 2 Apr 2017 10:07:37 +0200 Subject: [PATCH 132/149] Bael 112 validator upgrade (#1567) * BAEL-112 - upgrading hibernate validator * BAEL-112 - upgrading hibernate validator --- spring-mvc-java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-mvc-java/pom.xml b/spring-mvc-java/pom.xml index 0f6dbfbd98..dcf740b22b 100644 --- a/spring-mvc-java/pom.xml +++ b/spring-mvc-java/pom.xml @@ -176,7 +176,7 @@ org.hibernate hibernate-validator - 5.1.2.Final + ${hibernate-validator.version} javax.el @@ -370,7 +370,7 @@ 1.1.7 - 5.3.3.Final + 5.4.1.Final 3.1.0 1.2 From b4b5f79ce1ca5cb0f78a2cb8ae15d4dc920f97ee Mon Sep 17 00:00:00 2001 From: KevinGilmore Date: Sun, 2 Apr 2017 03:08:13 -0500 Subject: [PATCH 133/149] BAEL-666: README (#1563) * Add files via upload * Update pom.xml * Update RunGuice.java * Update Communication.java * Update CommunicationMode.java * Update DefaultCommunicator.java * Update EmailCommunicationMode.java * Update IMCommunicationMode.java * Update SMSCommunicationMode.java * Update MessageLogger.java * Update MessageSentLoggable.java * Update AOPModule.java * Update BasicModule.java * Update CommunicationModel.java * Update Communicator.java * Update BasicModule.java * Update RunGuice.java * Update MessageLogger.java * Update Communicator.java * Update pom.xml * BAEL-278: Updated README.md * BAEL-554: Add and update README.md files * Update pom.xml * Update pom.xml * Update pom.xml * BAEL-345: fixed assertion * BAEL-109: Updated README.md * BAEL-345: Added README.md * Reinstating reactor-core module in root-level pom * BAEL-393: Adding guide-intro module to root pom * BAEL-9: Updated README.md * BAEL-157: README.md updated * Changed project name * Update RunGuice.java Removed references to message logging and output * Update Communication.java Removed message logging-related code * BAEL-566: Updated README.md * New project name * BAEL-393: removing guice-intro directory * BAEL-393: renamed module guice-intro to guice in root pom.xml * BAEL-393 and BAEL-541 README.md files * BAEL-731: Updated README.md * BAEL-680: renamed test methods * BAEL-714: Updated README.md * BAEL-737: Updated README.md * BAEL-680 and BAEL-756 README.md updates * BAEL-666: Updated README --- jackson/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/jackson/README.md b/jackson/README.md index d9faa377f1..5bc16e66b7 100644 --- a/jackson/README.md +++ b/jackson/README.md @@ -26,3 +26,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [Inheritance with Jackson](http://www.baeldung.com/jackson-inheritance) - [Guide to @JsonFormat in Jackson](http://www.baeldung.com/jackson-jsonformat) - [A Guide to Optional with Jackson](http://www.baeldung.com/jackson-optional) +- [Map Serialization and Deserialization with Jackson](http://www.baeldung.com/jackson-map) From d85f1640d70ab55243d4677c17bb02c88443954c Mon Sep 17 00:00:00 2001 From: dhruba619 Date: Sun, 2 Apr 2017 16:35:58 +0530 Subject: [PATCH 134/149] BAEL-716 Junit vs testng improvement --- .../baeldung/junit4vstestng/SortedTests.java | 25 +++++++++++++++++++ .../test/java/baeldung/com/PriorityTest.java | 23 +++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java create mode 100644 testng/src/test/java/baeldung/com/PriorityTest.java diff --git a/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java b/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java new file mode 100644 index 0000000000..1fa4a4e61b --- /dev/null +++ b/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java @@ -0,0 +1,25 @@ +package com.baeldung.junit4vstestng; + +import static org.junit.Assert.assertTrue; + +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class SortedTests { + + @Test + public void a_givenString_whenChangedtoInt_thenTrue(){ + assertTrue( + Integer.valueOf("10") instanceof Integer); + } + + @Test + public void b_givenInt_whenChangedtoString_thenTrue(){ + assertTrue( + String.valueOf(10) instanceof String); + } + +} diff --git a/testng/src/test/java/baeldung/com/PriorityTest.java b/testng/src/test/java/baeldung/com/PriorityTest.java new file mode 100644 index 0000000000..34f2d6fe47 --- /dev/null +++ b/testng/src/test/java/baeldung/com/PriorityTest.java @@ -0,0 +1,23 @@ +package baeldung.com; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class PriorityTest { + + private String testString = "10"; + private int testInt = 23; + + @Test(priority = 1) + public void givenString_whenChangedToInt_thenCorrect() { + Assert.assertTrue( + Integer.valueOf(testString) instanceof Integer); + } + + @Test(priority = 2) + public void givenInt_whenChangedToString_thenCorrect() { + Assert.assertTrue( + String.valueOf(testInt) instanceof String); + } + +} From 8e060b9aafb5c172f1b6da6a9b0df8f1dfed0531 Mon Sep 17 00:00:00 2001 From: Nikhil Khatwani Date: Sun, 2 Apr 2017 20:18:21 +0530 Subject: [PATCH 135/149] Bael 711 guide to microservices using lagom framework v2 (#1573) * BAEL_711_Guide_to_Microservices_using_lagom_framework Changes * BAEL_711_Guide_to_Microservices_using_lagom_framework_v2 * BAEL_711_Guide_to_Microservices_using_lagom-Review Comments * Corrected scala version --- lagom/build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lagom/build.sbt b/lagom/build.sbt index 064d67468e..06927e3301 100644 --- a/lagom/build.sbt +++ b/lagom/build.sbt @@ -1,7 +1,7 @@ organization in ThisBuild := "org.baeldung" // the Scala version that will be used for cross-compiled libraries -scalaVersion in ThisBuild := "2.11.7" +scalaVersion in ThisBuild := "2.11.8" lagomKafkaEnabled in ThisBuild := false @@ -38,4 +38,4 @@ lazy val weatherImpl = project("weather-impl") ) .dependsOn(weatherApi) -def project(id: String) = Project(id, base = file(id)) \ No newline at end of file +def project(id: String) = Project(id, base = file(id)) From c3b73c5e568a703a482727574d5bf355ba942933 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sun, 2 Apr 2017 15:50:00 +0100 Subject: [PATCH 136/149] Bael 627 (#1431) * Initial commit * Spock examples * Changed Spock Version --- groovy-spock/pom.xml | 47 ++++ .../src/test/groovy/FirstSpecification.groovy | 89 +++++++ groovy-spock/src/test/groovy/Notifier.groovy | 3 + .../src/test/groovy/PaymentGateway.groovy | 3 + pom.xml | 1 + .../spring-custom-aop/.gitignore | 4 + spring-custom-aop/spring-custom-aop/README.MD | 13 + spring-custom-aop/spring-custom-aop/pom.xml | 224 ++++++++++++++++++ .../SpringBootAnnotatedApp.java | 25 ++ .../SpringBootPlainApp.java | 13 + .../components/AttrListener.java | 23 ++ .../components/EchoServlet.java | 29 +++ .../components/HelloFilter.java | 32 +++ .../components/HelloServlet.java | 32 +++ .../com/baeldung/git/CommitIdApplication.java | 23 ++ .../baeldung/git/CommitInfoController.java | 30 +++ .../src/main/java/com/baeldung/git/README.md | 2 + .../InternationalizationApp.java | 15 ++ .../config/MvcConfig.java | 38 +++ .../config/PageController.java | 14 ++ .../src/main/java/com/baeldung/intro/App.java | 13 + .../intro/controller/HomeController.java | 18 ++ .../baeldung/servlets/ApplicationMain.java | 19 ++ .../configuration/WebAppInitializer.java | 32 +++ .../configuration/WebMvcConfigure.java | 39 +++ .../baeldung/servlets/props/Constants.java | 20 ++ .../servlets/props/PropertyLoader.java | 27 +++ .../servlets/props/PropertySourcesLoader.java | 23 ++ .../servlets/GenericCustomServlet.java | 18 ++ .../servlets/javaee/AnnotationServlet.java | 20 ++ .../servlets/javaee/EEWebXmlServlet.java | 20 ++ .../SpringRegistrationBeanServlet.java | 17 ++ .../embedded/EmbeddedTomcatExample.java | 16 ++ .../java/com/baeldung/utils/Application.java | 18 ++ .../utils/controller/UtilsController.java | 49 ++++ .../com/baeldung/webjar/TestController.java | 15 ++ .../webjar/WebjarsdemoApplication.java | 13 + .../main/java/org/baeldung/Application.java | 13 + .../org/baeldung/boot/DemoApplication.java | 14 ++ .../baeldung/boot/components/FooService.java | 21 ++ .../boot/exceptions/CommonException.java | 13 + .../boot/exceptions/FooNotFoundException.java | 13 + .../java/org/baeldung/boot/model/Foo.java | 46 ++++ .../boot/repository/FooRepository.java | 8 + .../baeldung/boot/service/FooController.java | 26 ++ .../java/org/baeldung/client/Details.java | 32 +++ .../baeldung/client/DetailsServiceClient.java | 20 ++ .../common/error/MyCustomErrorController.java | 24 ++ .../SpringHelloServletRegistrationBean.java | 15 ++ .../error/controller/ErrorController.java | 22 ++ .../MyServletContainerCustomizationBean.java | 25 ++ .../ExecutorServiceExitCodeGenerator.java | 29 +++ .../java/org/baeldung/config/WebConfig.java | 17 ++ .../controller/GenericEntityController.java | 59 +++++ .../controller/servlet/HelloWorldServlet.java | 43 ++++ .../servlet/SpringHelloWorldServlet.java | 43 ++++ .../StringToEnumConverterFactory.java | 27 +++ .../StringToLocalDateTimeConverter.java | 16 ++ .../org/baeldung/domain/GenericEntity.java | 42 ++++ .../main/java/org/baeldung/domain/Modes.java | 6 + .../baeldung/endpoints/CustomEndpoint.java | 35 +++ .../org/baeldung/endpoints/ListEndpoints.java | 23 ++ .../org/baeldung/endpoints/MyHealthCheck.java | 22 ++ .../baeldung/main/SpringBootApplication.java | 68 ++++++ .../monitor/jmx/MonitoringConfig.java | 21 ++ .../repository/GenericEntityRepository.java | 7 + .../org/baeldung/service/LoginService.java | 5 + .../baeldung/service/LoginServiceImpl.java | 29 +++ .../session/exception/Application.java | 23 ++ .../exception/repository/FooRepository.java | 10 + .../repository/FooRepositoryImpl.java | 25 ++ .../HeaderVersionArgumentResolver.java | 26 ++ .../org/baeldung/web/resolver/Version.java | 11 + .../src/main/resources/application.properties | 43 ++++ .../src/main/resources/banner.txt | 14 ++ .../src/main/resources/custom.properties | 4 + .../src/main/resources/demo.properties | 6 + .../src/main/resources/logback.xml | 14 ++ .../src/main/resources/messages.properties | 4 + .../src/main/resources/messages_fr.properties | 4 + .../src/main/resources/public/error/404.html | 8 + .../src/main/resources/templates/index.html | 19 ++ .../resources/templates/international.html | 29 +++ .../src/main/resources/templates/other.html | 16 ++ .../src/main/resources/templates/utils.html | 23 ++ .../src/main/webapp/WEB-INF/context.xml | 12 + .../src/main/webapp/WEB-INF/dispatcher.xml | 16 ++ .../src/main/webapp/WEB-INF/web.xml | 40 ++++ .../src/main/webapp/annotationservlet.jsp | 1 + .../src/main/webapp/index.jsp | 1 + ...otWithServletComponentIntegrationTest.java | 65 +++++ ...ithoutServletComponentIntegrationTest.java | 50 ++++ .../baeldung/git/CommitIdIntegrationTest.java | 41 ++++ .../java/com/baeldung/intro/AppLiveTest.java | 41 ++++ .../baeldung/utils/UtilsControllerTest.java | 41 ++++ ...WebjarsdemoApplicationIntegrationTest.java | 18 ++ .../SpringBootApplicationIntegrationTest.java | 66 ++++++ .../SpringBootJPAIntegrationTest.java | 27 +++ .../SpringBootMailIntegrationTest.java | 81 +++++++ .../boot/ApplicationIntegrationTest.java | 17 ++ .../boot/DemoApplicationIntegrationTest.java | 17 ++ .../org/baeldung/boot/FooComponentTests.java | 70 ++++++ .../org/baeldung/boot/FooIntegrationTest.java | 43 ++++ .../java/org/baeldung/boot/FooJPATest.java | 34 +++ .../java/org/baeldung/boot/FooJsonTest.java | 35 +++ .../FooRepositoryIntegrationTest.java | 34 +++ .../HibernateSessionIntegrationTest.java | 32 +++ .../NoHibernateSessionIntegrationTest.java | 21 ++ .../DetailsServiceClientIntegrationTest.java | 46 ++++ .../src/test/resources/application.properties | 5 + .../resources/exception-hibernate.properties | 2 + .../src/test/resources/exception.properties | 6 + .../src/test/resources/import.sql | 1 + .../resources/org/baeldung/boot/expected.json | 4 + 114 files changed, 2967 insertions(+) create mode 100644 groovy-spock/pom.xml create mode 100644 groovy-spock/src/test/groovy/FirstSpecification.groovy create mode 100644 groovy-spock/src/test/groovy/Notifier.groovy create mode 100644 groovy-spock/src/test/groovy/PaymentGateway.groovy create mode 100644 spring-custom-aop/spring-custom-aop/.gitignore create mode 100644 spring-custom-aop/spring-custom-aop/README.MD create mode 100644 spring-custom-aop/spring-custom-aop/pom.xml create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootAnnotatedApp.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootPlainApp.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/AttrListener.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/EchoServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloFilter.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitIdApplication.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitInfoController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/README.md create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/InternationalizationApp.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/MvcConfig.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/PageController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/App.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/controller/HomeController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/ApplicationMain.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebMvcConfigure.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/Constants.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertyLoader.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertySourcesLoader.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/SpringRegistrationBeanServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/embedded/EmbeddedTomcatExample.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/Application.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/controller/UtilsController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/TestController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/WebjarsdemoApplication.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/Application.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/DemoApplication.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/components/FooService.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/CommonException.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/FooNotFoundException.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/model/Foo.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/repository/FooRepository.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/service/FooController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/Details.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/DetailsServiceClient.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/MyCustomErrorController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/SpringHelloServletRegistrationBean.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/controller/ErrorController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/properties/MyServletContainerCustomizationBean.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/resources/ExecutorServiceExitCodeGenerator.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/config/WebConfig.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/GenericEntityController.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/HelloWorldServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/SpringHelloWorldServlet.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToEnumConverterFactory.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToLocalDateTimeConverter.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/GenericEntity.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/Modes.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/CustomEndpoint.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/ListEndpoints.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/MyHealthCheck.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/main/SpringBootApplication.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/monitor/jmx/MonitoringConfig.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/repository/GenericEntityRepository.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginService.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginServiceImpl.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/Application.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepository.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepositoryImpl.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/HeaderVersionArgumentResolver.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/Version.java create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/application.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/banner.txt create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/custom.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/demo.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/logback.xml create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/messages.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/messages_fr.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/public/error/404.html create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/templates/index.html create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/templates/international.html create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/templates/other.html create mode 100644 spring-custom-aop/spring-custom-aop/src/main/resources/templates/utils.html create mode 100644 spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/context.xml create mode 100644 spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/dispatcher.xml create mode 100644 spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/web.xml create mode 100644 spring-custom-aop/spring-custom-aop/src/main/webapp/annotationservlet.jsp create mode 100644 spring-custom-aop/spring-custom-aop/src/main/webapp/index.jsp create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithServletComponentIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithoutServletComponentIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/git/CommitIdIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/intro/AppLiveTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/utils/UtilsControllerTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/webjar/WebjarsdemoApplicationIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootApplicationIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootJPAIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootMailIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/ApplicationIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/DemoApplicationIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooComponentTests.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJPATest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJsonTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/FooRepositoryIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/HibernateSessionIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/NoHibernateSessionIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/client/DetailsServiceClientIntegrationTest.java create mode 100644 spring-custom-aop/spring-custom-aop/src/test/resources/application.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/test/resources/exception-hibernate.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/test/resources/exception.properties create mode 100644 spring-custom-aop/spring-custom-aop/src/test/resources/import.sql create mode 100644 spring-custom-aop/spring-custom-aop/src/test/resources/org/baeldung/boot/expected.json diff --git a/groovy-spock/pom.xml b/groovy-spock/pom.xml new file mode 100644 index 0000000000..be84500b0d --- /dev/null +++ b/groovy-spock/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + org.spockframework + groovy-spock + 1.0-SNAPSHOT + jar + Spock Framework - Example Project + + + UTF-8 + UTF-8 + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.5 + + + + compile + testCompile + + + + + + + + + + org.spockframework + spock-core + 1.0-groovy-2.4 + test + + + org.codehaus.groovy + groovy-all + 2.4.7 + + + \ No newline at end of file diff --git a/groovy-spock/src/test/groovy/FirstSpecification.groovy b/groovy-spock/src/test/groovy/FirstSpecification.groovy new file mode 100644 index 0000000000..ed228899a2 --- /dev/null +++ b/groovy-spock/src/test/groovy/FirstSpecification.groovy @@ -0,0 +1,89 @@ +import spock.lang.Specification + +class FirstSpecification extends Specification { + + def "one plus one should equal two"() { + expect: + 1 + 1 == 2 + } + + def "two plus two should equal four"() { + given: + int left = 2 + int right = 2 + + when: + int result = left + right + + then: + result == 4 + } + + def "Should be able to remove from list"() { + given: + def list = [1, 2, 3, 4] + + when: + list.remove(0) + + then: + list == [2, 3, 4] + } + + def "Should get an index out of bounds when removing a non-existent item"() { + given: + def list = [1, 2, 3, 4] + + when: + list.remove(20) + + then: + thrown(IndexOutOfBoundsException) + list.size() == 4 + } + + def "numbers to the power of two"(int a, int b, int c) { + expect: + Math.pow(a, b) == c + + where: + a | b | c + 1 | 2 | 1 + 2 | 2 | 4 + 3 | 2 | 9 + } + + def "Should return default value for mock"() { + given: + def paymentGateway = Mock(PaymentGateway) + + when: + def result = paymentGateway.makePayment(12.99) + + then: + !result + } + + def "Should return true value for mock"() { + given: + def paymentGateway = Mock(PaymentGateway) + paymentGateway.makePayment(20) >> true + + when: + def result = paymentGateway.makePayment(20) + + then: + result + } + + def "Should verify notify was called"() { + given: + def notifier = Mock(Notifier) + + when: + notifier.notify('foo') + + then: + 1 * notifier.notify('foo') + } +} diff --git a/groovy-spock/src/test/groovy/Notifier.groovy b/groovy-spock/src/test/groovy/Notifier.groovy new file mode 100644 index 0000000000..d92d0f86ef --- /dev/null +++ b/groovy-spock/src/test/groovy/Notifier.groovy @@ -0,0 +1,3 @@ +interface Notifier { + void notify(String message) +} \ No newline at end of file diff --git a/groovy-spock/src/test/groovy/PaymentGateway.groovy b/groovy-spock/src/test/groovy/PaymentGateway.groovy new file mode 100644 index 0000000000..2a66e050f9 --- /dev/null +++ b/groovy-spock/src/test/groovy/PaymentGateway.groovy @@ -0,0 +1,3 @@ +interface PaymentGateway { + boolean makePayment(BigDecimal amount) +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 76f7247f51..c8d060ec04 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ + groovy-spock gson guava guava18 diff --git a/spring-custom-aop/spring-custom-aop/.gitignore b/spring-custom-aop/spring-custom-aop/.gitignore new file mode 100644 index 0000000000..60be5b80aa --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/.gitignore @@ -0,0 +1,4 @@ +/target/ +.settings/ +.classpath +.project diff --git a/spring-custom-aop/spring-custom-aop/README.MD b/spring-custom-aop/spring-custom-aop/README.MD new file mode 100644 index 0000000000..9fe18aaacc --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/README.MD @@ -0,0 +1,13 @@ +###The Course +The "REST With Spring" Classes: http://bit.ly/restwithspring + +###Relevant Articles: +- [Quick Guide to @RestClientTest in Spring Boot](http://www.baeldung.com/restclienttest-in-spring-boot) +- [Intro to Spring Boot Starters](http://www.baeldung.com/spring-boot-starters) +- [A Guide to Spring in Eclipse STS](http://www.baeldung.com/eclipse-sts-spring) +- [Introduction to WebJars](http://www.baeldung.com/maven-webjars) +- [Create a Fat Jar App with Spring Boot](http://www.baeldung.com/deployable-fat-jar-spring-boot) +- [The @ServletComponentScan Annotation in Spring Boot](http://www.baeldung.com/spring-servletcomponentscan) +- [A Custom Data Binder in Spring MVC](http://www.baeldung.com/spring-mvc-custom-data-binder) +- [Intro to Building an Application with Spring Boot](http://www.baeldung.com/intro-to-spring-boot) +- [How to Register a Servlet in a Java Web Application](http://www.baeldung.com/register-servlet) diff --git a/spring-custom-aop/spring-custom-aop/pom.xml b/spring-custom-aop/spring-custom-aop/pom.xml new file mode 100644 index 0000000000..bab6f1f101 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/pom.xml @@ -0,0 +1,224 @@ + + 4.0.0 + com.baeldung + spring-boot + 0.0.1-SNAPSHOT + war + spring-boot + This is simple boot application for Spring boot actuator test + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.2.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + + org.apache.tomcat.embed + tomcat-embed-jasper + ${tomcat.version} + + + + io.dropwizard.metrics + metrics-core + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter + + + com.jayway.jsonpath + json-path + test + + + org.springframework.boot + spring-boot-starter-mail + + + org.subethamail + subethasmtp + ${subethasmtp.version} + test + + + + org.webjars + bootstrap + ${bootstrap.version} + + + org.webjars + jquery + ${jquery.version} + + + + com.google.guava + guava + 18.0 + + + + org.apache.tomcat + tomcat-servlet-api + ${tomee-servlet-api.version} + provided + + + + + + spring-boot + + + src/main/resources + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-war-plugin + + + + pl.project13.maven + git-commit-id-plugin + ${git-commit-id-plugin.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + + + + + + + + integration + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*LiveTest.java + + + **/*IntegrationTest.java + + + + + + + json + + + + + + + + + + + + org.baeldung.boot.DemoApplication + UTF-8 + 1.8 + 4.3.4.RELEASE + 2.2.1 + 3.1.1 + 3.3.7-1 + 3.1.7 + 8.5.11 + + + diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootAnnotatedApp.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootAnnotatedApp.java new file mode 100644 index 0000000000..b4d416dd96 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootAnnotatedApp.java @@ -0,0 +1,25 @@ +package com.baeldung.annotation.servletcomponentscan; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * using the following annotations are equivalent: + *
  • + * @ServletComponentScan + *
  • + * @ServletComponentScan(basePackages = "com.baeldung.annotation.servletcomponentscan.components") + *
  • + * @ServletComponentScan(basePackageClasses = {AttrListener.class, HelloFilter.class, HelloServlet.class, EchoServlet.class}) + *
+ */ +@SpringBootApplication +@ServletComponentScan("com.baeldung.annotation.servletcomponentscan.components") +public class SpringBootAnnotatedApp { + + public static void main(String[] args) { + SpringApplication.run(SpringBootAnnotatedApp.class, args); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootPlainApp.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootPlainApp.java new file mode 100644 index 0000000000..8a39078aac --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/SpringBootPlainApp.java @@ -0,0 +1,13 @@ +package com.baeldung.annotation.servletcomponentscan; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = "com.baeldung.annotation.servletcomponentscan.components") +public class SpringBootPlainApp { + + public static void main(String[] args) { + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/AttrListener.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/AttrListener.java new file mode 100644 index 0000000000..bad39c52c4 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/AttrListener.java @@ -0,0 +1,23 @@ +package com.baeldung.annotation.servletcomponentscan.components; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +public class AttrListener implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + servletContextEvent + .getServletContext() + .setAttribute("servlet-context-attr", "test"); + System.out.println("context init"); + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + System.out.println("context destroy"); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/EchoServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/EchoServlet.java new file mode 100644 index 0000000000..3419cd0eaf --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/EchoServlet.java @@ -0,0 +1,29 @@ +package com.baeldung.annotation.servletcomponentscan.components; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +@WebServlet(name = "echo servlet", urlPatterns = "/echo") +public class EchoServlet extends HttpServlet { + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) { + try { + Path path = File + .createTempFile("echo", "tmp") + .toPath(); + Files.copy(request.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING); + Files.copy(path, response.getOutputStream()); + Files.delete(path); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloFilter.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloFilter.java new file mode 100644 index 0000000000..dc2368c5b2 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloFilter.java @@ -0,0 +1,32 @@ +package com.baeldung.annotation.servletcomponentscan.components; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebInitParam; +import java.io.IOException; + +@WebFilter(urlPatterns = "/hello", description = "a filter for hello servlet", initParams = { @WebInitParam(name = "msg", value = "filtering ") }, filterName = "hello filter", servletNames = { "echo servlet" }) +public class HelloFilter implements Filter { + + private FilterConfig filterConfig; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + System.out.println("filter init"); + this.filterConfig = filterConfig; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + servletResponse + .getOutputStream() + .print(filterConfig.getInitParameter("msg")); + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public void destroy() { + System.out.println("filter destroy"); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloServlet.java new file mode 100644 index 0000000000..aeae7aecc9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/annotation/servletcomponentscan/components/HelloServlet.java @@ -0,0 +1,32 @@ +package com.baeldung.annotation.servletcomponentscan.components; + +import javax.servlet.ServletConfig; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(urlPatterns = "/hello", initParams = { @WebInitParam(name = "msg", value = "hello")}) +public class HelloServlet extends HttpServlet { + + private ServletConfig servletConfig; + + @Override + public void init(ServletConfig servletConfig){ + this.servletConfig = servletConfig; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + response + .getOutputStream() + .write(servletConfig.getInitParameter("msg").getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitIdApplication.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitIdApplication.java new file mode 100644 index 0000000000..cd696eae70 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitIdApplication.java @@ -0,0 +1,23 @@ +package com.baeldung.git; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.io.ClassPathResource; + +@SpringBootApplication(scanBasePackages = { "com.baeldung.git" }) +public class CommitIdApplication { + public static void main(String[] args) { + SpringApplication.run(CommitIdApplication.class, args); + } + + @Bean + public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + PropertySourcesPlaceholderConfigurer c = new PropertySourcesPlaceholderConfigurer(); + c.setLocation(new ClassPathResource("git.properties")); + c.setIgnoreResourceNotFound(true); + c.setIgnoreUnresolvablePlaceholders(true); + return c; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitInfoController.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitInfoController.java new file mode 100644 index 0000000000..6d44e02ec2 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/CommitInfoController.java @@ -0,0 +1,30 @@ +package com.baeldung.git; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class CommitInfoController { + + @Value("${git.commit.message.short}") + private String commitMessage; + + @Value("${git.branch}") + private String branch; + + @Value("${git.commit.id}") + private String commitId; + + @RequestMapping("/commitId") + public Map getCommitId() { + Map result = new HashMap<>(); + result.put("Commit message", commitMessage); + result.put("Commit branch", branch); + result.put("Commit id", commitId); + return result; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/README.md b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/README.md new file mode 100644 index 0000000000..7e6a597c28 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/git/README.md @@ -0,0 +1,2 @@ +### Relevant Articles: +- [Injecting Git Information Into Spring](http://www.baeldung.com/spring-git-information) diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/InternationalizationApp.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/InternationalizationApp.java new file mode 100644 index 0000000000..c92d1c32e6 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/InternationalizationApp.java @@ -0,0 +1,15 @@ +package com.baeldung.internationalization; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class InternationalizationApp { + @RolesAllowed("*") + public static void main(String[] args) { + System.setProperty("security.basic.enabled", "false"); + SpringApplication.run(InternationalizationApp.class, args); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/MvcConfig.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/MvcConfig.java new file mode 100644 index 0000000000..59f7fd3ba5 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/MvcConfig.java @@ -0,0 +1,38 @@ +package com.baeldung.internationalization.config; + +import java.util.Locale; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.baeldung.internationalization.config") +public class MvcConfig extends WebMvcConfigurerAdapter { + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver slr = new SessionLocaleResolver(); + slr.setDefaultLocale(Locale.US); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/PageController.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/PageController.java new file mode 100644 index 0000000000..96a534b853 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/internationalization/config/PageController.java @@ -0,0 +1,14 @@ +package com.baeldung.internationalization.config; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class PageController { + + @GetMapping("/international") + public String getInternationalPage() { + return "international"; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/App.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/App.java new file mode 100644 index 0000000000..3db5d3256e --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/App.java @@ -0,0 +1,13 @@ +package com.baeldung.intro; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App +{ + public static void main( String[] args ) + { + SpringApplication.run(App.class, args); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/controller/HomeController.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/controller/HomeController.java new file mode 100644 index 0000000000..2a7111135c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/intro/controller/HomeController.java @@ -0,0 +1,18 @@ +package com.baeldung.intro.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HomeController { + + @RequestMapping("/") + public String root(){ + return "Index Page"; + } + + @RequestMapping("/local") + public String local(){ + return "/local"; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/ApplicationMain.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/ApplicationMain.java new file mode 100644 index 0000000000..a6ea3757fe --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/ApplicationMain.java @@ -0,0 +1,19 @@ +package com.baeldung.servlets; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.support.SpringBootServletInitializer; + +@SpringBootApplication +public class ApplicationMain extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(ApplicationMain.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(ApplicationMain.class); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java new file mode 100644 index 0000000000..eadd40355a --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebAppInitializer.java @@ -0,0 +1,32 @@ +package com.baeldung.servlets.configuration; + +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.XmlWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +public class WebAppInitializer implements WebApplicationInitializer { + + public void onStartup(ServletContext container) throws ServletException { + + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.register(WebMvcConfigure.class); + ctx.setServletContext(container); + + ServletRegistration.Dynamic servletOne = container.addServlet("SpringProgrammaticDispatcherServlet", new DispatcherServlet(ctx)); + servletOne.setLoadOnStartup(1); + servletOne.addMapping("/"); + + XmlWebApplicationContext xctx = new XmlWebApplicationContext(); + xctx.setConfigLocation("/WEB-INF/context.xml"); + xctx.setServletContext(container); + + ServletRegistration.Dynamic servletTwo = container.addServlet("SpringProgrammaticXMLDispatcherServlet", new DispatcherServlet(xctx)); + servletTwo.setLoadOnStartup(1); + servletTwo.addMapping("/"); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebMvcConfigure.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebMvcConfigure.java new file mode 100644 index 0000000000..3d6a10c2ac --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/configuration/WebMvcConfigure.java @@ -0,0 +1,39 @@ +package com.baeldung.servlets.configuration; + +import org.springframework.boot.web.support.ErrorPageFilter; +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.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.resource.PathResourceResolver; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +@Configuration +public class WebMvcConfigure extends WebMvcConfigurerAdapter { + + @Bean + public ViewResolver getViewResolver() { + InternalResourceViewResolver resolver = new InternalResourceViewResolver(); + resolver.setPrefix("/WEB-INF/"); + resolver.setSuffix(".jsp"); + return resolver; + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/").setCachePeriod(3600).resourceChain(true).addResolver(new PathResourceResolver()); + } + + @Bean + public ErrorPageFilter errorPageFilter() { + return new ErrorPageFilter(); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/Constants.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/Constants.java new file mode 100644 index 0000000000..6345d1f969 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/Constants.java @@ -0,0 +1,20 @@ +package com.baeldung.servlets.props; + +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Properties; + +public final class Constants { + + @Autowired + PropertySourcesLoader psl; + + public static final String breakLine = System.getProperty("line.separator"); + private static final PropertyLoader pl = new PropertyLoader(); + private static final Properties mainProps = pl.getProperties("custom.properties"); + public static final String DISPATCHER_SERVLET_NAME = mainProps.getProperty("dispatcher.servlet.name"); + public static final String DISPATCHER_SERVLET_MAPPING = mainProps.getProperty("dispatcher.servlet.mapping"); + private final String EXAMPLE_SERVLET_NAME = psl.getProperty("example.servlet.name"); + private final String EXAMPLE_SERVLET_MAPPING = psl.getProperty("example.servlet.mapping"); + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertyLoader.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertyLoader.java new file mode 100644 index 0000000000..c29da45929 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertyLoader.java @@ -0,0 +1,27 @@ +package com.baeldung.servlets.props; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class PropertyLoader { + private static final Logger log = LoggerFactory.getLogger(PropertyLoader.class); + + public Properties getProperties(String file) { + Properties prop = new Properties(); + InputStream input = null; + try { + input = getClass().getResourceAsStream(file); + prop.load(input); + if (input != null) { + input.close(); + } + } catch (IOException ex) { + log.error("IOException: " + ex); + } + return prop; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertySourcesLoader.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertySourcesLoader.java new file mode 100644 index 0000000000..56a6751326 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/props/PropertySourcesLoader.java @@ -0,0 +1,23 @@ +package com.baeldung.servlets.props; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.ConfigurableEnvironment; + +@Configuration +@ComponentScan(basePackages = { "com.baeldung.*" }) +@PropertySource("classpath:custom.properties") public class PropertySourcesLoader { + + private static final Logger log = LoggerFactory.getLogger(PropertySourcesLoader.class); + + @Autowired + ConfigurableEnvironment env; + + public String getProperty(String key) { + return env.getProperty(key); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java new file mode 100644 index 0000000000..49dd9404b7 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/GenericCustomServlet.java @@ -0,0 +1,18 @@ +package com.baeldung.servlets.servlets; + +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.io.PrintWriter; + +public class GenericCustomServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("

Hello World

"); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java new file mode 100644 index 0000000000..b50a7d5454 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/AnnotationServlet.java @@ -0,0 +1,20 @@ +package com.baeldung.servlets.servlets.javaee; + +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 java.io.IOException; + +@WebServlet(name = "AnnotationServlet", + description = "Example Servlet Using Annotations", + urlPatterns = { "/annotationservlet" }) +public class AnnotationServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/annotationservlet.jsp").forward(request, response); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java new file mode 100644 index 0000000000..c7b373064f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/javaee/EEWebXmlServlet.java @@ -0,0 +1,20 @@ +package com.baeldung.servlets.servlets.javaee; + +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.io.PrintWriter; + +public class EEWebXmlServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("

Hello World

"); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/SpringRegistrationBeanServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/SpringRegistrationBeanServlet.java new file mode 100644 index 0000000000..e3c225d429 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/SpringRegistrationBeanServlet.java @@ -0,0 +1,17 @@ +package com.baeldung.servlets.servlets.springboot; + +import com.baeldung.servlets.servlets.GenericCustomServlet; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringRegistrationBeanServlet { + + @Bean + public ServletRegistrationBean genericCustomServlet() { + ServletRegistrationBean bean = new ServletRegistrationBean(new GenericCustomServlet(), "/springregistrationbeanservlet/*"); + bean.setLoadOnStartup(1); + return bean; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/embedded/EmbeddedTomcatExample.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/embedded/EmbeddedTomcatExample.java new file mode 100644 index 0000000000..9e460d03a8 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/servlets/servlets/springboot/embedded/EmbeddedTomcatExample.java @@ -0,0 +1,16 @@ +package com.baeldung.servlets.servlets.springboot.embedded; + +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EmbeddedTomcatExample { + + @Bean + public EmbeddedServletContainerFactory servletContainer() { + TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); + return tomcat; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/Application.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/Application.java new file mode 100644 index 0000000000..a3d9f9130c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/Application.java @@ -0,0 +1,18 @@ +package com.baeldung.utils; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages="com.baeldung.utils") +public class Application { + + @RolesAllowed("*") + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/controller/UtilsController.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/controller/UtilsController.java new file mode 100644 index 0000000000..7b4827cdf2 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/utils/controller/UtilsController.java @@ -0,0 +1,49 @@ +package com.baeldung.utils.controller; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.ServletRequestUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.util.WebUtils; + +@Controller +public class UtilsController { + + @GetMapping("/utils") + public String webUtils(Model model) { + return "utils"; + } + + @PostMapping("/setParam") + public String post(HttpServletRequest request, Model model) { + String param = ServletRequestUtils.getStringParameter(request, "param", "DEFAULT"); + +// Long param = ServletRequestUtils.getLongParameter(request, "param",1L); +// boolean param = ServletRequestUtils.getBooleanParameter(request, "param", true); +// double param = ServletRequestUtils.getDoubleParameter(request, "param", 1000); +// float param = ServletRequestUtils.getFloatParameter(request, "param", (float) 1.00); +// int param = ServletRequestUtils.getIntParameter(request, "param", 100); + +// try { +// ServletRequestUtils.getRequiredStringParameter(request, "param"); +// } catch (ServletRequestBindingException e) { +// e.printStackTrace(); +// } + + WebUtils.setSessionAttribute(request, "parameter", param); + model.addAttribute("parameter", "You set: "+(String) WebUtils.getSessionAttribute(request, "parameter")); + return "utils"; + } + + @GetMapping("/other") + public String other(HttpServletRequest request, Model model) { + String param = (String) WebUtils.getSessionAttribute(request, "parameter"); + model.addAttribute("parameter", param); + return "other"; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/TestController.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/TestController.java new file mode 100644 index 0000000000..e8e7fd5ce9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/TestController.java @@ -0,0 +1,15 @@ +package com.baeldung.webjar; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class TestController { + + @RequestMapping(value = "/") + public String welcome(Model model) { + return "index"; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/WebjarsdemoApplication.java b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/WebjarsdemoApplication.java new file mode 100644 index 0000000000..d2135754c9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/com/baeldung/webjar/WebjarsdemoApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.webjar; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +public class WebjarsdemoApplication { + + public static void main(String[] args) { + SpringApplication.run(WebjarsdemoApplication.class, args); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/Application.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..aae0c427a9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/Application.java @@ -0,0 +1,13 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContext; + +@org.springframework.boot.autoconfigure.SpringBootApplication +public class Application { + private static ApplicationContext applicationContext; + + public static void main(String[] args) { + applicationContext = SpringApplication.run(Application.class, args); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/DemoApplication.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/DemoApplication.java new file mode 100644 index 0000000000..e61d140396 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/DemoApplication.java @@ -0,0 +1,14 @@ +package org.baeldung.boot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + System.setProperty("spring.config.name", "demo"); + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/components/FooService.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/components/FooService.java new file mode 100644 index 0000000000..235fd43299 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/components/FooService.java @@ -0,0 +1,21 @@ +package org.baeldung.boot.components; + +import org.baeldung.boot.model.Foo; +import org.baeldung.boot.repository.FooRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class FooService { + + @Autowired + private FooRepository fooRepository; + + public Foo getFooWithId(Integer id) throws Exception { + return fooRepository.findOne(id); + } + + public Foo getFooWithName(String name) { + return fooRepository.findByName(name); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/CommonException.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/CommonException.java new file mode 100644 index 0000000000..1f008440e6 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/CommonException.java @@ -0,0 +1,13 @@ +package org.baeldung.boot.exceptions; + +public class CommonException extends RuntimeException{ + + /** + * + */ + private static final long serialVersionUID = 3080004140659213332L; + + public CommonException(String message){ + super(message); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/FooNotFoundException.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/FooNotFoundException.java new file mode 100644 index 0000000000..68ef3fa389 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/exceptions/FooNotFoundException.java @@ -0,0 +1,13 @@ +package org.baeldung.boot.exceptions; + +public class FooNotFoundException extends RuntimeException{ + + /** + * + */ + private static final long serialVersionUID = 9042200028456133589L; + + public FooNotFoundException(String message){ + super(message); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/model/Foo.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/model/Foo.java new file mode 100644 index 0000000000..ac8a8fe429 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/model/Foo.java @@ -0,0 +1,46 @@ +package org.baeldung.boot.model; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Foo implements Serializable { + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue + private Integer id; + private String name; + + public Foo() { + } + + public Foo(String name) { + this.name = name; + } + + + public Foo(Integer id, String name) { + super(); + this.id = id; + this.name = name; + } + + 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/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/repository/FooRepository.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/repository/FooRepository.java new file mode 100644 index 0000000000..09d6975dba --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/repository/FooRepository.java @@ -0,0 +1,8 @@ +package org.baeldung.boot.repository; + +import org.baeldung.boot.model.Foo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FooRepository extends JpaRepository { + public Foo findByName(String name); +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/service/FooController.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/service/FooController.java new file mode 100644 index 0000000000..834fa342e2 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/boot/service/FooController.java @@ -0,0 +1,26 @@ +package org.baeldung.boot.service; + +import org.baeldung.boot.components.FooService; +import org.baeldung.boot.model.Foo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class FooController { + + @Autowired + private FooService fooService; + + @GetMapping("/{id}") + public Foo getFooWithId(@PathVariable Integer id) throws Exception { + return fooService.getFooWithId(id); + } + + @GetMapping("/") + public Foo getFooWithName(@RequestParam String name) throws Exception { + return fooService.getFooWithName(name); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/Details.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/Details.java new file mode 100644 index 0000000000..2ae3adc38f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/Details.java @@ -0,0 +1,32 @@ +package org.baeldung.client; + +public class Details { + + private String name; + + private String login; + + public Details() { + } + + public Details(String name, String login) { + this.name = name; + this.login = login; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/DetailsServiceClient.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/DetailsServiceClient.java new file mode 100644 index 0000000000..51fa7c6181 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/client/DetailsServiceClient.java @@ -0,0 +1,20 @@ +package org.baeldung.client; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class DetailsServiceClient { + + private final RestTemplate restTemplate; + + public DetailsServiceClient(RestTemplateBuilder restTemplateBuilder) { + restTemplate = restTemplateBuilder.build(); + } + + public Details getUserDetails(String name) { + return restTemplate.getForObject("/{name}/details", Details.class, name); + } + +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/MyCustomErrorController.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/MyCustomErrorController.java new file mode 100644 index 0000000000..a50b88f94b --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/MyCustomErrorController.java @@ -0,0 +1,24 @@ +package org.baeldung.common.error; + +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.web.bind.annotation.RequestMapping; + +public class MyCustomErrorController implements ErrorController { + + private static final String PATH = "/error"; + + public MyCustomErrorController() { + // TODO Auto-generated constructor stub + } + + @RequestMapping(value = PATH) + public String error() { + return "Error heaven"; + } + + @Override + public String getErrorPath() { + return PATH; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/SpringHelloServletRegistrationBean.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/SpringHelloServletRegistrationBean.java new file mode 100644 index 0000000000..774cf1b970 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/SpringHelloServletRegistrationBean.java @@ -0,0 +1,15 @@ +package org.baeldung.common.error; + +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +import javax.servlet.Servlet; + +public class SpringHelloServletRegistrationBean extends ServletRegistrationBean { + + public SpringHelloServletRegistrationBean() { + } + + public SpringHelloServletRegistrationBean(Servlet servlet, String... urlMappings) { + super(servlet, urlMappings); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/controller/ErrorController.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/controller/ErrorController.java new file mode 100644 index 0000000000..9e63418a02 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/error/controller/ErrorController.java @@ -0,0 +1,22 @@ +package org.baeldung.common.error.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ErrorController { + + public ErrorController() { + } + + @RequestMapping("/400") + String error400() { + return "Error Code: 400 occured."; + } + + @RequestMapping("/errorHeaven") + String errorHeaven() { + return "You have reached the heaven of errors!!!"; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/properties/MyServletContainerCustomizationBean.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/properties/MyServletContainerCustomizationBean.java new file mode 100644 index 0000000000..3d239f8944 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/properties/MyServletContainerCustomizationBean.java @@ -0,0 +1,25 @@ +package org.baeldung.common.properties; + +import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.boot.web.servlet.ErrorPage; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +@Component +public class MyServletContainerCustomizationBean implements EmbeddedServletContainerCustomizer { + + public MyServletContainerCustomizationBean() { + + } + + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + container.setPort(8084); + container.setContextPath("/springbootapp"); + + container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); + container.addErrorPages(new ErrorPage("/errorHeaven")); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/resources/ExecutorServiceExitCodeGenerator.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/resources/ExecutorServiceExitCodeGenerator.java new file mode 100644 index 0000000000..07f57ec1ef --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/common/resources/ExecutorServiceExitCodeGenerator.java @@ -0,0 +1,29 @@ +package org.baeldung.common.resources; + +import java.util.Objects; +import java.util.concurrent.ExecutorService; + +import org.springframework.boot.ExitCodeGenerator; + +public class ExecutorServiceExitCodeGenerator implements ExitCodeGenerator { + + private ExecutorService executorService; + + public ExecutorServiceExitCodeGenerator(ExecutorService executorService) { + } + + @Override + public int getExitCode() { + int returnCode = 0; + try { + if (!Objects.isNull(executorService)) { + executorService.shutdownNow(); + returnCode = 1; + } + } catch (SecurityException ex) { + returnCode = 0; + } + return returnCode; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/config/WebConfig.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/config/WebConfig.java new file mode 100644 index 0000000000..4ef407823e --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/config/WebConfig.java @@ -0,0 +1,17 @@ +package org.baeldung.config; + +import org.baeldung.web.resolver.HeaderVersionArgumentResolver; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import java.util.List; + +@Configuration +public class WebConfig extends WebMvcConfigurerAdapter { + + @Override + public void addArgumentResolvers(final List argumentResolvers) { + argumentResolvers.add(new HeaderVersionArgumentResolver()); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/GenericEntityController.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/GenericEntityController.java new file mode 100644 index 0000000000..7d1ad7d899 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/GenericEntityController.java @@ -0,0 +1,59 @@ +package org.baeldung.controller; + +import org.baeldung.domain.GenericEntity; +import org.baeldung.domain.Modes; +import org.baeldung.web.resolver.Version; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@RestController +public class GenericEntityController { + private List entityList = new ArrayList<>(); + + { + entityList.add(new GenericEntity(1l, "entity_1")); + entityList.add(new GenericEntity(2l, "entity_2")); + entityList.add(new GenericEntity(3l, "entity_3")); + entityList.add(new GenericEntity(4l, "entity_4")); + } + + @RequestMapping("/entity/all") + public List findAll() { + return entityList; + } + + @RequestMapping(value = "/entity", method = RequestMethod.POST) + public GenericEntity addEntity(GenericEntity entity) { + entityList.add(entity); + return entity; + } + + @RequestMapping("/entity/findby/{id}") + public GenericEntity findById(@PathVariable Long id) { + return entityList.stream().filter(entity -> entity.getId().equals(id)).findFirst().get(); + } + + @GetMapping("/entity/findbydate/{date}") + public GenericEntity findByDate(@PathVariable("date") LocalDateTime date) { + return entityList.stream().findFirst().get(); + } + + @GetMapping("/entity/findbymode/{mode}") + public GenericEntity findByEnum(@PathVariable("mode") Modes mode) { + return entityList.stream().findFirst().get(); + } + + @GetMapping("/entity/findbyversion") + public ResponseEntity findByVersion(@Version String version) { + return version != null ? new ResponseEntity(entityList.stream().findFirst().get(), HttpStatus.OK) : new ResponseEntity(HttpStatus.NOT_FOUND); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/HelloWorldServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/HelloWorldServlet.java new file mode 100644 index 0000000000..fc8fefd77e --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/HelloWorldServlet.java @@ -0,0 +1,43 @@ +package org.baeldung.controller.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class HelloWorldServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public HelloWorldServlet() { + super(); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("HelloWorldServlet: GET METHOD"); + out.flush(); + } finally { + if (!Objects.isNull(out)) + out.close(); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("HelloWorldServlet: POST METHOD"); + out.flush(); + } finally { + if (!Objects.isNull(out)) + out.close(); + } + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/SpringHelloWorldServlet.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/SpringHelloWorldServlet.java new file mode 100644 index 0000000000..16cff5b1fa --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/controller/servlet/SpringHelloWorldServlet.java @@ -0,0 +1,43 @@ +package org.baeldung.controller.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class SpringHelloWorldServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public SpringHelloWorldServlet() { + super(); + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("SpringHelloWorldServlet: GET METHOD"); + out.flush(); + } finally { + if (!Objects.isNull(out)) + out.close(); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("SpringHelloWorldServlet: POST METHOD"); + out.flush(); + } finally { + if (!Objects.isNull(out)) + out.close(); + } + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToEnumConverterFactory.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToEnumConverterFactory.java new file mode 100644 index 0000000000..17c6fd06de --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToEnumConverterFactory.java @@ -0,0 +1,27 @@ +package org.baeldung.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.stereotype.Component; + +@Component +public class StringToEnumConverterFactory implements ConverterFactory { + + private static class StringToEnumConverter implements Converter { + + private Class enumType; + + public StringToEnumConverter(Class enumType) { + this.enumType = enumType; + } + + public T convert(String source) { + return (T) Enum.valueOf(this.enumType, source.trim()); + } + } + + @Override + public Converter getConverter(final Class targetType) { + return new StringToEnumConverter(targetType); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToLocalDateTimeConverter.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToLocalDateTimeConverter.java new file mode 100644 index 0000000000..cbb9e6ddb4 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/converter/StringToLocalDateTimeConverter.java @@ -0,0 +1,16 @@ +package org.baeldung.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Component +public class StringToLocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(final String source) { + return LocalDateTime.parse(source, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/GenericEntity.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/GenericEntity.java new file mode 100644 index 0000000000..7b1d27cb66 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/GenericEntity.java @@ -0,0 +1,42 @@ +package org.baeldung.domain; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class GenericEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + private String value; + + public GenericEntity() { + } + + public GenericEntity(String value) { + this.value = value; + } + + public GenericEntity(Long id, String value) { + this.id = id; + this.value = value; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/Modes.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/Modes.java new file mode 100644 index 0000000000..473406ef26 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/domain/Modes.java @@ -0,0 +1,6 @@ +package org.baeldung.domain; + +public enum Modes { + + ALPHA, BETA; +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/CustomEndpoint.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/CustomEndpoint.java new file mode 100644 index 0000000000..222a54c6ef --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/CustomEndpoint.java @@ -0,0 +1,35 @@ +package org.baeldung.endpoints; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.stereotype.Component; + +@Component +public class CustomEndpoint implements Endpoint> { + + public CustomEndpoint() { + + } + + public String getId() { + return "customEndpoint"; + } + + public boolean isEnabled() { + return true; + } + + public boolean isSensitive() { + return true; + } + + public List invoke() { + // Your logic to display the output + List messages = new ArrayList(); + messages.add("This is message 1"); + messages.add("This is message 2"); + return messages; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/ListEndpoints.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/ListEndpoints.java new file mode 100644 index 0000000000..0add741a1f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/ListEndpoints.java @@ -0,0 +1,23 @@ +package org.baeldung.endpoints; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.stereotype.Component; + +@Component +public class ListEndpoints extends AbstractEndpoint> { + private List endpoints; + + @Autowired + public ListEndpoints(List endpoints) { + super("listEndpoints"); + this.endpoints = endpoints; + } + + public List invoke() { + return this.endpoints; + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/MyHealthCheck.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/MyHealthCheck.java new file mode 100644 index 0000000000..4410a02d47 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/endpoints/MyHealthCheck.java @@ -0,0 +1,22 @@ +package org.baeldung.endpoints; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +@Component +public class MyHealthCheck implements HealthIndicator { + + public Health health() { + int errorCode = check(); // perform some specific health check + if (errorCode != 0) { + return Health.down().withDetail("Error Code", errorCode).withDetail("Description", "You custom MyHealthCheck endpoint is down").build(); + } + return Health.up().build(); + } + + public int check() { + // Your logic to check health + return 1; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/main/SpringBootApplication.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/main/SpringBootApplication.java new file mode 100644 index 0000000000..b828a5b841 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/main/SpringBootApplication.java @@ -0,0 +1,68 @@ +package org.baeldung.main; + +import org.baeldung.common.error.SpringHelloServletRegistrationBean; +import org.baeldung.common.resources.ExecutorServiceExitCodeGenerator; +import org.baeldung.controller.servlet.HelloWorldServlet; +import org.baeldung.controller.servlet.SpringHelloWorldServlet; +import org.baeldung.service.LoginService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@RestController +@EnableAutoConfiguration +@ComponentScan({ "org.baeldung.common.error", "org.baeldung.common.error.controller", "org.baeldung.common.properties", "org.baeldung.common.resources", "org.baeldung.endpoints", "org.baeldung.service", "org.baeldung.monitor.jmx", "org.baeldung.service" }) +public class SpringBootApplication { + + private static ApplicationContext applicationContext; + + @Autowired + private LoginService service; + + @RequestMapping("/") + String home() { + service.login("admin", "admin".toCharArray()); + return "TADA!!! You are in Spring Boot Actuator test application."; + } + + public static void main(String[] args) { + applicationContext = SpringApplication.run(SpringBootApplication.class, args); + } + + @Bean + public ExecutorService executorService() { + return Executors.newFixedThreadPool(10); + } + + @Bean + public HelloWorldServlet helloWorldServlet() { + return new HelloWorldServlet(); + } + + @Bean + public SpringHelloServletRegistrationBean servletRegistrationBean() { + SpringHelloServletRegistrationBean bean = new SpringHelloServletRegistrationBean(new SpringHelloWorldServlet(), "/springHelloWorld/*"); + bean.setLoadOnStartup(1); + bean.addInitParameter("message", "SpringHelloWorldServlet special message"); + return bean; + } + + @Bean + @Autowired + public ExecutorServiceExitCodeGenerator executorServiceExitCodeGenerator(ExecutorService executorService) { + return new ExecutorServiceExitCodeGenerator(executorService); + } + + public void shutDown(ExecutorServiceExitCodeGenerator executorServiceExitCodeGenerator) { + SpringApplication.exit(applicationContext, executorServiceExitCodeGenerator); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/monitor/jmx/MonitoringConfig.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/monitor/jmx/MonitoringConfig.java new file mode 100644 index 0000000000..febe3336eb --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/monitor/jmx/MonitoringConfig.java @@ -0,0 +1,21 @@ +package org.baeldung.monitor.jmx; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; + +@Configuration +public class MonitoringConfig { + @Autowired + private MetricRegistry registry; + + @Bean + public JmxReporter jmxReporter() { + JmxReporter reporter = JmxReporter.forRegistry(registry).build(); + reporter.start(); + return reporter; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/repository/GenericEntityRepository.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/repository/GenericEntityRepository.java new file mode 100644 index 0000000000..7bb1e6dcdc --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/repository/GenericEntityRepository.java @@ -0,0 +1,7 @@ +package org.baeldung.repository; + +import org.baeldung.domain.GenericEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GenericEntityRepository extends JpaRepository { +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginService.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginService.java new file mode 100644 index 0000000000..34840fad67 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginService.java @@ -0,0 +1,5 @@ +package org.baeldung.service; + +public interface LoginService { + public boolean login(String userName, char[] password); +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginServiceImpl.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginServiceImpl.java new file mode 100644 index 0000000000..2e5ef89c48 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/service/LoginServiceImpl.java @@ -0,0 +1,29 @@ +package org.baeldung.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.stereotype.Service; + +@Service +public class LoginServiceImpl implements LoginService { + + private CounterService counterService; + + @Autowired + public LoginServiceImpl(CounterService counterService) { + this.counterService = counterService; + } + + public boolean login(String userName, char[] password) { + boolean success; + if (userName.equals("admin") && "secret".toCharArray().equals(password)) { + counterService.increment("counter.login.success"); + success = true; + } else { + counterService.increment("counter.login.failure"); + success = false; + } + return success; + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/Application.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/Application.java new file mode 100644 index 0000000000..23d741b98c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/Application.java @@ -0,0 +1,23 @@ +package org.baeldung.session.exception; + +import org.baeldung.boot.model.Foo; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean; + +@EntityScan(basePackageClasses = Foo.class) +@SpringBootApplication +public class Application { + public static void main(String[] args) { + System.setProperty("spring.config.name", "exception"); + System.setProperty("spring.profiles.active", "exception"); + SpringApplication.run(Application.class, args); + } + + @Bean + public HibernateJpaSessionFactoryBean sessionFactory() { + return new HibernateJpaSessionFactoryBean(); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepository.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepository.java new file mode 100644 index 0000000000..679d691b26 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.session.exception.repository; + +import org.baeldung.boot.model.Foo; + +public interface FooRepository { + + void save(Foo foo); + + Foo get(Integer id); +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepositoryImpl.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepositoryImpl.java new file mode 100644 index 0000000000..83de888e5e --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/session/exception/repository/FooRepositoryImpl.java @@ -0,0 +1,25 @@ +package org.baeldung.session.exception.repository; + +import org.baeldung.boot.model.Foo; +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +@Profile("exception") +@Repository +public class FooRepositoryImpl implements FooRepository { + @Autowired + private SessionFactory sessionFactory; + + @Override + public void save(Foo foo) { + sessionFactory.getCurrentSession().saveOrUpdate(foo); + } + + @Override + public Foo get(Integer id) { + return sessionFactory.getCurrentSession().get(Foo.class, id); + } + +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/HeaderVersionArgumentResolver.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/HeaderVersionArgumentResolver.java new file mode 100644 index 0000000000..89a77f38d1 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/HeaderVersionArgumentResolver.java @@ -0,0 +1,26 @@ +package org.baeldung.web.resolver; + +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import javax.servlet.http.HttpServletRequest; + +@Component +public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(final MethodParameter methodParameter) { + return methodParameter.getParameterAnnotation(Version.class) != null; + } + + @Override + public Object resolveArgument(final MethodParameter methodParameter, final ModelAndViewContainer modelAndViewContainer, final NativeWebRequest nativeWebRequest, final WebDataBinderFactory webDataBinderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); + + return request.getHeader("Version"); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/Version.java b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/Version.java new file mode 100644 index 0000000000..2a9e6e60b3 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/java/org/baeldung/web/resolver/Version.java @@ -0,0 +1,11 @@ +package org.baeldung.web.resolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Version { +} diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/application.properties b/spring-custom-aop/spring-custom-aop/src/main/resources/application.properties new file mode 100644 index 0000000000..72ed8795c9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/application.properties @@ -0,0 +1,43 @@ +server.port=8080 +server.contextPath=/springbootapp +management.port=8081 +management.address=127.0.0.1 + +endpoints.shutdown.enabled=true + +endpoints.jmx.domain=Spring Sample Application +endpoints.jmx.uniqueNames=true + +##jolokia.config.debug=true +##endpoints.jolokia.enabled=true +##endpoints.jolokia.path=jolokia + +spring.jmx.enabled=true +endpoints.jmx.enabled=true + +## for pretty printing of json when endpoints accessed over HTTP +http.mappers.jsonPrettyPrint=true + +## Configuring info endpoint +info.app.name=Spring Sample Application +info.app.description=This is my first spring boot application G1 +info.app.version=1.0.0 + +## Spring Security Configurations +security.user.name=admin1 +security.user.password=secret1 +management.security.role=SUPERUSER + +logging.level.org.springframework=INFO + +#Servlet Configuration +servlet.name=dispatcherExample +servlet.mapping=/dispatcherExampleURL + +#banner.charset=UTF-8 +#banner.location=classpath:banner.txt +#banner.image.location=classpath:banner.gif +#banner.image.width= //TODO +#banner.image.height= //TODO +#banner.image.margin= //TODO +#banner.image.invert= //TODO \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/banner.txt b/spring-custom-aop/spring-custom-aop/src/main/resources/banner.txt new file mode 100644 index 0000000000..abfa666eb6 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/banner.txt @@ -0,0 +1,14 @@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@#@@@@@########@@@@@@@@@@@@@@@@@@@@@@@@...@@@@@@@@@:..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@#. @@@@@* *@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@#o @@@@@* @@@@@* @@@:*.*@@@@@@@: *8@@@ @@@@&:.#@. @o**@@@@**:@o*o@@:.:@@@@@:.o#@&*:@@@@ +@@@@@@@@@@@@* @@@@@* 8888 8@ @@@8 #@o 8@# .@ @@* :. @* @@@@ @. : &@ ** .@@@@ +@@@@@@@@@@. @ o@@@@@* *@@@o::& .* 8@@@@. @@ 8@@@@. @* @@@@ @. @@@& * @@@@# .@@@@ +@@@@@@@@@& @ @@@@@@* @@@@@@ 8 @@@@ .. o&&&&&&& @@ #@@@@. @* @@@@ @. @@@# * @@@@@ .@@@@ +@@@@@@@@@ @@o @@@@@@@* oooo* 8 @@@& @* @@@ # 88. 88. *& o#: @. @@@# *@ &#& .@@@@ +@@@@@@@@# @@@8 @@@@@@@* .*@@@#. *@@ @@@& :#@@@o .@@: *&@8 @o o@@: @. @@@# *@@#. :8# .@@@@ +@@@@@@@@@ @@@@ &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# o@@@@ @@@@@ +@@@@@& &@@@@ 8@@@@@@@@@8&8@@@@@#8#@@@o8@#&@@o&@@@&@@8@@&@@@@88@@8#@8&@@##@@@@@@#8@@#8@@88@@@@@ *@@@@@@@ +@@@# #@@@@#. @@@@@@@@@@@@@8@@8#o@&#@@@@o.@o*@@*.@@@.@&:8o8*@@@8&@@#@@@8@@@@8@#@@@8&@@@@@@#@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/custom.properties b/spring-custom-aop/spring-custom-aop/src/main/resources/custom.properties new file mode 100644 index 0000000000..34f31bcd50 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/custom.properties @@ -0,0 +1,4 @@ +dispatcher.servlet.name=dispatcherExample +dispatcher.servlet.mapping=/dispatcherExampleURL +example.servlet.name=dispatcherExample +example.servlet.mapping=/dispatcherExampleURL \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/demo.properties b/spring-custom-aop/spring-custom-aop/src/main/resources/demo.properties new file mode 100644 index 0000000000..649b64f59b --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/demo.properties @@ -0,0 +1,6 @@ +spring.output.ansi.enabled=never +server.port=7070 + +# Security +security.user.name=admin +security.user.password=password \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/logback.xml b/spring-custom-aop/spring-custom-aop/src/main/resources/logback.xml new file mode 100644 index 0000000000..78913ee76f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + web - %date [%thread] %-5level %logger{36} - %message%n + + + + + + + + + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/messages.properties b/spring-custom-aop/spring-custom-aop/src/main/resources/messages.properties new file mode 100644 index 0000000000..e4dbc44c3f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/messages.properties @@ -0,0 +1,4 @@ +greeting=Hello! Welcome to our website! +lang.change=Change the language +lang.eng=English +lang.fr=French \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/messages_fr.properties b/spring-custom-aop/spring-custom-aop/src/main/resources/messages_fr.properties new file mode 100644 index 0000000000..ac5853717d --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/messages_fr.properties @@ -0,0 +1,4 @@ +greeting=Bonjour! Bienvenue sur notre site! +lang.change=Changez la langue +lang.eng=Anglais +lang.fr=Francais \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/public/error/404.html b/spring-custom-aop/spring-custom-aop/src/main/resources/public/error/404.html new file mode 100644 index 0000000000..df83ce219b --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/public/error/404.html @@ -0,0 +1,8 @@ + + + RESOURCE NOT FOUND + + +

404 RESOURCE NOT FOUND

+ + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/templates/index.html b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/index.html new file mode 100644 index 0000000000..2c4387ed10 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/index.html @@ -0,0 +1,19 @@ + + + WebJars Demo + + + + +

+
+ × + Success! It is working as we expected. +
+
+ + + + + + diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/templates/international.html b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/international.html new file mode 100644 index 0000000000..a2a5fbb591 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/international.html @@ -0,0 +1,29 @@ + + + + +Home + + + + +

+ +

+: + + + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/templates/other.html b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/other.html new file mode 100644 index 0000000000..d13373f9fe --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/other.html @@ -0,0 +1,16 @@ + + + + +Spring Utils Demo + + + + Parameter set by you:

+ + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/resources/templates/utils.html b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/utils.html new file mode 100644 index 0000000000..93030f424f --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/resources/templates/utils.html @@ -0,0 +1,23 @@ + + + + +Spring Utils Demo + + + +

+

Set Parameter:

+

+ + +

+
+Another Page + + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/context.xml b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/context.xml new file mode 100644 index 0000000000..263bed4430 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/context.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/dispatcher.xml b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/dispatcher.xml new file mode 100644 index 0000000000..ade8e66777 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/dispatcher.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/web.xml b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..60a4b079de --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,40 @@ + + + JSP + + index.html + index.htm + index.jsp + + + + + EEWebXmlServlet + com.baeldung.servlets.javaee.EEWebXmlServlet + + + + EEWebXmlServlet + /eewebxmlservlet + + + + + SpringBootWebXmlServlet + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/dispatcher.xml + + 1 + + + + SpringBootWebXmlServlet + / + + + + diff --git a/spring-custom-aop/spring-custom-aop/src/main/webapp/annotationservlet.jsp b/spring-custom-aop/spring-custom-aop/src/main/webapp/annotationservlet.jsp new file mode 100644 index 0000000000..f21748df50 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/webapp/annotationservlet.jsp @@ -0,0 +1 @@ +

Annotation Servlet!

\ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/main/webapp/index.jsp b/spring-custom-aop/spring-custom-aop/src/main/webapp/index.jsp new file mode 100644 index 0000000000..e534282777 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/main/webapp/index.jsp @@ -0,0 +1 @@ +

Hello!

\ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithServletComponentIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithServletComponentIntegrationTest.java new file mode 100644 index 0000000000..8d5eb56bf4 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithServletComponentIntegrationTest.java @@ -0,0 +1,65 @@ +package com.baeldung.annotation.servletcomponentscan; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SpringBootAnnotatedApp.class) +@AutoConfigureMockMvc +@TestPropertySource(properties = { "security.basic.enabled=false" }) +public class SpringBootWithServletComponentIntegrationTest { + + @Autowired private ServletContext servletContext; + + @Test + public void givenServletContext_whenAccessAttrs_thenFoundAttrsPutInServletListner() { + assertNotNull(servletContext); + assertNotNull(servletContext.getAttribute("servlet-context-attr")); + assertEquals("test", servletContext.getAttribute("servlet-context-attr")); + } + + @Test + public void givenServletContext_whenCheckHelloFilterMappings_thenCorrect() { + assertNotNull(servletContext); + FilterRegistration filterRegistration = servletContext.getFilterRegistration("hello filter"); + + assertNotNull(filterRegistration); + assertTrue(filterRegistration + .getServletNameMappings() + .contains("echo servlet")); + } + + @Autowired private TestRestTemplate restTemplate; + + @Test + public void givenServletFilter_whenGetHello_thenRequestFiltered() { + ResponseEntity responseEntity = this.restTemplate.getForEntity("/hello", String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals("filtering hello", responseEntity.getBody()); + } + + @Test + public void givenFilterAndServlet_whenPostEcho_thenEchoFiltered() { + ResponseEntity responseEntity = this.restTemplate.postForEntity("/echo", "echo", String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals("filtering echo", responseEntity.getBody()); + } + + + +} + + diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithoutServletComponentIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithoutServletComponentIntegrationTest.java new file mode 100644 index 0000000000..64507ad02c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/annotation/servletcomponentscan/SpringBootWithoutServletComponentIntegrationTest.java @@ -0,0 +1,50 @@ +package com.baeldung.annotation.servletcomponentscan; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SpringBootPlainApp.class) +@AutoConfigureMockMvc +@TestPropertySource(properties = { "security.basic.enabled=false" }) +public class SpringBootWithoutServletComponentIntegrationTest { + + @Autowired private ServletContext servletContext; + + @Autowired private TestRestTemplate restTemplate; + + @Test + public void givenServletContext_whenAccessAttrs_thenNotFound() { + assertNull(servletContext.getAttribute("servlet-context-attr")); + } + + @Test + public void givenServletFilter_whenGetHello_thenEndpointNotFound() { + ResponseEntity responseEntity = this.restTemplate.getForEntity("/hello", String.class); + assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode()); + } + + @Test + public void givenServletContext_whenCheckFilterMappings_thenEmpty() { + assertNotNull(servletContext); + FilterRegistration filterRegistration = servletContext.getFilterRegistration("hello filter"); + + assertNull(filterRegistration); + } + +} + + diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/git/CommitIdIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/git/CommitIdIntegrationTest.java new file mode 100644 index 0000000000..348d594c05 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/git/CommitIdIntegrationTest.java @@ -0,0 +1,41 @@ +package com.baeldung.git; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = CommitIdApplication.class) +public class CommitIdIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(CommitIdIntegrationTest.class); + + @Value("${git.commit.message.short:UNKNOWN}") + private String commitMessage; + + @Value("${git.branch:UNKNOWN}") + private String branch; + + @Value("${git.commit.id:UNKNOWN}") + private String commitId; + + @Test + public void whenInjecting_shouldDisplay() throws Exception { + + LOG.info(commitId); + LOG.info(commitMessage); + LOG.info(branch); + + assertThat(commitMessage).isNotEqualTo("UNKNOWN"); + + assertThat(branch).isNotEqualTo("UNKNOWN"); + + assertThat(commitId).isNotEqualTo("UNKNOWN"); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/intro/AppLiveTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/intro/AppLiveTest.java new file mode 100644 index 0000000000..af46fe0423 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/intro/AppLiveTest.java @@ -0,0 +1,41 @@ +package com.baeldung.intro; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.hamcrest.Matchers.equalTo; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(properties = { "security.basic.enabled=false" }) +public class AppLiveTest { + + @Autowired + private MockMvc mvc; + + @Test + public void getIndex() throws Exception { + mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(equalTo("Index Page"))); + } + + @Test + public void getLocal() throws Exception { + mvc.perform(MockMvcRequestBuilders.get("/local").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(equalTo("/local"))); + } + +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/utils/UtilsControllerTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/utils/UtilsControllerTest.java new file mode 100644 index 0000000000..7aed45dbb0 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/utils/UtilsControllerTest.java @@ -0,0 +1,41 @@ +package com.baeldung.utils; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +import com.baeldung.utils.controller.UtilsController; + +public class UtilsControllerTest { + + @InjectMocks + private UtilsController utilsController; + + private MockMvc mockMvc; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.mockMvc = MockMvcBuilders.standaloneSetup(utilsController) + .build(); + + } + + @Test + public void givenParameter_setRequestParam_andSetSessionAttribute() throws Exception { + String param = "testparam"; + this.mockMvc.perform( + post("/setParam") + .param("param", param) + .sessionAttr("parameter", param)) + .andExpect(status().isOk()); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/webjar/WebjarsdemoApplicationIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/webjar/WebjarsdemoApplicationIntegrationTest.java new file mode 100644 index 0000000000..d6e71dcf6b --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/com/baeldung/webjar/WebjarsdemoApplicationIntegrationTest.java @@ -0,0 +1,18 @@ +package com.baeldung.webjar; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = WebjarsdemoApplication.class) +@WebAppConfiguration +public class WebjarsdemoApplicationIntegrationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootApplicationIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootApplicationIntegrationTest.java new file mode 100644 index 0000000000..87c59a4662 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootApplicationIntegrationTest.java @@ -0,0 +1,66 @@ +package org.baeldung; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +import org.baeldung.domain.Modes; +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.http.MediaType; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.nio.charset.Charset; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +@WebAppConfiguration +public class SpringBootApplicationIntegrationTest { + @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + + @Before + public void setupMockMvc() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Test + public void givenRequestHasBeenMade_whenMeetsAllOfGivenConditions_thenCorrect() throws Exception { + MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); + + mockMvc.perform(MockMvcRequestBuilders.get("/entity/all")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().contentType(contentType)).andExpect(jsonPath("$", hasSize(4))); + } + + @Test + public void givenRequestHasBeenMade_whenMeetsFindByDateOfGivenConditions_thenCorrect() throws Exception { + MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); + + mockMvc.perform(MockMvcRequestBuilders.get("/entity/findbydate/{date}", "2011-12-03T10:15:30")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().contentType(contentType)) + .andExpect(jsonPath("$.id", equalTo(1))); + } + + @Test + public void givenRequestHasBeenMade_whenMeetsFindByModeOfGivenConditions_thenCorrect() throws Exception { + MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); + + mockMvc.perform(MockMvcRequestBuilders.get("/entity/findbymode/{mode}", Modes.ALPHA.name())).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().contentType(contentType)).andExpect(jsonPath("$.id", equalTo(1))); + } + + @Test + public void givenRequestHasBeenMade_whenMeetsFindByVersionOfGivenConditions_thenCorrect() throws Exception { + MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); + + mockMvc.perform(MockMvcRequestBuilders.get("/entity/findbyversion").header("Version", "1.0.0")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().contentType(contentType)) + .andExpect(jsonPath("$.id", equalTo(1))); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootJPAIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootJPAIntegrationTest.java new file mode 100644 index 0000000000..d4b19e6a1d --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootJPAIntegrationTest.java @@ -0,0 +1,27 @@ +package org.baeldung; + +import org.baeldung.domain.GenericEntity; +import org.baeldung.repository.GenericEntityRepository; +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.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +public class SpringBootJPAIntegrationTest { + @Autowired + private GenericEntityRepository genericEntityRepository; + + @Test + public void givenGenericEntityRepository_whenSaveAndRetreiveEntity_thenOK() { + GenericEntity genericEntity = genericEntityRepository.save(new GenericEntity("test")); + GenericEntity foundedEntity = genericEntityRepository.findOne(genericEntity.getId()); + assertNotNull(foundedEntity); + assertEquals(genericEntity.getValue(), foundedEntity.getValue()); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootMailIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootMailIntegrationTest.java new file mode 100644 index 0000000000..10e3d6d60b --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/SpringBootMailIntegrationTest.java @@ -0,0 +1,81 @@ +package org.baeldung; + +import org.junit.After; +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.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.WebApplicationContext; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; + +import javax.mail.MessagingException; +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +public class SpringBootMailIntegrationTest { + @Autowired + private JavaMailSender javaMailSender; + + private Wiser wiser; + + private String userTo = "user2@localhost"; + private String userFrom = "user1@localhost"; + private String subject = "Test subject"; + private String textMail = "Text subject mail"; + + @Before + public void setUp() throws Exception { + final int TEST_PORT = 8025; + wiser = new Wiser(TEST_PORT); + wiser.start(); + } + + @After + public void tearDown() throws Exception { + wiser.stop(); + } + + @Test + public void givenMail_whenSendAndReceived_thenCorrect() throws Exception { + SimpleMailMessage message = composeEmailMessage(); + javaMailSender.send(message); + List messages = wiser.getMessages(); + + assertThat(messages, hasSize(1)); + WiserMessage wiserMessage = messages.get(0); + assertEquals(userFrom, wiserMessage.getEnvelopeSender()); + assertEquals(userTo, wiserMessage.getEnvelopeReceiver()); + assertEquals(subject, getSubject(wiserMessage)); + assertEquals(textMail, getMessage(wiserMessage)); + } + + private String getMessage(WiserMessage wiserMessage) throws MessagingException, IOException { + return wiserMessage.getMimeMessage().getContent().toString().trim(); + } + + private String getSubject(WiserMessage wiserMessage) throws MessagingException { + return wiserMessage.getMimeMessage().getSubject(); + } + + private SimpleMailMessage composeEmailMessage() { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(userTo); + mailMessage.setReplyTo(userFrom); + mailMessage.setFrom(userFrom); + mailMessage.setSubject(subject); + mailMessage.setText(textMail); + return mailMessage; + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/ApplicationIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/ApplicationIntegrationTest.java new file mode 100644 index 0000000000..57a8abc1ee --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/ApplicationIntegrationTest.java @@ -0,0 +1,17 @@ +package org.baeldung.boot; + +import org.baeldung.session.exception.Application; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = Application.class) +@TestPropertySource("classpath:exception.properties") +public class ApplicationIntegrationTest { + @Test + public void contextLoads() { + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/DemoApplicationIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/DemoApplicationIntegrationTest.java new file mode 100644 index 0000000000..4fcea35b4a --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/DemoApplicationIntegrationTest.java @@ -0,0 +1,17 @@ +package org.baeldung.boot; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = DemoApplication.class) +@WebAppConfiguration +public class DemoApplicationIntegrationTest { + + @Test + public void contextLoads() { + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooComponentTests.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooComponentTests.java new file mode 100644 index 0000000000..72ccc0bfb8 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooComponentTests.java @@ -0,0 +1,70 @@ +package org.baeldung.boot; + +import org.baeldung.boot.components.FooService; +import org.baeldung.boot.model.Foo; +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.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; + +@RunWith(SpringRunner.class) +@SpringBootTest( + classes = DemoApplication.class, + webEnvironment = WebEnvironment.RANDOM_PORT) +public class FooComponentTests { + + @Autowired + private TestRestTemplate testRestTemplate; + + @SpyBean + private FooService fooService; + + @Before + public void init() throws Exception { + Foo foo = new Foo(); + foo.setId(5); + foo.setName("MOCKED_FOO"); + + doReturn(foo).when(fooService).getFooWithId(anyInt()); + + // doCallRealMethod().when(fooComponent).getFooWithName(anyString()); + } + + @Test + public void givenInquiryingFooWithId_whenFooComponentIsMocked_thenAssertMockedResult() { + Map pathVariables = new HashMap<>(); + pathVariables.put("id", "1"); + ResponseEntity fooResponse = testRestTemplate.getForEntity("/{id}", Foo.class, pathVariables); + + assertNotNull(fooResponse); + assertEquals(HttpStatus.OK, fooResponse.getStatusCode()); + assertEquals(5, fooResponse.getBody().getId().longValue()); + assertEquals("MOCKED_FOO", fooResponse.getBody().getName()); + } + + @Test + public void givenInquiryingFooWithName_whenFooComponentIsMocked_thenAssertMockedResult() { + Map pathVariables = new HashMap<>(); + pathVariables.put("name", "Foo_Name"); + ResponseEntity fooResponse = testRestTemplate.getForEntity("/?name={name}", Foo.class, pathVariables); + + assertNotNull(fooResponse); + assertEquals(HttpStatus.OK, fooResponse.getStatusCode()); + assertEquals(1, fooResponse.getBody().getId().longValue()); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooIntegrationTest.java new file mode 100644 index 0000000000..932cce26d5 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooIntegrationTest.java @@ -0,0 +1,43 @@ +package org.baeldung.boot; +import java.util.HashMap; +import java.util.Map; + +import org.baeldung.boot.DemoApplication; +import org.baeldung.boot.model.Foo; +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.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes=DemoApplication.class,webEnvironment = WebEnvironment.RANDOM_PORT) +public class FooIntegrationTest { + + @Autowired + private TestRestTemplate testRestTemplate; + + + @Test + public void givenInquiryingFooWithId_whenIdIsValid_thenHttpStatusOK(){ + Map pathVariables = new HashMap(); + pathVariables.put("id", "1"); + ResponseEntity fooResponse = testRestTemplate.getForEntity("/{id}", Foo.class, pathVariables); + Assert.assertNotNull(fooResponse); + Assert.assertEquals(HttpStatus.OK,fooResponse.getStatusCode()); + } + + @Test + public void givenInquiryingFooWithName_whenNameIsValid_thenHttpStatusOK(){ + Map pathVariables = new HashMap(); + pathVariables.put("name", "Foo_Name"); + ResponseEntity fooResponse = testRestTemplate.getForEntity("/?name={name}", Foo.class, pathVariables); + Assert.assertNotNull(fooResponse); + Assert.assertEquals(HttpStatus.OK,fooResponse.getStatusCode()); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJPATest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJPATest.java new file mode 100644 index 0000000000..c29aa64e6c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJPATest.java @@ -0,0 +1,34 @@ +package org.baeldung.boot; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.baeldung.boot.model.Foo; +import org.baeldung.boot.repository.FooRepository; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@DataJpaTest +public class FooJPATest { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private FooRepository repository; + + @Test + public void findFooByName() { + this.entityManager.persist(new Foo("Foo_Name_2")); + Foo foo = this.repository.findByName("Foo_Name_2"); + assertNotNull(foo); + assertEquals("Foo_Name_2",foo.getName()); + // Due to having Insert query for Foo with Id 1, so TestEntityManager generates new Id of 2 + assertEquals(2l,foo.getId().longValue()); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJsonTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJsonTest.java new file mode 100644 index 0000000000..2789ed0a8c --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/FooJsonTest.java @@ -0,0 +1,35 @@ +package org.baeldung.boot; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.baeldung.boot.model.Foo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@JsonTest +public class FooJsonTest { + + @Autowired + private JacksonTester json; + + + @Test + public void testSerialize() throws Exception { + Foo foo = new Foo(3, "Foo_Name_3"); + assertThat(this.json.write(foo)).isEqualToJson("expected.json"); + assertThat(this.json.write(foo)).hasJsonPathStringValue("@.name"); + assertThat(this.json.write(foo)).extractingJsonPathStringValue("@.name").isEqualTo("Foo_Name_3"); + } + + @Test + public void testDeserialize() throws Exception { + String content = "{\"id\":4,\"name\":\"Foo_Name_4\"}"; + assertThat(this.json.parseObject(content).getName()).isEqualTo("Foo_Name_4"); + assertThat(this.json.parseObject(content).getId()==4); + } +} \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/FooRepositoryIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/FooRepositoryIntegrationTest.java new file mode 100644 index 0000000000..a844b26b2d --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/FooRepositoryIntegrationTest.java @@ -0,0 +1,34 @@ +package org.baeldung.boot.repository; + +import static org.junit.Assert.assertThat; + +import org.baeldung.boot.DemoApplicationIntegrationTest; +import org.baeldung.boot.model.Foo; + +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.is; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class FooRepositoryIntegrationTest extends DemoApplicationIntegrationTest { + @Autowired + private FooRepository fooRepository; + + @Before + public void setUp() { + fooRepository.save(new Foo("Foo")); + fooRepository.save(new Foo("Bar")); + } + + @Test + public void testFindByName() { + Foo foo = fooRepository.findByName("Bar"); + assertThat(foo, notNullValue()); + assertThat(foo.getId(), is(2)); + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/HibernateSessionIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/HibernateSessionIntegrationTest.java new file mode 100644 index 0000000000..be992bcc36 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/HibernateSessionIntegrationTest.java @@ -0,0 +1,32 @@ +package org.baeldung.boot.repository; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import org.baeldung.boot.ApplicationIntegrationTest; +import org.baeldung.boot.model.Foo; +import org.baeldung.session.exception.repository.FooRepository; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@TestPropertySource("classpath:exception-hibernate.properties") +public class HibernateSessionIntegrationTest extends ApplicationIntegrationTest { + @Autowired + private FooRepository fooRepository; + + @Test + public void whenSavingWithCurrentSession_thenThrowNoException() { + Foo foo = new Foo("Exception Solved"); + fooRepository.save(foo); + foo = null; + foo = fooRepository.get(1); + + assertThat(foo, notNullValue()); + assertThat(foo.getId(), is(1)); + assertThat(foo.getName(), is("Exception Solved")); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/NoHibernateSessionIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/NoHibernateSessionIntegrationTest.java new file mode 100644 index 0000000000..55b7fa7216 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/boot/repository/NoHibernateSessionIntegrationTest.java @@ -0,0 +1,21 @@ +package org.baeldung.boot.repository; + +import org.baeldung.boot.ApplicationIntegrationTest; +import org.baeldung.boot.model.Foo; +import org.baeldung.session.exception.repository.FooRepository; +import org.hibernate.HibernateException; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class NoHibernateSessionIntegrationTest extends ApplicationIntegrationTest { + @Autowired + private FooRepository fooRepository; + + @Test(expected = HibernateException.class) + public void whenSavingWithoutCurrentSession_thenThrowException() { + Foo foo = new Foo("Exception Thrown"); + fooRepository.save(foo); + } +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/client/DetailsServiceClientIntegrationTest.java b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/client/DetailsServiceClientIntegrationTest.java new file mode 100644 index 0000000000..5627855aa3 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/java/org/baeldung/client/DetailsServiceClientIntegrationTest.java @@ -0,0 +1,46 @@ +package org.baeldung.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.autoconfigure.web.client.RestClientTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.client.MockRestServiceServer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +@RunWith(SpringRunner.class) +@RestClientTest(DetailsServiceClient.class) +public class DetailsServiceClientIntegrationTest { + + @Autowired + private DetailsServiceClient client; + + @Autowired + private MockRestServiceServer server; + + @Autowired + private ObjectMapper objectMapper; + + @Before + public void setUp() throws Exception { + String detailsString = objectMapper.writeValueAsString(new Details("John Smith", "john")); + this.server.expect(requestTo("/john/details")).andRespond(withSuccess(detailsString, MediaType.APPLICATION_JSON)); + } + + @Test + public void whenCallingGetUserDetails_thenClientExecutesCorrectCall() throws Exception { + + Details details = this.client.getUserDetails("john"); + + assertThat(details.getLogin()).isEqualTo("john"); + assertThat(details.getName()).isEqualTo("John Smith"); + + } + +} diff --git a/spring-custom-aop/spring-custom-aop/src/test/resources/application.properties b/spring-custom-aop/spring-custom-aop/src/test/resources/application.properties new file mode 100644 index 0000000000..0e6cb86bc5 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/resources/application.properties @@ -0,0 +1,5 @@ +spring.mail.host=localhost +spring.mail.port=8025 +spring.mail.properties.mail.smtp.auth=false + +security.basic.enabled=false \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/resources/exception-hibernate.properties b/spring-custom-aop/spring-custom-aop/src/test/resources/exception-hibernate.properties new file mode 100644 index 0000000000..cde746acb9 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/resources/exception-hibernate.properties @@ -0,0 +1,2 @@ +spring.profiles.active=exception +spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext diff --git a/spring-custom-aop/spring-custom-aop/src/test/resources/exception.properties b/spring-custom-aop/spring-custom-aop/src/test/resources/exception.properties new file mode 100644 index 0000000000..c55e415a3a --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/resources/exception.properties @@ -0,0 +1,6 @@ +# Security +security.user.name=admin +security.user.password=password + +spring.dao.exceptiontranslation.enabled=false +spring.profiles.active=exception \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/resources/import.sql b/spring-custom-aop/spring-custom-aop/src/test/resources/import.sql new file mode 100644 index 0000000000..a382410271 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/resources/import.sql @@ -0,0 +1 @@ +Insert into Foo values(1,'Foo_Name'); \ No newline at end of file diff --git a/spring-custom-aop/spring-custom-aop/src/test/resources/org/baeldung/boot/expected.json b/spring-custom-aop/spring-custom-aop/src/test/resources/org/baeldung/boot/expected.json new file mode 100644 index 0000000000..f5409421a6 --- /dev/null +++ b/spring-custom-aop/spring-custom-aop/src/test/resources/org/baeldung/boot/expected.json @@ -0,0 +1,4 @@ +{ + "id":3, + "name":"Foo_Name_3" +} \ No newline at end of file From c817aec2dc5303da4aca06bd222b6c21704cee24 Mon Sep 17 00:00:00 2001 From: dhruba619 Date: Sun, 2 Apr 2017 21:20:31 +0530 Subject: [PATCH 137/149] BAEL-716 Junit vs testng improvement updated formatting --- .../com/baeldung/junit4vstestng/SortedTests.java | 15 ++++++--------- .../src/test/java/baeldung/com/PriorityTest.java | 6 ++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java b/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java index 1fa4a4e61b..fe0ec1469c 100644 --- a/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java +++ b/core-java/src/test/java/com/baeldung/junit4vstestng/SortedTests.java @@ -6,20 +6,17 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; - @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class SortedTests { - + @Test - public void a_givenString_whenChangedtoInt_thenTrue(){ - assertTrue( - Integer.valueOf("10") instanceof Integer); + public void a_givenString_whenChangedtoInt_thenTrue() { + assertTrue(Integer.valueOf("10") instanceof Integer); } - + @Test - public void b_givenInt_whenChangedtoString_thenTrue(){ - assertTrue( - String.valueOf(10) instanceof String); + public void b_givenInt_whenChangedtoString_thenTrue() { + assertTrue(String.valueOf(10) instanceof String); } } diff --git a/testng/src/test/java/baeldung/com/PriorityTest.java b/testng/src/test/java/baeldung/com/PriorityTest.java index 34f2d6fe47..d014d5c920 100644 --- a/testng/src/test/java/baeldung/com/PriorityTest.java +++ b/testng/src/test/java/baeldung/com/PriorityTest.java @@ -10,14 +10,12 @@ public class PriorityTest { @Test(priority = 1) public void givenString_whenChangedToInt_thenCorrect() { - Assert.assertTrue( - Integer.valueOf(testString) instanceof Integer); + Assert.assertTrue(Integer.valueOf(testString) instanceof Integer); } @Test(priority = 2) public void givenInt_whenChangedToString_thenCorrect() { - Assert.assertTrue( - String.valueOf(testInt) instanceof String); + Assert.assertTrue(String.valueOf(testInt) instanceof String); } } From d361c91ed3f9e2216c441f61be820b19f0748dfa Mon Sep 17 00:00:00 2001 From: ahamedm Date: Sun, 2 Apr 2017 22:57:45 +0400 Subject: [PATCH 138/149] BAEL-696 Implement OR in the REST API Query Language - Alternate Impl (#1576) * Dependency Injection Types, XML-Config, Java-Config, Test Classes * Formatting done with Formatter Configuration in Eclipse * REST Query Lang - Adv Search Ops - Improvement - C1 * REST Query Lang - Adv Search Ops - Improvement - C2 * BAEL-696 Code formatting * REST Query Lang - Adv Search Ops - Improvement - C3 * BAEL-696 Formatting * OR operation with PostFix Expression * Revert the changes done for PostFix Expr * Merged from Upstream * Remove Sorting of Predicates * REST Query Lang - Adv Search Ops - Improvement - C5 --- .../dao/GenericSpecificationsBuilder.java | 63 ++++++++++----- .../dao/UserSpecificationsBuilder.java | 7 +- .../web/controller/UserController.java | 26 +++++-- .../org/baeldung/web/util/CriteriaParser.java | 76 +++++++++++++++++++ .../baeldung/web/util/SearchOperation.java | 8 ++ .../baeldung/web/util/SpecSearchCriteria.java | 21 +++++ .../JPASpecificationIntegrationTest.java | 29 +++++-- .../query/JPASpecificationLiveTest.java | 51 ++++++++++--- 8 files changed, 233 insertions(+), 48 deletions(-) create mode 100644 spring-security-rest-full/src/main/java/org/baeldung/web/util/CriteriaParser.java diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java index 45c015f233..64bab9a435 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/GenericSpecificationsBuilder.java @@ -1,7 +1,9 @@ package org.baeldung.persistence.dao; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @@ -11,7 +13,7 @@ import org.baeldung.web.util.SpecSearchCriteria; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specifications; -public class GenericSpecificationsBuilder { +public class GenericSpecificationsBuilder { private final List params; @@ -19,11 +21,11 @@ public class GenericSpecificationsBuilder { this.params = new ArrayList<>(); } - public final GenericSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) { + public final GenericSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) { return with(null, key, operation, value, prefix, suffix); } - public final GenericSpecificationsBuilder with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) { + public final GenericSpecificationsBuilder with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) { SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); if (op != null) { if (op == SearchOperation.EQUALITY) // the operation may be complex operation @@ -44,33 +46,54 @@ public class GenericSpecificationsBuilder { return this; } - public Specification build(Function> converter) { + public Specification build(Function> converter) { if (params.size() == 0) { return null; } - params.sort(Comparator.comparing(SpecSearchCriteria::isOrPredicate)); - - final List> specs = params - .stream() - .map(converter) - .collect(Collectors.toCollection(ArrayList::new)); + final List> specs = params.stream() + .map(converter) + .collect(Collectors.toCollection(ArrayList::new)); Specification result = specs.get(0); for (int idx = 1; idx < specs.size(); idx++) { - result = params - .get(idx) - .isOrPredicate() - ? Specifications - .where(result) - .or(specs.get(idx)) - : Specifications - .where(result) - .and(specs.get(idx)); + result = params.get(idx) + .isOrPredicate() + ? Specifications.where(result) + .or(specs.get(idx)) + : Specifications.where(result) + .and(specs.get(idx)); } return result; } + public Specification build(Deque postFixedExprStack, Function> converter) { + + Deque> specStack = new LinkedList<>(); + + Collections.reverse((List) postFixedExprStack); + + while (!postFixedExprStack.isEmpty()) { + Object mayBeOperand = postFixedExprStack.pop(); + + if (!(mayBeOperand instanceof String)) { + specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand)); + } else { + Specification operand1 = specStack.pop(); + Specification operand2 = specStack.pop(); + if (mayBeOperand.equals(SearchOperation.AND_OPERATOR)) + specStack.push(Specifications.where(operand1) + .and(operand2)); + else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR)) + specStack.push(Specifications.where(operand1) + .or(operand2)); + } + + } + return specStack.pop(); + + } + } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java index bbcb521241..a8e5b96acb 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/persistence/dao/UserSpecificationsBuilder.java @@ -1,7 +1,6 @@ package org.baeldung.persistence.dao; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import org.baeldung.persistence.model.User; @@ -24,7 +23,7 @@ public final class UserSpecificationsBuilder { return with(null, key, operation, value, prefix, suffix); } - public final UserSpecificationsBuilder with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) { + public final UserSpecificationsBuilder with(final String orPredicate, final String key, final String operation, final Object value, final String prefix, final String suffix) { SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); if (op != null) { if (op == SearchOperation.EQUALITY) { // the operation may be complex operation @@ -39,7 +38,7 @@ public final class UserSpecificationsBuilder { op = SearchOperation.STARTS_WITH; } } - params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value)); + params.add(new SpecSearchCriteria(orPredicate, key, op, value)); } return this; } @@ -49,8 +48,6 @@ public final class UserSpecificationsBuilder { if (params.size() == 0) return null; - params.sort(Comparator.comparing(SpecSearchCriteria::isOrPredicate)); - Specification result = new UserSpecification(params.get(0)); for (int i = 1; i < params.size(); i++) { diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java index 4c21d9836d..8953a52a1b 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java @@ -5,14 +5,17 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.baeldung.persistence.dao.GenericSpecificationsBuilder; import org.baeldung.persistence.dao.IUserDAO; import org.baeldung.persistence.dao.MyUserPredicatesBuilder; import org.baeldung.persistence.dao.MyUserRepository; import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.dao.UserSpecification; import org.baeldung.persistence.dao.UserSpecificationsBuilder; import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor; import org.baeldung.persistence.model.MyUser; import org.baeldung.persistence.model.User; +import org.baeldung.web.util.CriteriaParser; import org.baeldung.web.util.SearchCriteria; import org.baeldung.web.util.SearchOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -74,9 +77,8 @@ public class UserController { @ResponseBody public List findAllBySpecification(@RequestParam(value = "search") String search) { UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - String operationSetExper = Joiner - .on("|") - .join(SearchOperation.SIMPLE_OPERATION_SET); + String operationSetExper = Joiner.on("|") + .join(SearchOperation.SIMPLE_OPERATION_SET); Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { @@ -94,12 +96,24 @@ public class UserController { return dao.findAll(spec); } + @GetMapping(value = "/users/spec/adv") + @ResponseBody + public List findAllByAdvPredicate(@RequestParam(value = "search") String search) { + Specification spec = resolveSpecificationFromInfixExpr(search); + return dao.findAll(spec); + } + + protected Specification resolveSpecificationFromInfixExpr(String searchParameters) { + CriteriaParser parser = new CriteriaParser(); + GenericSpecificationsBuilder specBuilder = new GenericSpecificationsBuilder<>(); + return specBuilder.build(parser.parse(searchParameters), UserSpecification::new); + } + protected Specification resolveSpecification(String searchParameters) { UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - String operationSetExper = Joiner - .on("|") - .join(SearchOperation.SIMPLE_OPERATION_SET); + String operationSetExper = Joiner.on("|") + .join(SearchOperation.SIMPLE_OPERATION_SET); Pattern pattern = Pattern.compile("(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),"); Matcher matcher = pattern.matcher(searchParameters + ","); while (matcher.find()) { diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/CriteriaParser.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/CriteriaParser.java new file mode 100644 index 0000000000..eabc938bce --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/CriteriaParser.java @@ -0,0 +1,76 @@ +package org.baeldung.web.util; + +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Joiner; + +public class CriteriaParser { + + private static Map ops; + + private static Pattern SpecCriteraRegex = Pattern.compile("^(\\w+?)(" + Joiner.on("|") + .join(SearchOperation.SIMPLE_OPERATION_SET) + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?)$"); + + private enum Operator { + OR(1), AND(2); + final int precedence; + + Operator(int p) { + precedence = p; + } + } + + static { + Map tempMap = new HashMap<>(); + tempMap.put("AND", Operator.AND); + tempMap.put("OR", Operator.OR); + tempMap.put("or", Operator.OR); + tempMap.put("and", Operator.AND); + + ops = Collections.unmodifiableMap(tempMap); + } + + private static boolean isHigerPrecedenceOperator(String currOp, String prevOp) { + return (ops.containsKey(prevOp) && ops.get(prevOp).precedence >= ops.get(currOp).precedence); + } + + public Deque parse(String searchParam) { + + Deque output = new LinkedList<>(); + Deque stack = new LinkedList<>(); + + for (String token : searchParam.split("\\s+")) { + if (ops.containsKey(token)) { + while (!stack.isEmpty() && isHigerPrecedenceOperator(token, stack.peek())) + output.push(stack.pop() + .equalsIgnoreCase(SearchOperation.OR_OPERATOR) ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR); + stack.push(token.equalsIgnoreCase(SearchOperation.OR_OPERATOR) ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR); + } else if (token.equals(SearchOperation.LEFT_PARANTHESIS)) { + stack.push(SearchOperation.LEFT_PARANTHESIS); + } else if (token.equals(SearchOperation.RIGHT_PARANTHESIS)) { + while (!stack.peek() + .equals(SearchOperation.LEFT_PARANTHESIS)) + output.push(stack.pop()); + stack.pop(); + } else { + + Matcher matcher = SpecCriteraRegex.matcher(token); + while (matcher.find()) { + output.push(new SpecSearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), matcher.group(5))); + } + } + } + + while (!stack.isEmpty()) + output.push(stack.pop()); + + return output; + } + +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java index fa09662201..db2c0133cf 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SearchOperation.java @@ -9,6 +9,14 @@ public enum SearchOperation { public static final String ZERO_OR_MORE_REGEX = "*"; + public static final String OR_OPERATOR = "OR"; + + public static final String AND_OPERATOR = "AND"; + + public static final String LEFT_PARANTHESIS = "("; + + public static final String RIGHT_PARANTHESIS = ")"; + public static SearchOperation getSimpleOperation(final char input) { switch (input) { case ':': diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java index 6b37fb579c..3435ff3342 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/SpecSearchCriteria.java @@ -26,6 +26,27 @@ public class SpecSearchCriteria { this.value = value; } + public SpecSearchCriteria(String key, String operation, String prefix, String value, String suffix) { + SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0)); + if (op != null) { + if (op == SearchOperation.EQUALITY) { // the operation may be complex operation + final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX); + + if (startWithAsterisk && endWithAsterisk) { + op = SearchOperation.CONTAINS; + } else if (startWithAsterisk) { + op = SearchOperation.ENDS_WITH; + } else if (endWithAsterisk) { + op = SearchOperation.STARTS_WITH; + } + } + } + this.key = key; + this.operation = op; + this.value = value; + } + public String getKey() { return key; } diff --git a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java index 244e19db90..d9ae95c876 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationIntegrationTest.java @@ -6,6 +6,7 @@ import org.baeldung.persistence.dao.UserSpecification; import org.baeldung.persistence.dao.UserSpecificationsBuilder; import org.baeldung.persistence.model.User; import org.baeldung.spring.PersistenceConfig; +import org.baeldung.web.util.CriteriaParser; import org.baeldung.web.util.SearchOperation; import org.baeldung.web.util.SpecSearchCriteria; import org.junit.Before; @@ -82,12 +83,12 @@ public class JPASpecificationIntegrationTest { public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() { UserSpecificationsBuilder builder = new UserSpecificationsBuilder(); - SpecSearchCriteria spec = new SpecSearchCriteria("'", "firstName", SearchOperation.EQUALITY, "john"); - SpecSearchCriteria spec1 = new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"); + SpecSearchCriteria spec = new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john"); + SpecSearchCriteria spec1 = new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe"); List results = repository.findAll(builder - .with(spec1) .with(spec) + .with(spec1) .build()); assertThat(results, hasSize(2)); @@ -96,11 +97,25 @@ public class JPASpecificationIntegrationTest { } @Test - public void givenFirstOrLastNameGenericBuilder_whenGettingListOfUsers_thenCorrect() { - GenericSpecificationsBuilder builder = new GenericSpecificationsBuilder(); + public void givenFirstOrLastNameAndAgeGenericBuilder_whenGettingListOfUsers_thenCorrect() { + GenericSpecificationsBuilder builder = new GenericSpecificationsBuilder<>(); Function> converter = UserSpecification::new; - builder.with("'", "firstName", ":", "john", null, null); - builder.with(null, "lastName", ":", "doe", null, null); + + CriteriaParser parser=new CriteriaParser(); + List results = repository.findAll(builder.build(parser.parse("( lastName:doe OR firstName:john ) AND age:22"), converter)); + + assertThat(results, hasSize(1)); + assertThat(userJohn, isIn(results)); + assertThat(userTom, not(isIn(results))); + } + + @Test + public void givenFirstOrLastNameGenericBuilder_whenGettingListOfUsers_thenCorrect() { + GenericSpecificationsBuilder builder = new GenericSpecificationsBuilder<>(); + Function> converter = UserSpecification::new; + + builder.with("firstName", ":", "john", null, null); + builder.with("'", "lastName", ":", "doe", null, null); List results = repository.findAll(builder.build(converter)); diff --git a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java index 55fde80add..70787266d8 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/persistence/query/JPASpecificationLiveTest.java @@ -47,8 +47,9 @@ public class JPASpecificationLiveTest { @Test public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() { - final Response response = givenAuth().get(EURL_PREFIX + "'firstName:john,lastName:doe"); - final String result = response.body().asString(); + final Response response = givenAuth().get(EURL_PREFIX + "firstName:john,'lastName:doe"); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertTrue(result.contains(userTom.getEmail())); } @@ -56,7 +57,8 @@ public class JPASpecificationLiveTest { @Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "firstName:john,lastName:doe"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertFalse(result.contains(userTom.getEmail())); @@ -65,7 +67,8 @@ public class JPASpecificationLiveTest { @Test public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "firstName!john"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userTom.getEmail())); assertFalse(result.contains(userJohn.getEmail())); @@ -74,7 +77,8 @@ public class JPASpecificationLiveTest { @Test public void givenMinAge_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "age>25"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userTom.getEmail())); assertFalse(result.contains(userJohn.getEmail())); @@ -83,7 +87,8 @@ public class JPASpecificationLiveTest { @Test public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "firstName:jo*"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertFalse(result.contains(userTom.getEmail())); @@ -92,7 +97,8 @@ public class JPASpecificationLiveTest { @Test public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "firstName:*n"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertFalse(result.contains(userTom.getEmail())); @@ -101,7 +107,8 @@ public class JPASpecificationLiveTest { @Test public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "firstName:*oh*"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertFalse(result.contains(userTom.getEmail())); @@ -110,13 +117,37 @@ public class JPASpecificationLiveTest { @Test public void givenAgeRange_whenGettingListOfUsers_thenCorrect() { final Response response = givenAuth().get(URL_PREFIX + "age>20,age<25"); - final String result = response.body().asString(); + final String result = response.body() + .asString(); assertTrue(result.contains(userJohn.getEmail())); assertFalse(result.contains(userTom.getEmail())); } + private final String ADV_URL_PREFIX = "http://localhost:8082/spring-security-rest-full/auth/users/spec/adv?search="; + + @Test + public void givenFirstOrLastName_whenGettingAdvListOfUsers_thenCorrect() { + final Response response = givenAuth().get(ADV_URL_PREFIX + "firstName:john OR lastName:doe"); + final String result = response.body() + .asString(); + assertTrue(result.contains(userJohn.getEmail())); + assertTrue(result.contains(userTom.getEmail())); + } + + @Test + public void givenFirstOrFirstNameAndAge_whenGettingAdvListOfUsers_thenCorrect() { + final Response response = givenAuth().get(ADV_URL_PREFIX + "( firstName:john OR firstName:tom ) AND age>22"); + final String result = response.body() + .asString(); + assertFalse(result.contains(userJohn.getEmail())); + assertTrue(result.contains(userTom.getEmail())); + } + private final RequestSpecification givenAuth() { - return RestAssured.given().auth().preemptive().basic("user1", "user1Pass"); + return RestAssured.given() + .auth() + .preemptive() + .basic("user1", "user1Pass"); } } From 54615ddd11f87be945780b5437d7bda060ed5fb5 Mon Sep 17 00:00:00 2001 From: Diane Duan Date: Mon, 3 Apr 2017 03:28:36 +0800 Subject: [PATCH 139/149] BAEL-640: Guide to Mathematical Operations with Guava (#1390) * Guava IntMath tests * Guava DoubleMath test * break down testDoubleMath() --- .../org/baeldung/guava/GuavaMathTest.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 guava/src/test/java/org/baeldung/guava/GuavaMathTest.java diff --git a/guava/src/test/java/org/baeldung/guava/GuavaMathTest.java b/guava/src/test/java/org/baeldung/guava/GuavaMathTest.java new file mode 100644 index 0000000000..d0c551032c --- /dev/null +++ b/guava/src/test/java/org/baeldung/guava/GuavaMathTest.java @@ -0,0 +1,192 @@ +package org.baeldung.guava; + +import com.google.common.math.DoubleMath; +import com.google.common.math.IntMath; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.math.BigInteger; +import java.math.RoundingMode; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class GuavaMathTest { + @Test + public void testIntMathAdd() { + try { + IntMath.checkedAdd(Integer.MAX_VALUE, 1); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + try { + IntMath.checkedAdd(Integer.MIN_VALUE, -1); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + int result1 = IntMath.checkedAdd(2, 1); + assertThat(result1, equalTo(3)); + + int result2 = IntMath.saturatedAdd(Integer.MAX_VALUE, 100); + assertThat(result2, equalTo(Integer.MAX_VALUE)); + + int result3 = IntMath.saturatedAdd(Integer.MIN_VALUE, -100); + assertThat(result3, equalTo(Integer.MIN_VALUE)); + } + + @Test + public void testIntMathSubtract() { + try { + IntMath.checkedSubtract(Integer.MIN_VALUE, 1); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + try { + IntMath.checkedSubtract(Integer.MAX_VALUE, -1); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + }; + + int result1 = IntMath.checkedSubtract(200, 100); + assertThat(result1, equalTo(100)); + + int result2 = IntMath.saturatedSubtract(Integer.MIN_VALUE, 1); + assertThat(result2, equalTo(Integer.MIN_VALUE)); + + int result3 = IntMath.saturatedSubtract(Integer.MAX_VALUE, -1); + assertThat(result3, equalTo(Integer.MAX_VALUE)); + } + + @Test + public void testIntMathMultiply() { + try { + IntMath.checkedMultiply(Integer.MAX_VALUE, 2); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + try { + IntMath.checkedMultiply(Integer.MIN_VALUE, 2); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + int result1 = IntMath.checkedMultiply(21, 3); + assertThat(result1, equalTo(63)); + + int result2 = IntMath.saturatedMultiply(Integer.MAX_VALUE, 2); + assertThat(result2, equalTo(Integer.MAX_VALUE)); + + int result3 = IntMath.saturatedMultiply(Integer.MIN_VALUE, 2); + assertThat(result3, equalTo(Integer.MIN_VALUE)); + } + + @Test + public void testIntMathPow() { + try { + IntMath.checkedPow(Integer.MAX_VALUE, 2); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + try { + IntMath.checkedPow(Integer.MIN_VALUE, 3); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + + int result1 = IntMath.saturatedPow(3, 3); + assertThat(result1, equalTo(27)); + + int result2 = IntMath.saturatedPow(Integer.MAX_VALUE, 2); + assertThat(result2, equalTo(Integer.MAX_VALUE)); + + int result3 = IntMath.saturatedPow(Integer.MIN_VALUE, 3); + assertThat(result3, equalTo(Integer.MIN_VALUE)); + } + + @Test + public void testIntMathRound() { + int result1 = IntMath.divide(3, 2, RoundingMode.DOWN); + assertThat(result1, equalTo(1)); + int result2 = IntMath.divide(3, 2, RoundingMode.UP); + assertThat(result2, equalTo(2)); + + int result3 = IntMath.log2(5, RoundingMode.FLOOR); + assertThat(result3, equalTo(2)); + int result4 = IntMath.log2(5, RoundingMode.CEILING); + assertThat(result4, equalTo(3)); + + int result5 = IntMath.log10(11, RoundingMode.HALF_UP); + assertThat(result5, equalTo(1)); + + int result6 = IntMath.sqrt(4, RoundingMode.UNNECESSARY); + assertThat(result6, equalTo(2)); + try { + IntMath.sqrt(5, RoundingMode.UNNECESSARY); + assertTrue(false); + } catch (ArithmeticException e) { + assertTrue(true); + } + } + + @Test + public void testIntMathAdditionalFunctions() { + int result1 = IntMath.gcd(15, 20); + assertThat(result1, equalTo(5)); + + int result2 = IntMath.mod(8, 3); + assertThat(result2, equalTo(2)); + + boolean result3 = IntMath.isPowerOfTwo(8); + assertTrue(result3); + boolean result4 = IntMath.isPowerOfTwo(9); + assertFalse(result4); + + int result5 = IntMath.factorial(4); + assertThat(result5, equalTo(24)); + + int result6 = IntMath.binomial(7, 3); + assertThat(result6, equalTo(35)); + } + + @Test + public void should_detect_integer() { + boolean result1 = DoubleMath.isMathematicalInteger(2.0); + assertThat(result1, equalTo(true)); + boolean result2 = DoubleMath.isMathematicalInteger(2.1); + assertThat(result2, equalTo(false)); + } + + @Test + public void should_round_to_integer_types() { + int result3 = DoubleMath.roundToInt(2.5, RoundingMode.DOWN); + assertThat(result3, equalTo(2)); + + long result4 = DoubleMath.roundToLong(2.5, RoundingMode.HALF_UP); + assertThat(result4, equalTo(3L)); + + BigInteger result5 = DoubleMath.roundToBigInteger(2.5, RoundingMode.UP); + assertThat(result5, equalTo(new BigInteger("3"))); + } + + @Test + public void should_calculate_log_2() { + int result6 = DoubleMath.log2(10, RoundingMode.UP); + assertThat(result6, equalTo(4)); + } +} \ No newline at end of file From 09329512cd24d1948b2d1aa98cfbfee3be31f1e8 Mon Sep 17 00:00:00 2001 From: buddhini81 Date: Mon, 3 Apr 2017 02:04:46 +0530 Subject: [PATCH 140/149] Modifications to the JavaEEAnnotationsSample application (#1522) * Delete the class As it is not relevant for the example * Update AccountServlet.java Changes made in doPost method. * Update UploadCustomerDocumentsServlet.java Changes made in doPost method * README file added * Fix error - add missing import --- .../JavaEEAnnotationsSample/README.txt | 67 +++++++++++++++++++ .../javaeeannotations/AccountServlet.java | 10 +-- .../DepositRequestListener.java | 20 ------ .../UploadCustomerDocumentsServlet.java | 5 ++ 4 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/README.txt delete mode 100644 jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/README.txt b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/README.txt new file mode 100644 index 0000000000..063856b2be --- /dev/null +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/README.txt @@ -0,0 +1,67 @@ +About the application +--------------------- +This application demonstrates the usage of JavaEE Web Annotations. + + +Contents of the application +--------------------------- +1. AccountServlet.java - Demonstrates the @WebServlet and @ServletSecurity annotation. + +NOTES: @WebServlet annotation designates the AccountServlet class as a Servlet component. + The usage of its parameters 'urlPatterns' & 'initParams' can be observed. + An initialization parameter 'type' is being set to denote the type of the bank account. + + @ServletSecurity annotation imposes security constraints on the AccountServlet based on + the tomcat-users.xml (this code assumes there is a role 'admin' in your tomcat-users.xml) + +N.B : To see @ServletSecurity annotation in action, please uncomment the annotation code + for @ServletSecurity. + + +2. BankAppServletContextListener.java - Demonstrates the @WebListener annotation for denoting a class as a ServletContextListener. + +NOTES: Sets a Servlet context attribute ATTR_DEFAULT_LANGUAGE to 'english' on web application start up, + which can then be used throughout the application. + + +3. LogInFilter.java - Demonstrates the @WebFilter annotation. + +NOTES: @WebFilter annotation designates the LogInFilter class as a Filter component. + It filters all requests to the bank account servlet and redirects them to + the login page. + +N.B : To see @WebFilter annotation in action, please uncomment the annotation code for @WebFilter. + + +4. UploadCustomerDocumentsServlet.java - Demonstrates the @MultipartConfig annotation. + +NOTES: @MultipartConfig anotation designates the UploadCustomerDocumentsServlet Servlet component, + to handle multipart/form-data requests. + To see it in action, deploy the web application an access the url: http://:/JavaEEAnnotationsSample/upload.jsp + Once you upload a file from here, it will get uploaded to D:/custDocs (assuming such a folder exists). + + +5. index.jsp - This is the welcome page. + +NOTES: You can enter a deposit amount here and click on the 'Deposit' button to see the AccountServlet in action. + +6. login.jsp - All requests to the AccountServlet are redirected to this page, if the LogInFilter is imposed. + +7. upload.jsp - Demonstrates the usage of handling multipart/form-data requests by the UploadCustomerDocumentsServlet. + + +Building and Running the application +------------------------------------ +To build the application: + +1. Open the project in eclipse +2. Right click on it in eclispe and choose Run As > Maven build +3. Give 'clean install' under Goals +4. This should build the WAR file of the application + +To run the application: + +1. Right click on the project +2. Run as > Run on Server +3. This will start you Tomcat server and deploy the application (Provided that you have configured Tomcat in your eclipse) +4. You should now be able to access the url : http://:/JavaEEAnnotationsSample diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java index e24eb307bb..a8ed74984b 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/AccountServlet.java @@ -36,19 +36,15 @@ public class AccountServlet extends javax.servlet.http.HttpServlet { } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + double accountBalance = 1000d; - double interestRate = Double.parseDouble(request.getAttribute("interest").toString()); - String paramDepositAmt = request.getParameter("dep"); double depositAmt = Double.parseDouble(paramDepositAmt); accountBalance = accountBalance + depositAmt; - + PrintWriter writer = response.getWriter(); - writer.println(" Balance of " + accountType + " account is: " + - accountBalance + "
This account bares an interest rate of " + interestRate + - " % "); + writer.println(" Balance of " + accountType + " account is: " + accountBalance + ""); writer.flush(); - } } diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java deleted file mode 100644 index f361c84b97..0000000000 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/DepositRequestListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.baeldung.javaeeannotations; - -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.annotation.WebListener; -import javax.servlet.http.HttpServletRequest; - -@WebListener -public class DepositRequestListener implements ServletRequestListener { - - public void requestDestroyed(ServletRequestEvent event) { - - } - - public void requestInitialized(ServletRequestEvent evet) { - HttpServletRequest req = (HttpServletRequest)evet.getServletRequest(); - req.setAttribute("interest", new Double(10)); - } - -} diff --git a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java index 3a139ad7cc..28922dba46 100644 --- a/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java +++ b/jee7/src/main/java/com/baeldung/javaeeannotations/JavaEEAnnotationsSample/src/main/java/com/baeldung/javaeeannotations/UploadCustomerDocumentsServlet.java @@ -1,6 +1,7 @@ package com.baeldung.javaeeannotations; import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; @@ -24,6 +25,10 @@ public class UploadCustomerDocumentsServlet extends HttpServlet { for (Part part : request.getParts()) { part.write("myFile"); } + + PrintWriter writer = response.getWriter(); + writer.println("File uploaded successfully!"); + writer.flush(); } } From 44e63c68f2520cafd3bd73f703c9500f994eb1ba Mon Sep 17 00:00:00 2001 From: Tomasz Lelek Date: Sun, 2 Apr 2017 23:54:28 +0200 Subject: [PATCH 141/149] Bael 769 javers (#1543) * BAEL-769 code for javers article * BAEL-769 add more examples --- libraries/pom.xml | 6 + .../java/com/baeldung/javers/Address.java | 11 ++ .../main/java/com/baeldung/javers/Person.java | 27 +++++ .../baeldung/javers/PersonWithAddress.java | 40 +++++++ .../java/com/baeldung/javers/JaversTest.java | 113 ++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 libraries/src/main/java/com/baeldung/javers/Address.java create mode 100644 libraries/src/main/java/com/baeldung/javers/Person.java create mode 100644 libraries/src/main/java/com/baeldung/javers/PersonWithAddress.java create mode 100644 libraries/src/test/java/com/baeldung/javers/JaversTest.java diff --git a/libraries/pom.xml b/libraries/pom.xml index 71d0e76c8a..11295230b4 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -67,6 +67,11 @@ jsonassert ${jsonassert.version} + + org.javers + javers-core + ${javers.version} + @@ -78,6 +83,7 @@ 3.21.0-GA 3.6.2 1.5.0 + 3.1.0 \ No newline at end of file diff --git a/libraries/src/main/java/com/baeldung/javers/Address.java b/libraries/src/main/java/com/baeldung/javers/Address.java new file mode 100644 index 0000000000..14f5907ef6 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javers/Address.java @@ -0,0 +1,11 @@ +package com.baeldung.javers; + + +public class Address { + private String country; + + public Address(String country) { + this.country = country; + } + +} diff --git a/libraries/src/main/java/com/baeldung/javers/Person.java b/libraries/src/main/java/com/baeldung/javers/Person.java new file mode 100644 index 0000000000..c53a09358b --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javers/Person.java @@ -0,0 +1,27 @@ +package com.baeldung.javers; + +public class Person { + private Integer id; + private String name; + + public Person(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setId(Integer id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/libraries/src/main/java/com/baeldung/javers/PersonWithAddress.java b/libraries/src/main/java/com/baeldung/javers/PersonWithAddress.java new file mode 100644 index 0000000000..0b4e33fcb5 --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javers/PersonWithAddress.java @@ -0,0 +1,40 @@ +package com.baeldung.javers; + + +import java.util.List; + +public class PersonWithAddress { + private Integer id; + private String name; + private List
address; + + public PersonWithAddress(Integer id, String name, List
address) { + this.id = id; + this.name = name; + this.address = address; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setId(Integer id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public List
getAddress() { + return address; + } + + public void setAddress(List
address) { + this.address = address; + } +} diff --git a/libraries/src/test/java/com/baeldung/javers/JaversTest.java b/libraries/src/test/java/com/baeldung/javers/JaversTest.java new file mode 100644 index 0000000000..e8e3e62e08 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javers/JaversTest.java @@ -0,0 +1,113 @@ +package com.baeldung.javers; + + +import org.javers.common.collections.Lists; +import org.javers.core.Javers; +import org.javers.core.JaversBuilder; +import org.javers.core.diff.Diff; +import org.javers.core.diff.changetype.NewObject; +import org.javers.core.diff.changetype.ObjectRemoved; +import org.javers.core.diff.changetype.ValueChange; +import org.javers.core.diff.changetype.container.ListChange; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JaversTest { + + @Test + public void givenPersonObject_whenApplyModificationOnIt_thenShouldDetectChange() { + //given + Javers javers = JaversBuilder.javers().build(); + + Person person = new Person(1, "Michael Program"); + Person personAfterModification = new Person(1, "Michael Java"); + + //when + Diff diff = javers.compare(person, personAfterModification); + + //then + ValueChange change = diff.getChangesByType(ValueChange.class).get(0); + + assertThat(diff.getChanges()).hasSize(1); + assertThat(change.getPropertyName()).isEqualTo("name"); + assertThat(change.getLeft()).isEqualTo("Michael Program"); + assertThat(change.getRight()).isEqualTo("Michael Java"); + } + + + @Test + public void givenListOfPersons_whenCompare_ThenShouldDetectChanges() { + //given + Javers javers = JaversBuilder.javers().build(); + Person personThatWillBeRemoved = new Person(2, "Thomas Link"); + List oldList = Lists.asList(new Person(1, "Michael Program"), personThatWillBeRemoved); + List newList = Lists.asList(new Person(1, "Michael Not Program")); + + //when + Diff diff = javers.compareCollections(oldList, newList, Person.class); + + //then + assertThat(diff.getChanges()).hasSize(3); + + + ValueChange valueChange = diff.getChangesByType(ValueChange.class).get(0); + assertThat(valueChange.getPropertyName()).isEqualTo("name"); + assertThat(valueChange.getLeft()).isEqualTo("Michael Program"); + assertThat(valueChange.getRight()).isEqualTo("Michael Not Program"); + + ObjectRemoved objectRemoved = diff.getChangesByType(ObjectRemoved.class).get(0); + assertThat(objectRemoved.getAffectedObject().get().equals(personThatWillBeRemoved)).isTrue(); + + ListChange listChange = diff.getChangesByType(ListChange.class).get(0); + assertThat(listChange.getValueRemovedChanges().size()).isEqualTo(1); + + } + + @Test + public void givenListOfPerson_whenPersonHasNewAddress_thenDetectThatChange() { + //given + Javers javers = JaversBuilder.javers().build(); + + PersonWithAddress person = + new PersonWithAddress(1, "Tom", Arrays.asList(new Address("England"))); + + PersonWithAddress personWithNewAddress = + new PersonWithAddress(1, "Tom", + Arrays.asList(new Address("England"), new Address("USA"))); + + + //when + Diff diff = javers.compare(person, personWithNewAddress); + List objectsByChangeType = diff.getObjectsByChangeType(NewObject.class); + + //then + assertThat(objectsByChangeType).hasSize(1); + assertThat(objectsByChangeType.get(0).equals(new Address("USA"))); + } + + @Test + public void givenListOfPerson_whenPersonRemovedAddress_thenDetectThatChange() { + //given + Javers javers = JaversBuilder.javers().build(); + + PersonWithAddress person = + new PersonWithAddress(1, "Tom", Arrays.asList(new Address("England"))); + + PersonWithAddress personWithNewAddress = + new PersonWithAddress(1, "Tom", Collections.emptyList()); + + + //when + Diff diff = javers.compare(person, personWithNewAddress); + List objectsByChangeType = diff.getObjectsByChangeType(ObjectRemoved.class); + + //then + assertThat(objectsByChangeType).hasSize(1); + assertThat(objectsByChangeType.get(0).equals(new Address("England"))); + } +} From 77d270a4b1bf0f145083ba36e64a08281ef2392c Mon Sep 17 00:00:00 2001 From: lor6 Date: Mon, 3 Apr 2017 04:53:22 +0300 Subject: [PATCH 142/149] in memory dbs config (#1551) * in memory dbs config * small fix --- spring-data-rest/pom.xml | 22 ++++++- .../java/com/baeldung/config/DbConfig.java | 61 +++++++++++++++++++ .../java/com/baeldung/models/Address.java | 3 +- .../main/java/com/baeldung/models/Author.java | 3 +- .../main/java/com/baeldung/models/Book.java | 3 +- .../java/com/baeldung/models/Library.java | 3 +- .../resources/persistence-derby.properties | 8 +++ .../main/resources/persistence-h2.properties | 8 +++ .../resources/persistence-hsqldb.properties | 8 +++ .../resources/persistence-sqlite.properties | 4 ++ 10 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 spring-data-rest/src/main/java/com/baeldung/config/DbConfig.java create mode 100644 spring-data-rest/src/main/resources/persistence-derby.properties create mode 100644 spring-data-rest/src/main/resources/persistence-h2.properties create mode 100644 spring-data-rest/src/main/resources/persistence-hsqldb.properties create mode 100644 spring-data-rest/src/main/resources/persistence-sqlite.properties diff --git a/spring-data-rest/pom.xml b/spring-data-rest/pom.xml index 1e1ec02e96..91b6d61878 100644 --- a/spring-data-rest/pom.xml +++ b/spring-data-rest/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.1.RELEASE + 1.5.2.RELEASE @@ -41,6 +41,26 @@ com.h2database h2 + + org.springframework.boot diff --git a/spring-data-rest/src/main/java/com/baeldung/config/DbConfig.java b/spring-data-rest/src/main/java/com/baeldung/config/DbConfig.java new file mode 100644 index 0000000000..8d1f9de497 --- /dev/null +++ b/spring-data-rest/src/main/java/com/baeldung/config/DbConfig.java @@ -0,0 +1,61 @@ +package com.baeldung.config; + +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +//@Configuration +@EnableJpaRepositories(basePackages = "com.baeldung.repositories") +// @PropertySource("persistence-h2.properties") +// @PropertySource("persistence-hsqldb.properties") +// @PropertySource("persistence-derby.properties") +public class DbConfig { + + @Autowired + private Environment env; + + @Bean + public DataSource dataSource() { + final DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(env.getProperty("driverClassName")); + dataSource.setUrl(env.getProperty("url")); + dataSource.setUsername(env.getProperty("user")); + dataSource.setPassword(env.getProperty("password")); + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource()); + em.setPackagesToScan(new String[] { "com.baeldung.models" }); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + em.setJpaProperties(additionalProperties()); + return em; + } + + final Properties additionalProperties() { + final Properties hibernateProperties = new Properties(); + if (env.getProperty("hibernate.hbm2ddl.auto") != null) { + hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); + } + if (env.getProperty("hibernate.dialect") != null) { + hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect")); + } + if (env.getProperty("hibernate.show_sql") != null) { + hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql")); + } + return hibernateProperties; + } + +} diff --git a/spring-data-rest/src/main/java/com/baeldung/models/Address.java b/spring-data-rest/src/main/java/com/baeldung/models/Address.java index 98cf5f0869..82e3783f3e 100644 --- a/spring-data-rest/src/main/java/com/baeldung/models/Address.java +++ b/spring-data-rest/src/main/java/com/baeldung/models/Address.java @@ -3,6 +3,7 @@ package com.baeldung.models; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; @@ -10,7 +11,7 @@ import javax.persistence.OneToOne; public class Address { @Id - @GeneratedValue + @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column(nullable = false) diff --git a/spring-data-rest/src/main/java/com/baeldung/models/Author.java b/spring-data-rest/src/main/java/com/baeldung/models/Author.java index 7025fa4ad3..cdd04cbdcf 100644 --- a/spring-data-rest/src/main/java/com/baeldung/models/Author.java +++ b/spring-data-rest/src/main/java/com/baeldung/models/Author.java @@ -6,6 +6,7 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; @@ -15,7 +16,7 @@ import javax.persistence.ManyToMany; public class Author { @Id - @GeneratedValue + @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column(nullable = false) diff --git a/spring-data-rest/src/main/java/com/baeldung/models/Book.java b/spring-data-rest/src/main/java/com/baeldung/models/Book.java index 8f836a259a..06abfb8f4d 100644 --- a/spring-data-rest/src/main/java/com/baeldung/models/Book.java +++ b/spring-data-rest/src/main/java/com/baeldung/models/Book.java @@ -5,6 +5,7 @@ import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToMany; @@ -14,7 +15,7 @@ import javax.persistence.ManyToOne; public class Book { @Id - @GeneratedValue + @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column(nullable = false) diff --git a/spring-data-rest/src/main/java/com/baeldung/models/Library.java b/spring-data-rest/src/main/java/com/baeldung/models/Library.java index 61eead16ea..c27512d0e4 100644 --- a/spring-data-rest/src/main/java/com/baeldung/models/Library.java +++ b/spring-data-rest/src/main/java/com/baeldung/models/Library.java @@ -5,6 +5,7 @@ import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; @@ -16,7 +17,7 @@ import org.springframework.data.rest.core.annotation.RestResource; public class Library { @Id - @GeneratedValue + @GeneratedValue(strategy=GenerationType.IDENTITY) private long id; @Column diff --git a/spring-data-rest/src/main/resources/persistence-derby.properties b/spring-data-rest/src/main/resources/persistence-derby.properties new file mode 100644 index 0000000000..9bcd91c6f9 --- /dev/null +++ b/spring-data-rest/src/main/resources/persistence-derby.properties @@ -0,0 +1,8 @@ +driverClassName=org.apache.derby.jdbc.EmbeddedDriver +url=jdbc:derby:memory:myD;create=true +username=sa +password= + +hibernate.dialect=org.hibernate.dialect.DerbyDialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=create-drop \ No newline at end of file diff --git a/spring-data-rest/src/main/resources/persistence-h2.properties b/spring-data-rest/src/main/resources/persistence-h2.properties new file mode 100644 index 0000000000..d535f9dbe4 --- /dev/null +++ b/spring-data-rest/src/main/resources/persistence-h2.properties @@ -0,0 +1,8 @@ +driverClassName=org.h2.Driver +url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1 +username=sa +password= + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=create-drop \ No newline at end of file diff --git a/spring-data-rest/src/main/resources/persistence-hsqldb.properties b/spring-data-rest/src/main/resources/persistence-hsqldb.properties new file mode 100644 index 0000000000..00464f1026 --- /dev/null +++ b/spring-data-rest/src/main/resources/persistence-hsqldb.properties @@ -0,0 +1,8 @@ +driverClassName=org.hsqldb.jdbc.JDBCDriver +url=jdbc:hsqldb:mem:myDb +username=sa +password= + +hibernate.dialect=org.hibernate.dialect.HSQLDialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=create-drop \ No newline at end of file diff --git a/spring-data-rest/src/main/resources/persistence-sqlite.properties b/spring-data-rest/src/main/resources/persistence-sqlite.properties new file mode 100644 index 0000000000..018c2cbaca --- /dev/null +++ b/spring-data-rest/src/main/resources/persistence-sqlite.properties @@ -0,0 +1,4 @@ +driverClassName=org.sqlite.JDBC +url=jdbc:sqlite:memory:myDb +username=sa +password=sa \ No newline at end of file From 1a05305b31f966f64e0f8e5fe1ea06ea26f62be2 Mon Sep 17 00:00:00 2001 From: lor6 Date: Mon, 3 Apr 2017 05:18:07 +0300 Subject: [PATCH 143/149] custom failureAnalyzer (#1559) --- .../FailureAnalyzerApplication.java | 15 +++++++++++ ...yBeanNotOfRequiredTypeFailureAnalyzer.java | 25 +++++++++++++++++++ .../com/baeldung/failureanalyzer/MyDAO.java | 5 ++++ .../baeldung/failureanalyzer/MySecondDAO.java | 8 ++++++ .../baeldung/failureanalyzer/MyService.java | 13 ++++++++++ .../main/resources/META-INF/spring.factories | 1 + 6 files changed, 67 insertions(+) create mode 100644 spring-boot/src/main/java/com/baeldung/failureanalyzer/FailureAnalyzerApplication.java create mode 100644 spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java create mode 100644 spring-boot/src/main/java/com/baeldung/failureanalyzer/MyDAO.java create mode 100644 spring-boot/src/main/java/com/baeldung/failureanalyzer/MySecondDAO.java create mode 100644 spring-boot/src/main/java/com/baeldung/failureanalyzer/MyService.java create mode 100644 spring-boot/src/main/resources/META-INF/spring.factories diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/FailureAnalyzerApplication.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/FailureAnalyzerApplication.java new file mode 100644 index 0000000000..3489732b6f --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/FailureAnalyzerApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.failureanalyzer; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class FailureAnalyzerApplication { + @RolesAllowed("*") + public static void main(String[] args) { + System.setProperty("security.basic.enabled", "false"); + SpringApplication.run(FailureAnalyzerApplication.class, args); + } +} diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java new file mode 100644 index 0000000000..2f83ad6b57 --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java @@ -0,0 +1,25 @@ +package com.baeldung.failureanalyzer; + +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; + +public class MyBeanNotOfRequiredTypeFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, BeanNotOfRequiredTypeException cause) { + return new FailureAnalysis(getDescription(cause), getAction(cause), cause); + } + + private String getDescription(BeanNotOfRequiredTypeException ex) { + return "The bean " + ex.getBeanName() // + + " could not be injected as " + ex.getRequiredType().getName() // + + " because it is of type " + ex.getActualType().getName(); + } + + private String getAction(BeanNotOfRequiredTypeException ex) { + return "Consider creating a bean with name "+ ex.getBeanName() // + + " of type " + ex.getRequiredType().getName(); + } + +} diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyDAO.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyDAO.java new file mode 100644 index 0000000000..ddaeb28574 --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyDAO.java @@ -0,0 +1,5 @@ +package com.baeldung.failureanalyzer; + +public class MyDAO { + +} diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MySecondDAO.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MySecondDAO.java new file mode 100644 index 0000000000..12dd73a05b --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MySecondDAO.java @@ -0,0 +1,8 @@ +package com.baeldung.failureanalyzer; + +import org.springframework.stereotype.Repository; + +@Repository("myDAO") +public class MySecondDAO { + +} diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyService.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyService.java new file mode 100644 index 0000000000..72334ca8fa --- /dev/null +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyService.java @@ -0,0 +1,13 @@ +package com.baeldung.failureanalyzer; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; + +@Service +public class MyService { + + @Resource(name = "myDAO") + private MyDAO myDAO; + +} diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..e3d3aa4c8e --- /dev/null +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.diagnostics.FailureAnalyzer=com.baeldung.failureanalyzer.MyBeanNotOfRequiredTypeFailureAnalyzer \ No newline at end of file From 4d08f3db6d0e990d91fb146d141beb59975972df Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 3 Apr 2017 16:14:11 +0200 Subject: [PATCH 144/149] Refactor Analyzer examples (#1579) * Refactor Analyzer examples * Refactor Analyzer examples --- .../MyBeanNotOfRequiredTypeFailureAnalyzer.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java index 2f83ad6b57..2bbae8944a 100644 --- a/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java +++ b/spring-boot/src/main/java/com/baeldung/failureanalyzer/MyBeanNotOfRequiredTypeFailureAnalyzer.java @@ -4,7 +4,8 @@ import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; -public class MyBeanNotOfRequiredTypeFailureAnalyzer extends AbstractFailureAnalyzer { +public class MyBeanNotOfRequiredTypeFailureAnalyzer + extends AbstractFailureAnalyzer { @Override protected FailureAnalysis analyze(Throwable rootFailure, BeanNotOfRequiredTypeException cause) { @@ -12,14 +13,16 @@ public class MyBeanNotOfRequiredTypeFailureAnalyzer extends AbstractFailureAnaly } private String getDescription(BeanNotOfRequiredTypeException ex) { - return "The bean " + ex.getBeanName() // - + " could not be injected as " + ex.getRequiredType().getName() // - + " because it is of type " + ex.getActualType().getName(); + return String.format("The bean %s could not be injected as %s because it is of type %s", + ex.getBeanName(), + ex.getRequiredType().getName(), + ex.getActualType().getName()); } private String getAction(BeanNotOfRequiredTypeException ex) { - return "Consider creating a bean with name "+ ex.getBeanName() // - + " of type " + ex.getRequiredType().getName(); + return String.format("Consider creating a bean with name %s of type %s", + ex.getBeanName(), + ex.getRequiredType().getName()); } } From e7cc45644ebf45fb9725b78318a9e1faa7f934a0 Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 3 Apr 2017 18:03:10 +0200 Subject: [PATCH 145/149] Optimize build (#1582) --- ...t.java => JsoupParserIntegrationTest.java} | 4 +- pom.xml | 1 + spring-data-gemfire/pom.xml | 6 +++ ...=> EmployeeRepositoryIntegrationTest.java} | 6 +-- spring-data-javaslang/pom.xml | 17 +++++-- ...gTests.java => SpringIntegrationTest.java} | 45 ++++++++++--------- 6 files changed, 50 insertions(+), 29 deletions(-) rename jsoup/src/test/java/com/baeldung/jsoup/{JsoupParserTest.java => JsoupParserIntegrationTest.java} (98%) rename spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/{EmployeeRepositoryTest.java => EmployeeRepositoryIntegrationTest.java} (96%) rename spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/{SpringTests.java => SpringIntegrationTest.java} (80%) diff --git a/jsoup/src/test/java/com/baeldung/jsoup/JsoupParserTest.java b/jsoup/src/test/java/com/baeldung/jsoup/JsoupParserIntegrationTest.java similarity index 98% rename from jsoup/src/test/java/com/baeldung/jsoup/JsoupParserTest.java rename to jsoup/src/test/java/com/baeldung/jsoup/JsoupParserIntegrationTest.java index ba6d7358bc..dadd57b0ed 100644 --- a/jsoup/src/test/java/com/baeldung/jsoup/JsoupParserTest.java +++ b/jsoup/src/test/java/com/baeldung/jsoup/JsoupParserIntegrationTest.java @@ -14,9 +14,9 @@ import java.io.IOException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class JsoupParserTest { +public class JsoupParserIntegrationTest { - Document doc; + private Document doc; @Before public void setUp() throws IOException { diff --git a/pom.xml b/pom.xml index c8d060ec04..a6b46a0e39 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ spring-data-couchbase-2 spring-data-dynamodb spring-data-elasticsearch + spring-data-javaslang spring-data-mongodb spring-data-neo4j spring-data-redis diff --git a/spring-data-gemfire/pom.xml b/spring-data-gemfire/pom.xml index f387b04651..eb450ebc81 100644 --- a/spring-data-gemfire/pom.xml +++ b/spring-data-gemfire/pom.xml @@ -75,6 +75,12 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + + **/*IntegrationTest.java + **/*LiveTest.java + + diff --git a/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java b/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryIntegrationTest.java similarity index 96% rename from spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java rename to spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryIntegrationTest.java index 8714ad2d81..26f7bc8a4e 100644 --- a/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryTest.java +++ b/spring-data-gemfire/src/test/java/com/baeldung/spring/data/gemfire/repository/EmployeeRepositoryIntegrationTest.java @@ -18,13 +18,13 @@ import static junit.framework.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=GemfireConfiguration.class, loader=AnnotationConfigContextLoader.class) -public class EmployeeRepositoryTest { +public class EmployeeRepositoryIntegrationTest { - @Autowired + @Autowired private EmployeeRepository employeeRepository; @Autowired - FunctionExecution execution; + private FunctionExecution execution; @Test public void whenEmployeeIsSaved_ThenAbleToRead(){ diff --git a/spring-data-javaslang/pom.xml b/spring-data-javaslang/pom.xml index c265e893cc..76fbce1e2e 100644 --- a/spring-data-javaslang/pom.xml +++ b/spring-data-javaslang/pom.xml @@ -1,8 +1,8 @@ 4.0.0 - spring-data-javaslangb - spring-data-javaslangb + spring-data-javaslang + spring-data-javaslang 0.0.1-SNAPSHOT UTF-8 @@ -65,7 +65,18 @@ ${project.build.testSourceDirectory} - + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + diff --git a/spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringTests.java b/spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringIntegrationTest.java similarity index 80% rename from spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringTests.java rename to spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringIntegrationTest.java index 59a6c120fa..7a23fa1ef2 100644 --- a/spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringTests.java +++ b/spring-data-javaslang/src/test/java/com/baeldung/spring_data_tests/SpringIntegrationTest.java @@ -1,34 +1,33 @@ package com.baeldung.spring_data_tests; -import org.junit.Test; -import org.junit.runner.RunWith; - -import com.baeldung.spring_data_app.MainApp; import com.baeldung.spring_data.model.Book; import com.baeldung.spring_data.model.JavaBook; import com.baeldung.spring_data.repository.BookRepository; import com.baeldung.spring_data.repository.JavaBookRepository; - +import com.baeldung.spring_data_app.MainApp; +import javaslang.collection.List; +import javaslang.collection.Seq; +import javaslang.control.Option; +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.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringRunner; -import javaslang.collection.Seq; -import javaslang.collection.List; -import javaslang.control.Option; - import java.util.ArrayList; +import static org.assertj.core.api.Assertions.assertThat; + @RunWith(SpringRunner.class) @SpringBootTest(classes = MainApp.class,webEnvironment = WebEnvironment.NONE) -public class SpringTests { +public class SpringIntegrationTest { @Autowired - JavaBookRepository javaRepository; + private JavaBookRepository javaRepository; @Autowired - BookRepository repository; + private BookRepository repository; @Test public void should_return_seq(){ @@ -38,7 +37,8 @@ public class SpringTests { testBook.setAuthors(authors); Book book = repository.save(testBook); Option> books = repository.findByTitleContaining("Seq Test"); - assert(!books.isEmpty()); + + assertThat(books).isNotEmpty(); } @@ -50,8 +50,9 @@ public class SpringTests { testBook.setAuthors(authors); Book book = repository.save(testBook); Option retBook = repository.findById(1L); - assert(retBook.isDefined() && !retBook.isEmpty()); - assert(retBook.get() != null); + + assertThat(retBook.isDefined()).isTrue(); + assertThat(retBook).isNotEmpty(); } @Test @@ -64,9 +65,11 @@ public class SpringTests { testBook.setAuthors(authors); JavaBook book = javaRepository.save(testBook); java.util.List books = javaRepository.findByTitleContaining("Seq"); - assert(!books.isEmpty()); - assert(books.size() == 1); - assert(books.get(0).getTitle().equals("Javaslang in Spring Data Seq Return")); + assertThat(books) + .isNotEmpty() + .hasSize(1) + .extracting("title") + .contains("Javaslang in Spring Data Seq Return"); } @Test @@ -79,8 +82,8 @@ public class SpringTests { testBook.setAuthors(authors); JavaBook book = javaRepository.save(testBook); JavaBook retBook = javaRepository.findById(1L); - assert(retBook != null); - assert(retBook.getId() == 1L); - assert(retBook.getTitle().contains("Data")); + + assertThat(retBook.getId()).isEqualTo(1L); + assertThat(retBook.getTitle()).isEqualTo("Javaslang in Spring Data"); } } \ No newline at end of file From 2a76b9c65689b05054ae2d6d82dd77ef73496586 Mon Sep 17 00:00:00 2001 From: gitterjim-I Date: Mon, 3 Apr 2017 23:30:45 +0100 Subject: [PATCH 146/149] change test names, bael-667 (#1581) * article Bael-667 initial commit. * Switch to use logging framework for output. * Make code more concise. Refactor as suggested. * modify test method names --- .../list/flattennestedlist/FlattenNestedListTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java index b7939d09da..285b217156 100644 --- a/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java +++ b/core-java/src/test/java/com/baeldung/list/flattennestedlist/FlattenNestedListTest.java @@ -17,7 +17,7 @@ public class FlattenNestedListTest { List> lol = asList(asList("one:one"), asList("two:one", "two:two", "two:three"), asList("three:one", "three:two", "three:three", "three:four")); @Test - public void givenString_flattenNestedList1() { + public void givenNestedList_thenFlattenNestedListImperative() { List ls = flattenListOfListsImperatively(lol); assertNotNull(ls); @@ -27,7 +27,7 @@ public class FlattenNestedListTest { } @Test - public void givenString_flattenNestedList2() { + public void givenNestedList_thenFlattenNestedListStream() { List ls = flattenListOfListsStream(lol); assertNotNull(ls); From 8da820b35a34ae0a5cc6cc54528032a4ee7e2ad7 Mon Sep 17 00:00:00 2001 From: Wim Deblauwe Date: Tue, 4 Apr 2017 00:35:35 +0200 Subject: [PATCH 147/149] BAEL-87 - @JsonComponent in Spring Boot (#1519) --- .../java/org/baeldung/jsoncomponent/User.java | 15 ++++++ .../jsoncomponent/UserCombinedSerializer.java | 46 +++++++++++++++++++ .../jsoncomponent/UserJsonDeserializer.java | 22 +++++++++ .../jsoncomponent/UserJsonSerializer.java | 29 ++++++++++++ .../UserJsonDeserializerTest.java | 27 +++++++++++ .../jsoncomponent/UserJsonSerializerTest.java | 27 +++++++++++ 6 files changed, 166 insertions(+) create mode 100644 spring-boot/src/main/java/org/baeldung/jsoncomponent/User.java create mode 100644 spring-boot/src/main/java/org/baeldung/jsoncomponent/UserCombinedSerializer.java create mode 100644 spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonDeserializer.java create mode 100644 spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonSerializer.java create mode 100644 spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonDeserializerTest.java create mode 100644 spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonSerializerTest.java diff --git a/spring-boot/src/main/java/org/baeldung/jsoncomponent/User.java b/spring-boot/src/main/java/org/baeldung/jsoncomponent/User.java new file mode 100644 index 0000000000..8961874526 --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/jsoncomponent/User.java @@ -0,0 +1,15 @@ +package org.baeldung.jsoncomponent; + +import javafx.scene.paint.Color; + +public class User { + private final Color favoriteColor; + + public User(Color favoriteColor) { + this.favoriteColor = favoriteColor; + } + + public Color getFavoriteColor() { + return favoriteColor; + } +} diff --git a/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserCombinedSerializer.java b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserCombinedSerializer.java new file mode 100644 index 0000000000..302b0dce61 --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserCombinedSerializer.java @@ -0,0 +1,46 @@ +package org.baeldung.jsoncomponent; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.node.TextNode; +import javafx.scene.paint.Color; +import org.springframework.boot.jackson.JsonComponent; + +import java.io.IOException; + +@JsonComponent +public class UserCombinedSerializer { + public static class UserJsonSerializer extends JsonSerializer { + + @Override + public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("favoriteColor", + getColorAsWebColor(user.getFavoriteColor())); + jsonGenerator.writeEndObject(); + } + + private static String getColorAsWebColor(Color color) { + int r = (int) Math.round(color.getRed() * 255.0); + int g = (int) Math.round(color.getGreen() * 255.0); + int b = (int) Math.round(color.getBlue() * 255.0); + return String.format("#%02x%02x%02x", r, g, b); + } + } + + @JsonComponent + public static class UserJsonDeserializer extends JsonDeserializer { + @Override + public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); + TextNode favoriteColor = (TextNode) treeNode.get("favoriteColor"); + return new User(Color.web(favoriteColor.asText())); + } + } +} diff --git a/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonDeserializer.java b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonDeserializer.java new file mode 100644 index 0000000000..d18de7e3f1 --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonDeserializer.java @@ -0,0 +1,22 @@ +package org.baeldung.jsoncomponent; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.node.TextNode; +import javafx.scene.paint.Color; +import org.springframework.boot.jackson.JsonComponent; + +import java.io.IOException; + +@JsonComponent +public class UserJsonDeserializer extends JsonDeserializer { + @Override + public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); + TextNode favoriteColor = (TextNode) treeNode.get("favoriteColor"); + return new User(Color.web(favoriteColor.asText())); + } +} diff --git a/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonSerializer.java b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonSerializer.java new file mode 100644 index 0000000000..d90f662a4b --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/jsoncomponent/UserJsonSerializer.java @@ -0,0 +1,29 @@ +package org.baeldung.jsoncomponent; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import javafx.scene.paint.Color; +import org.springframework.boot.jackson.JsonComponent; + +import java.io.IOException; + +@JsonComponent +public class UserJsonSerializer extends JsonSerializer { + + @Override + public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("favoriteColor", + getColorAsWebColor(user.getFavoriteColor())); + jsonGenerator.writeEndObject(); + } + + private static String getColorAsWebColor(Color color) { + int r = (int) Math.round(color.getRed() * 255.0); + int g = (int) Math.round(color.getGreen() * 255.0); + int b = (int) Math.round(color.getBlue() * 255.0); + return String.format("#%02x%02x%02x", r, g, b); + } +} diff --git a/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonDeserializerTest.java b/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonDeserializerTest.java new file mode 100644 index 0000000000..51c1c72ea3 --- /dev/null +++ b/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonDeserializerTest.java @@ -0,0 +1,27 @@ +package org.baeldung.jsoncomponent; + +import com.fasterxml.jackson.databind.ObjectMapper; +import javafx.scene.paint.Color; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +@JsonTest +@RunWith(SpringRunner.class) +public class UserJsonDeserializerTest { + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testDeserialize() throws IOException { + User user = objectMapper.readValue("{\"favoriteColor\":\"#f0f8ff\"}", User.class); + assertEquals(Color.ALICEBLUE, user.getFavoriteColor()); + } +} \ No newline at end of file diff --git a/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonSerializerTest.java b/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonSerializerTest.java new file mode 100644 index 0000000000..c85af4a17f --- /dev/null +++ b/spring-boot/src/test/java/org/baeldung/jsoncomponent/UserJsonSerializerTest.java @@ -0,0 +1,27 @@ +package org.baeldung.jsoncomponent; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import javafx.scene.paint.Color; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.assertEquals; + +@JsonTest +@RunWith(SpringRunner.class) +public class UserJsonSerializerTest { + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testSerialization() throws JsonProcessingException { + String json = objectMapper.writeValueAsString(new User(Color.ALICEBLUE)); + assertEquals("{\"favoriteColor\":\"#f0f8ff\"}", json); + } +} \ No newline at end of file From a4f4301196de133395001482597c4d6267ff7fbf Mon Sep 17 00:00:00 2001 From: Yasin Date: Tue, 4 Apr 2017 15:58:51 +0530 Subject: [PATCH 148/149] BAEL-729 Adding custom info to actuator's /info endpoint (#1584) * yasin.bhojawala@gmail.com Evaluation article on Different Types of Bean Injection in Spring * Revert "yasin.bhojawala@gmail.com" This reverts commit 963cc51a7a15b75b550108fe4e198cd65a274032. * Fixing compilation error and removing unused import * Introduction to Java9 StackWalking API - yasin.bhojawala@gmail.com Code examples for the article "Introduction to Java9 StackWalking API" * BAEL-608 Introduction to Java9 StackWalking API * BAEL-608 Introduction to Java9 StackWalking API changing the test names to BDD style * BAEL-608 Introduction to Java9 StackWalking API correcting the typo * BAEL-608 Introduction to Java9 StackWalking API improving method names * BAEL-608 Introduction to Java9 StackWalking API test method names improvements * BAEL-718 Quick intro to javatuples * merging pom from master * BAEL-722 Intro to JSONassert * BAEL-722 Intro to JSONassert Updated to 1.5.0 * BAEL-745 Quick Math.pow example * BAEL-729 Adding custom info to actuator's /info endpoint --- .../java/org/baeldung/boot/model/User.java | 41 +++++++++++++++++++ .../boot/repository/UserRepository.java | 10 +++++ .../info/TotalUsersInfoContributor.java | 26 ++++++++++++ .../src/main/resources/application.properties | 5 ++- spring-boot/src/main/resources/data.sql | 5 +++ spring-boot/src/main/resources/schema.sql | 6 +++ 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 spring-boot/src/main/java/org/baeldung/boot/model/User.java create mode 100644 spring-boot/src/main/java/org/baeldung/boot/repository/UserRepository.java create mode 100644 spring-boot/src/main/java/org/baeldung/endpoints/info/TotalUsersInfoContributor.java create mode 100644 spring-boot/src/main/resources/data.sql create mode 100644 spring-boot/src/main/resources/schema.sql diff --git a/spring-boot/src/main/java/org/baeldung/boot/model/User.java b/spring-boot/src/main/java/org/baeldung/boot/model/User.java new file mode 100644 index 0000000000..f60ac86fe4 --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/boot/model/User.java @@ -0,0 +1,41 @@ +package org.baeldung.boot.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "users") +public class User { + + @Id + @GeneratedValue + private Integer id; + private String name; + private Integer status; + + 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; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } +} diff --git a/spring-boot/src/main/java/org/baeldung/boot/repository/UserRepository.java b/spring-boot/src/main/java/org/baeldung/boot/repository/UserRepository.java new file mode 100644 index 0000000000..3a419a65bd --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/boot/repository/UserRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.boot.repository; + +import org.baeldung.boot.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository("userRepository") +public interface UserRepository extends JpaRepository { + public int countByStatus(int status); +} diff --git a/spring-boot/src/main/java/org/baeldung/endpoints/info/TotalUsersInfoContributor.java b/spring-boot/src/main/java/org/baeldung/endpoints/info/TotalUsersInfoContributor.java new file mode 100644 index 0000000000..790584644f --- /dev/null +++ b/spring-boot/src/main/java/org/baeldung/endpoints/info/TotalUsersInfoContributor.java @@ -0,0 +1,26 @@ +package org.baeldung.endpoints.info; + +import java.util.HashMap; +import java.util.Map; + +import org.baeldung.boot.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class TotalUsersInfoContributor implements InfoContributor { + + @Autowired + UserRepository userRepository; + + @Override + public void contribute(Info.Builder builder) { + Map userDetails = new HashMap<>(); + userDetails.put("active", userRepository.countByStatus(1)); + userDetails.put("inactive", userRepository.countByStatus(0)); + + builder.withDetail("users", userDetails); + } +} diff --git a/spring-boot/src/main/resources/application.properties b/spring-boot/src/main/resources/application.properties index 1ffc95849d..84315a2477 100644 --- a/spring-boot/src/main/resources/application.properties +++ b/spring-boot/src/main/resources/application.properties @@ -2,7 +2,9 @@ server.port=8080 server.contextPath=/springbootapp management.port=8081 management.address=127.0.0.1 - +#debug=true +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto = update endpoints.shutdown.enabled=true endpoints.jmx.domain=Spring Sample Application @@ -22,6 +24,7 @@ http.mappers.jsonPrettyPrint=true info.app.name=Spring Sample Application info.app.description=This is my first spring boot application G1 info.app.version=1.0.0 +info.java-vendor = ${java.specification.vendor} ## Spring Security Configurations security.user.name=admin1 diff --git a/spring-boot/src/main/resources/data.sql b/spring-boot/src/main/resources/data.sql new file mode 100644 index 0000000000..c44034c739 --- /dev/null +++ b/spring-boot/src/main/resources/data.sql @@ -0,0 +1,5 @@ +insert into users values (1, 'Alex', 1); +insert into users values (2, 'Bob', 1); +insert into users values (3, 'John', 0); +insert into users values (4, 'Harry', 0); +insert into users values (5, 'Smith', 1); \ No newline at end of file diff --git a/spring-boot/src/main/resources/schema.sql b/spring-boot/src/main/resources/schema.sql new file mode 100644 index 0000000000..27859c1652 --- /dev/null +++ b/spring-boot/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +create table USERS( + ID int not null AUTO_INCREMENT, + NAME varchar(100) not null, + STATUS int, + PRIMARY KEY ( ID ) +); \ No newline at end of file From 50ff1d18c481668f4b1edd2d4af4369c9564e5ba Mon Sep 17 00:00:00 2001 From: Abhinab Kanrar Date: Tue, 4 Apr 2017 16:02:55 +0530 Subject: [PATCH 149/149] quick-guide-to-the-java-stringtokenizer (#1587) * rest with spark java * 4 * Update Application.java * indentation changes * spring @requestmapping shortcuts * removing spring requestmapping and pushing spring-mvc-java * Joining/Splitting Strings with Java and Stream API * adding more join/split functionality * changing package name * testcase change * adding webutils * adding testcase for WebUtils and ServletRequestUtils * adding testcase * spring-security-stormpath * adding ratpack module * adding pom.xml * adding following modules with updated testcase : DB, Filter, Json * adding spring-boot custom banner tutorial * changing banner format in plain text * Delete banner.txt~ * Delete b.txt~ * CORS in JAX-RS * ratpack with google guice * adding factory instance example * quick-guide-to-the-java-stringtokenizer * Update Application.java * Delete MovieCrudService.java~ --- .../baeldung/stringtokenizer/Application.java | 21 +++++++++++++ .../stringtokenizer/ApplicationTest.java | 30 +++++++++++++++++++ .../main/java/com/baeldung/Application.java | 1 + .../java/com/baeldung/filter/CORSFilter.java | 18 +++++++++++ 4 files changed, 70 insertions(+) create mode 100644 core-java/src/main/java/com/baeldung/stringtokenizer/Application.java create mode 100644 core-java/src/test/java/com/baeldung/stringtokenizer/ApplicationTest.java create mode 100644 resteasy/src/main/java/com/baeldung/filter/CORSFilter.java diff --git a/core-java/src/main/java/com/baeldung/stringtokenizer/Application.java b/core-java/src/main/java/com/baeldung/stringtokenizer/Application.java new file mode 100644 index 0000000000..caa7ef06da --- /dev/null +++ b/core-java/src/main/java/com/baeldung/stringtokenizer/Application.java @@ -0,0 +1,21 @@ +package com.baeldung.stringtokenizer; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +public class Application { + + public List getTokens(String str) { + List tokens = new ArrayList(); +// StringTokenizer tokenizer = new StringTokenizer( str ); + StringTokenizer tokenizer = new StringTokenizer( str , "," ); +// StringTokenizer tokenizer = new StringTokenizer( str , "," , true ); + while (tokenizer.hasMoreElements()) { + tokens.add( tokenizer.nextToken() ); +// tokens.add( tokenizer.nextToken( "," ) ); + } + return tokens; + } + +} diff --git a/core-java/src/test/java/com/baeldung/stringtokenizer/ApplicationTest.java b/core-java/src/test/java/com/baeldung/stringtokenizer/ApplicationTest.java new file mode 100644 index 0000000000..5e3df5b303 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/stringtokenizer/ApplicationTest.java @@ -0,0 +1,30 @@ +package com.baeldung.stringtokenizer; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ApplicationTest { + + Application application = new Application(); + List expectedTokens = new ArrayList(); + + @Before + public void init() { + expectedTokens.add( "Welcome" ); + expectedTokens.add( "to" ); + expectedTokens.add( "baeldung.com" ); + } + + @Test + public void givenString_thenGetListOfString() { + String str = "Welcome,to,baeldung.com"; + List actualTokens = application.getTokens(str); + assertEquals(expectedTokens, actualTokens); + } + +} diff --git a/ratpack/src/main/java/com/baeldung/Application.java b/ratpack/src/main/java/com/baeldung/Application.java index 94709e88e9..7f46b241ea 100644 --- a/ratpack/src/main/java/com/baeldung/Application.java +++ b/ratpack/src/main/java/com/baeldung/Application.java @@ -46,3 +46,4 @@ public class Application { } } + diff --git a/resteasy/src/main/java/com/baeldung/filter/CORSFilter.java b/resteasy/src/main/java/com/baeldung/filter/CORSFilter.java new file mode 100644 index 0000000000..16562d4359 --- /dev/null +++ b/resteasy/src/main/java/com/baeldung/filter/CORSFilter.java @@ -0,0 +1,18 @@ +package com.baeldung.filter; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +public class CORSFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + + } + +}