diff --git a/apache-poi/README.md b/apache-poi/README.md
index cef6810c97..10fe8ba2e7 100644
--- a/apache-poi/README.md
+++ b/apache-poi/README.md
@@ -1,2 +1,3 @@
### Relevant Articles:
- [Microsoft Word Processing in Java with Apache POI](http://www.baeldung.com/java-microsoft-word-with-apache-poi)
+- [Working with Microsoft Excel in Java](http://www.baeldung.com/java-microsoft-excel)
diff --git a/core-java/pom.xml b/core-java/pom.xml
index 5c91150941..2b6f065c85 100644
--- a/core-java/pom.xml
+++ b/core-java/pom.xml
@@ -1,5 +1,5 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.baeldung
core-java
@@ -10,6 +10,44 @@
+
+ org.neo4j
+ neo4j
+ 3.1.0
+
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+ 1.1.1
+
+
+
+ org.neo4j
+ neo4j-jdbc-driver
+ 3.0.1
+
+
+
+ org.neo4j
+ neo4j-ogm-core
+ 2.1.1
+
+
+
+ org.neo4j
+ neo4j-ogm-embedded-driver
+ 2.1.1
+
+
+
+ com.google.inject
+ guice
+ 4.1.0
+ no_aop
+ test
+
+
net.sourceforge.collections
@@ -63,7 +101,6 @@
grep4j
${grep4j.version}
-
@@ -269,7 +306,8 @@
true
-
+
org.baeldung.executable.ExecutableMavenJar
diff --git a/core-java/src/main/java/com/baeldung/graph/Car.java b/core-java/src/main/java/com/baeldung/graph/Car.java
new file mode 100644
index 0000000000..1dc65a0d4b
--- /dev/null
+++ b/core-java/src/main/java/com/baeldung/graph/Car.java
@@ -0,0 +1,50 @@
+package com.baeldung.graph;
+
+import org.neo4j.ogm.annotation.GraphId;
+import org.neo4j.ogm.annotation.NodeEntity;
+import org.neo4j.ogm.annotation.Relationship;
+
+/**
+ * @author Danil Kornishev (danil.kornishev@mastercard.com)
+ */
+@NodeEntity
+public class Car {
+ @GraphId
+ private Long id;
+
+ private String make;
+
+ @Relationship(direction = "INCOMING")
+ private Company company;
+
+ public Car(String make, String model) {
+ this.make = make;
+ this.model = model;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getMake() {
+ return make;
+ }
+
+ public void setMake(String make) {
+ this.make = make;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ private String model;
+}
diff --git a/core-java/src/main/java/com/baeldung/graph/Company.java b/core-java/src/main/java/com/baeldung/graph/Company.java
new file mode 100644
index 0000000000..1fe892b331
--- /dev/null
+++ b/core-java/src/main/java/com/baeldung/graph/Company.java
@@ -0,0 +1,45 @@
+package com.baeldung.graph;
+
+import org.neo4j.ogm.annotation.NodeEntity;
+import org.neo4j.ogm.annotation.Relationship;
+
+/**
+ * @author Danil Kornishev (danil.kornishev@mastercard.com)
+ */
+@NodeEntity
+public class Company {
+ private Long id;
+
+ private String name;
+
+ @Relationship(type="owns")
+ private Car car;
+
+ public Company(String name) {
+ 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;
+ }
+
+ public Car getCar() {
+ return car;
+ }
+
+ public void setCar(Car car) {
+ this.car = car;
+ }
+}
diff --git a/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPost.java b/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPost.java
new file mode 100644
index 0000000000..afc05e356a
--- /dev/null
+++ b/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPost.java
@@ -0,0 +1,36 @@
+package com.baeldung.java_8_features.groupingby;
+
+public class BlogPost {
+ private String title;
+ private String author;
+ private BlogPostType type;
+ private int likes;
+
+ public BlogPost(String title, String author, BlogPostType type, int likes) {
+ this.title = title;
+ this.author = author;
+ this.type = type;
+ this.likes = likes;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public BlogPostType getType() {
+ return type;
+ }
+
+ public int getLikes() {
+ return likes;
+ }
+
+ @Override
+ public String toString() {
+ return "BlogPost{" + "title='" + title + '\'' + ", type=" + type + ", likes=" + likes + '}';
+ }
+}
diff --git a/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPostType.java b/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPostType.java
new file mode 100644
index 0000000000..2029784e91
--- /dev/null
+++ b/core-java/src/main/java/com/baeldung/java_8_features/groupingby/BlogPostType.java
@@ -0,0 +1,5 @@
+package com.baeldung.java_8_features.groupingby;
+
+public enum BlogPostType {
+ NEWS, REVIEW, GUIDE
+}
diff --git a/core-java/src/test/java/com/baeldung/graph/Neo4JServerTest.java b/core-java/src/test/java/com/baeldung/graph/Neo4JServerTest.java
new file mode 100644
index 0000000000..b41588b71e
--- /dev/null
+++ b/core-java/src/test/java/com/baeldung/graph/Neo4JServerTest.java
@@ -0,0 +1,60 @@
+package com.baeldung.graph;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Driver;
+import org.neo4j.driver.v1.GraphDatabase;
+import org.neo4j.driver.v1.Session;
+import org.neo4j.driver.v1.StatementResult;
+import org.testng.Assert;
+
+@Ignore
+public class Neo4JServerTest {
+
+ @Test
+ public void standAloneDriver() {
+ Driver driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "12345"));
+ Session session = driver.session();
+
+ session.run("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ StatementResult result = session.run("MATCH (company:Company)-[:owns]-> (car:Car)" +
+ "WHERE car.make='tesla' and car.model='modelX'" +
+ "RETURN company.name");
+
+ Assert.assertTrue(result.hasNext());
+ Assert.assertEquals(result.next().get("company.name").asString(), "Baeldung");
+
+ session.close();
+ driver.close();
+ }
+
+ @Test
+ public void standAloneJdbc() throws Exception {
+ Connection con = DriverManager.getConnection("jdbc:neo4j:bolt://localhost/?user=neo4j,password=12345,scheme=basic");
+
+ // Querying
+ try (Statement stmt = con.createStatement()) {
+ stmt.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ ResultSet rs = stmt.executeQuery("MATCH (company:Company)-[:owns]-> (car:Car)" +
+ "WHERE car.make='tesla' and car.model='modelX'" +
+ "RETURN company.name");
+
+ while (rs.next()) {
+ Assert.assertEquals(rs.getString("company.name"), "Baeldung");
+ }
+ }
+ con.close();
+ }
+}
diff --git a/core-java/src/test/java/com/baeldung/graph/Neo4jOgmTest.java b/core-java/src/test/java/com/baeldung/graph/Neo4jOgmTest.java
new file mode 100644
index 0000000000..00bd47d029
--- /dev/null
+++ b/core-java/src/test/java/com/baeldung/graph/Neo4jOgmTest.java
@@ -0,0 +1,46 @@
+package com.baeldung.graph;
+
+import org.junit.Test;
+import org.neo4j.ogm.config.Configuration;
+import org.neo4j.ogm.model.Result;
+import org.neo4j.ogm.session.Session;
+import org.neo4j.ogm.session.SessionFactory;
+import org.testng.Assert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Danil Kornishev (danil.kornishev@mastercard.com)
+ */
+public class Neo4jOgmTest {
+
+ @Test
+ public void testOgm() {
+ Configuration conf = new Configuration();
+ conf.driverConfiguration().setDriverClassName("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver");
+
+ SessionFactory factory = new SessionFactory(conf, "com.baeldung.graph");
+ Session session = factory.openSession();
+
+ Car tesla = new Car("tesla", "modelS");
+ Company baeldung = new Company("baeldung");
+
+ baeldung.setCar(tesla);
+
+ session.save(baeldung);
+
+ Map params = new HashMap<>();
+ params.put("make", "tesla");
+ Result result = session.query("MATCH (car:Car) <-[:owns]- (company:Company)" +
+ " WHERE car.make=$make" +
+ " RETURN company", params);
+
+ Map firstResult = result.iterator().next();
+
+ Assert.assertEquals(firstResult.size(), 1);
+
+ Company actual = (Company) firstResult.get("company");
+ Assert.assertEquals(actual.getName(), baeldung.getName());
+ }
+}
diff --git a/core-java/src/test/java/com/baeldung/graph/Neo4jTest.java b/core-java/src/test/java/com/baeldung/graph/Neo4jTest.java
new file mode 100644
index 0000000000..6956c2c39f
--- /dev/null
+++ b/core-java/src/test/java/com/baeldung/graph/Neo4jTest.java
@@ -0,0 +1,167 @@
+package com.baeldung.graph;
+
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Label;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.NotFoundException;
+import org.neo4j.graphdb.RelationshipType;
+import org.neo4j.graphdb.Result;
+import org.neo4j.graphdb.factory.GraphDatabaseFactory;
+import org.testng.Assert;
+
+public class Neo4jTest {
+
+ private static GraphDatabaseService graphDb;
+
+ @Before
+ public void setUp() {
+ GraphDatabaseFactory graphDbFactory = new GraphDatabaseFactory();
+ graphDb = graphDbFactory.newEmbeddedDatabase(new File("data/cars"));
+ }
+
+ @After
+ public void tearDown() {
+ graphDb.shutdown();
+ }
+
+ @Test
+ public void testPersonCar() {
+ graphDb.beginTx();
+ Node car = graphDb.createNode(Label.label("Car"));
+ car.setProperty("make", "tesla");
+ car.setProperty("model", "model3");
+
+ Node owner = graphDb.createNode(Label.label("Person"));
+ owner.setProperty("firstName", "baeldung");
+ owner.setProperty("lastName", "baeldung");
+
+ owner.createRelationshipTo(car, RelationshipType.withName("owner"));
+
+ Result result = graphDb.execute("MATCH (c:Car) <-[owner]- (p:Person) " +
+ "WHERE c.make = 'tesla'" +
+ "RETURN p.firstName, p.lastName");
+
+ Map firstResult = result.next();
+ Assert.assertEquals("baeldung", firstResult.get("p.firstName"));
+ }
+
+ @Test
+ public void testCreateNode() {
+
+ graphDb.beginTx();
+
+ Result result = graphDb.execute("CREATE (baeldung:Company {name:\"Baeldung\"})" +
+ "RETURN baeldung");
+
+ Map firstResult = result.next();
+ Node firstNode = (Node) firstResult.get("baeldung");
+ Assert.assertEquals(firstNode.getProperty("name"), "Baeldung");
+ }
+
+ @Test
+ public void testCreateNodeAndLink() {
+ graphDb.beginTx();
+
+ Result result = graphDb.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ Map firstResult = result.next();
+ Assert.assertTrue(firstResult.containsKey("baeldung"));
+ Assert.assertTrue(firstResult.containsKey("tesla"));
+ }
+
+ @Test
+ public void testFindAndReturn() {
+ graphDb.beginTx();
+
+ graphDb.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ Result result = graphDb.execute("MATCH (company:Company)-[:owns]-> (car:Car)" +
+ "WHERE car.make='tesla' and car.model='modelX'" +
+ "RETURN company.name");
+
+ Map firstResult = result.next();
+ Assert.assertEquals(firstResult.get("company.name"), "Baeldung");
+ }
+
+ @Test
+ public void testUpdate() {
+ graphDb.beginTx();
+
+ graphDb.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ Result result = graphDb.execute("MATCH (car:Car)" +
+ "WHERE car.make='tesla'" +
+ " SET car.milage=120" +
+ " SET car :Car:Electro" +
+ " SET car.model=NULL" +
+ " RETURN car");
+
+ Map firstResult = result.next();
+ Node car = (Node) firstResult.get("car");
+
+ Assert.assertEquals(car.getProperty("milage"), 120L);
+ Assert.assertEquals(car.getLabels(), Arrays.asList(Label.label("Car"), Label.label("Electro")));
+
+ try {
+ car.getProperty("model");
+ Assert.fail();
+ } catch (NotFoundException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDelete() {
+ graphDb.beginTx();
+
+ graphDb.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " +
+ "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" +
+ "RETURN baeldung, tesla");
+
+ graphDb.execute("MATCH (company:Company)" +
+ " WHERE company.name='Baeldung'" +
+ " DELETE company");
+
+ Result result = graphDb.execute("MATCH (company:Company)" +
+ " WHERE company.name='Baeldung'" +
+ " RETURN company");
+
+ Assert.assertFalse(result.hasNext());
+ }
+
+ @Test
+ public void testBindings() {
+ graphDb.beginTx();
+
+ Map params = new HashMap<>();
+ params.put("name", "baeldung");
+ params.put("make", "tesla");
+ params.put("model", "modelS");
+
+ Result result = graphDb.execute("CREATE (baeldung:Company {name:$name}) " +
+ "-[:owns]-> (tesla:Car {make: $make, model: $model})" +
+ "RETURN baeldung, tesla", params);
+
+ Map firstResult = result.next();
+ Assert.assertTrue(firstResult.containsKey("baeldung"));
+ Assert.assertTrue(firstResult.containsKey("tesla"));
+
+ Node car = (Node) firstResult.get("tesla");
+ Assert.assertEquals(car.getProperty("model"), "modelS");
+ }
+}
diff --git a/core-java/src/test/java/com/baeldung/java/concurrentmodification/ConcurrentModificationUnitTest.java b/core-java/src/test/java/com/baeldung/java/concurrentmodification/ConcurrentModificationUnitTest.java
new file mode 100644
index 0000000000..f7a7bd5fe0
--- /dev/null
+++ b/core-java/src/test/java/com/baeldung/java/concurrentmodification/ConcurrentModificationUnitTest.java
@@ -0,0 +1,80 @@
+package com.baeldung.java.concurrentmodification;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.util.Lists.newArrayList;
+
+public class ConcurrentModificationUnitTest {
+ @Test(expected = ConcurrentModificationException.class)
+ public void givenIterating_whenRemoving_thenThrowException() throws InterruptedException {
+
+ List integers = newArrayList(1, 2, 3);
+
+ for (Integer integer : integers) {
+ integers.remove(1);
+ }
+ }
+
+ @Test
+ public void givenIterating_whenUsingIteratorRemove_thenNoError() throws InterruptedException {
+
+ List integers = newArrayList(1, 2, 3);
+
+ for (Iterator iterator = integers.iterator(); iterator.hasNext();) {
+ Integer integer = iterator.next();
+ if(integer == 2) {
+ iterator.remove();
+ }
+ }
+
+ assertThat(integers).containsExactly(1, 3);
+ }
+
+ @Test
+ public void givenIterating_whenUsingRemovalList_thenNoError() throws InterruptedException {
+
+ List integers = newArrayList(1, 2, 3);
+ List toRemove = newArrayList();
+
+ for (Integer integer : integers) {
+ if(integer == 2) {
+ toRemove.add(integer);
+ }
+ }
+ integers.removeAll(toRemove);
+
+ assertThat(integers).containsExactly(1, 3);
+ }
+
+ @Test
+ public void whenUsingRemoveIf_thenRemoveElements() throws InterruptedException {
+
+ Collection integers = newArrayList(1, 2, 3);
+
+ integers.removeIf(i -> i == 2);
+
+ assertThat(integers).containsExactly(1, 3);
+ }
+
+ @Test
+ public void whenUsingStream_thenRemoveElements() {
+ Collection integers = newArrayList(1, 2, 3);
+
+ List collected = integers
+ .stream()
+ .filter(i -> i != 2)
+ .map(Object::toString)
+ .collect(toList());
+
+ assertThat(collected).containsExactly("1", "3");
+ }
+
+}
diff --git a/core-java/src/test/java/com/baeldung/java8/Java8GroupingByCollectorUnitTest.java b/core-java/src/test/java/com/baeldung/java8/Java8GroupingByCollectorUnitTest.java
new file mode 100644
index 0000000000..4452b4db9a
--- /dev/null
+++ b/core-java/src/test/java/com/baeldung/java8/Java8GroupingByCollectorUnitTest.java
@@ -0,0 +1,231 @@
+package com.baeldung.java8;
+
+import com.baeldung.java_8_features.groupingby.BlogPost;
+import com.baeldung.java_8_features.groupingby.BlogPostType;
+import org.junit.Test;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentMap;
+
+import static java.util.Comparator.comparingInt;
+import static java.util.stream.Collectors.*;
+import static org.junit.Assert.*;
+
+public class Java8GroupingByCollectorUnitTest {
+
+ private static final List posts = Arrays.asList(
+ new BlogPost("News item 1", "Author 1", BlogPostType.NEWS, 15),
+ new BlogPost("Tech review 1", "Author 2", BlogPostType.REVIEW, 5),
+ new BlogPost("Programming guide", "Author 1", BlogPostType.GUIDE, 20),
+ new BlogPost("News item 2", "Author 2", BlogPostType.NEWS, 35),
+ new BlogPost("Tech review 2", "Author 1", BlogPostType.REVIEW, 15));
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByType_thenGetAMapBetweenTypeAndPosts() {
+ Map> postsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType));
+
+ assertEquals(2, postsPerType
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, postsPerType
+ .get(BlogPostType.GUIDE)
+ .size());
+ assertEquals(2, postsPerType
+ .get(BlogPostType.REVIEW)
+ .size());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndTheirTitlesAreJoinedInAString_thenGetAMapBetweenTypeAndCsvTitles() {
+ Map postsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
+
+ assertEquals("Post titles: [News item 1, News item 2]", postsPerType.get(BlogPostType.NEWS));
+ assertEquals("Post titles: [Programming guide]", postsPerType.get(BlogPostType.GUIDE));
+ assertEquals("Post titles: [Tech review 1, Tech review 2]", postsPerType.get(BlogPostType.REVIEW));
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndSumTheLikes_thenGetAMapBetweenTypeAndPostLikes() {
+ Map likesPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
+
+ assertEquals(50, likesPerType
+ .get(BlogPostType.NEWS)
+ .intValue());
+ assertEquals(20, likesPerType
+ .get(BlogPostType.REVIEW)
+ .intValue());
+ assertEquals(20, likesPerType
+ .get(BlogPostType.GUIDE)
+ .intValue());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeInAnEnumMap_thenGetAnEnumMapBetweenTypeAndPosts() {
+ EnumMap> postsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, () -> new EnumMap<>(BlogPostType.class), toList()));
+
+ assertEquals(2, postsPerType
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, postsPerType
+ .get(BlogPostType.GUIDE)
+ .size());
+ assertEquals(2, postsPerType
+ .get(BlogPostType.REVIEW)
+ .size());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeInSets_thenGetAMapBetweenTypesAndSetsOfPosts() {
+ Map> postsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, toSet()));
+
+ assertEquals(2, postsPerType
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, postsPerType
+ .get(BlogPostType.GUIDE)
+ .size());
+ assertEquals(2, postsPerType
+ .get(BlogPostType.REVIEW)
+ .size());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeConcurrently_thenGetAMapBetweenTypeAndPosts() {
+ ConcurrentMap> postsPerType = posts
+ .parallelStream()
+ .collect(groupingByConcurrent(BlogPost::getType));
+
+ assertEquals(2, postsPerType
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, postsPerType
+ .get(BlogPostType.GUIDE)
+ .size());
+ assertEquals(2, postsPerType
+ .get(BlogPostType.REVIEW)
+ .size());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndAveragingLikes_thenGetAMapBetweenTypeAndAverageNumberOfLikes() {
+ Map averageLikesPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
+
+ assertEquals(25, averageLikesPerType
+ .get(BlogPostType.NEWS)
+ .intValue());
+ assertEquals(20, averageLikesPerType
+ .get(BlogPostType.GUIDE)
+ .intValue());
+ assertEquals(10, averageLikesPerType
+ .get(BlogPostType.REVIEW)
+ .intValue());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndCounted_thenGetAMapBetweenTypeAndNumberOfPosts() {
+ Map numberOfPostsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, counting()));
+
+ assertEquals(2, numberOfPostsPerType
+ .get(BlogPostType.NEWS)
+ .intValue());
+ assertEquals(1, numberOfPostsPerType
+ .get(BlogPostType.GUIDE)
+ .intValue());
+ assertEquals(2, numberOfPostsPerType
+ .get(BlogPostType.REVIEW)
+ .intValue());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndMaxingLikes_thenGetAMapBetweenTypeAndMaximumNumberOfLikes() {
+ Map> maxLikesPerPostType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, maxBy(comparingInt(BlogPost::getLikes))));
+
+ assertTrue(maxLikesPerPostType
+ .get(BlogPostType.NEWS)
+ .isPresent());
+ assertEquals(35, maxLikesPerPostType
+ .get(BlogPostType.NEWS)
+ .get()
+ .getLikes());
+
+ assertTrue(maxLikesPerPostType
+ .get(BlogPostType.GUIDE)
+ .isPresent());
+ assertEquals(20, maxLikesPerPostType
+ .get(BlogPostType.GUIDE)
+ .get()
+ .getLikes());
+
+ assertTrue(maxLikesPerPostType
+ .get(BlogPostType.REVIEW)
+ .isPresent());
+ assertEquals(15, maxLikesPerPostType
+ .get(BlogPostType.REVIEW)
+ .get()
+ .getLikes());
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByAuthorAndThenByType_thenGetAMapBetweenAuthorAndMapsBetweenTypeAndBlogPosts() {
+ Map>> map = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
+
+ assertEquals(1, map
+ .get("Author 1")
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, map
+ .get("Author 1")
+ .get(BlogPostType.GUIDE)
+ .size());
+ assertEquals(1, map
+ .get("Author 1")
+ .get(BlogPostType.REVIEW)
+ .size());
+
+ assertEquals(1, map
+ .get("Author 2")
+ .get(BlogPostType.NEWS)
+ .size());
+ assertEquals(1, map
+ .get("Author 2")
+ .get(BlogPostType.REVIEW)
+ .size());
+ assertNull(map
+ .get("Author 2")
+ .get(BlogPostType.GUIDE));
+ }
+
+ @Test
+ public void givenAListOfPosts_whenGroupedByTypeAndSummarizingLikes_thenGetAMapBetweenTypeAndSummary() {
+ Map likeStatisticsPerType = posts
+ .stream()
+ .collect(groupingBy(BlogPost::getType, summarizingInt(BlogPost::getLikes)));
+
+ IntSummaryStatistics newsLikeStatistics = likeStatisticsPerType.get(BlogPostType.NEWS);
+
+ assertEquals(2, newsLikeStatistics.getCount());
+ assertEquals(50, newsLikeStatistics.getSum());
+ assertEquals(25.0, newsLikeStatistics.getAverage(), 0.001);
+ assertEquals(35, newsLikeStatistics.getMax());
+ assertEquals(15, newsLikeStatistics.getMin());
+ }
+
+}
diff --git a/guava/src/test/java/org/baeldung/guava/CustomEvent.java b/guava/src/main/java/org/baeldung/guava/CustomEvent.java
similarity index 100%
rename from guava/src/test/java/org/baeldung/guava/CustomEvent.java
rename to guava/src/main/java/org/baeldung/guava/CustomEvent.java
diff --git a/guava/src/test/java/org/baeldung/guava/EventBusWrapper.java b/guava/src/main/java/org/baeldung/guava/EventBusWrapper.java
similarity index 100%
rename from guava/src/test/java/org/baeldung/guava/EventBusWrapper.java
rename to guava/src/main/java/org/baeldung/guava/EventBusWrapper.java
diff --git a/guava/src/test/java/org/baeldung/guava/EventListener.java b/guava/src/main/java/org/baeldung/guava/EventListener.java
similarity index 100%
rename from guava/src/test/java/org/baeldung/guava/EventListener.java
rename to guava/src/main/java/org/baeldung/guava/EventListener.java
diff --git a/spring-jersey/pom.xml b/spring-jersey/pom.xml
index 293850d41e..41ebb9a6b5 100644
--- a/spring-jersey/pom.xml
+++ b/spring-jersey/pom.xml
@@ -9,7 +9,7 @@
war
- 2.25
+ 2.25.1
1.7.22
1.1.8
4.12
diff --git a/spring-jersey/src/main/java/com/baeldung/client/rest/RestClient.java b/spring-jersey/src/main/java/com/baeldung/client/rest/RestClient.java
index 0e45b68b14..34f7d45601 100644
--- a/spring-jersey/src/main/java/com/baeldung/client/rest/RestClient.java
+++ b/spring-jersey/src/main/java/com/baeldung/client/rest/RestClient.java
@@ -18,7 +18,7 @@ public class RestClient {
}
public Employee getJsonEmployee(int id) {
- return client.target(REST_URI).path(new Integer(id).toString()).request(MediaType.APPLICATION_JSON).get(Employee.class);
+ return client.target(REST_URI).path(String.valueOf(id)).request(MediaType.APPLICATION_JSON).get(Employee.class);
}
public Response createXmlEmployee(Employee emp) {
@@ -26,6 +26,6 @@ public class RestClient {
}
public Employee getXmlEmployee(int id) {
- return client.target(REST_URI).path(new Integer(id).toString()).request(MediaType.APPLICATION_XML).get(Employee.class);
+ return client.target(REST_URI).path(String.valueOf(id)).request(MediaType.APPLICATION_XML).get(Employee.class);
}
}
diff --git a/spring-mvc-forms/pom.xml b/spring-mvc-forms/pom.xml
index 31a0c38791..f17d695c35 100644
--- a/spring-mvc-forms/pom.xml
+++ b/spring-mvc-forms/pom.xml
@@ -46,6 +46,23 @@
commons-fileupload
${fileupload.version}
+
+
+ org.springframework.security
+ spring-security-web
+ ${org.springframework.security.version}
+
+
+ org.springframework.security
+ spring-security-config
+ ${org.springframework.security.version}
+
+
+ org.springframework.security
+ spring-security-taglibs
+ ${org.springframework.security.version}
+
+
@@ -98,6 +115,7 @@
5.3.3.Final
enter-location-of-server
1.3.2
+ 4.2.1.RELEASE
diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/SecurityConfig.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/SecurityConfig.java
new file mode 100644
index 0000000000..e35844138d
--- /dev/null
+++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/SecurityConfig.java
@@ -0,0 +1,122 @@
+package com.baeldung.springmvcforms.configuration;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.TestingAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Bean
+ public UserDetailsService userDetailsService() throws Exception {
+ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
+ manager.createUser(User.withUsername("user")
+ .password("userPass")
+ .roles("USER")
+ .build());
+ manager.createUser(User.withUsername("admin")
+ .password("adminPass")
+ .roles("ADMIN")
+ .build());
+ return manager;
+ }
+
+ @Configuration
+ @Order(1)
+ public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter {
+
+ public App1ConfigurationAdapter() {
+ super();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("admin")
+ .password("admin")
+ .roles("ADMIN");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/admin*")
+ .authorizeRequests()
+ .anyRequest()
+ .hasRole("ADMIN")
+ // log in
+ .and()
+ .formLogin()
+ .loginPage("/loginAdmin")
+ .loginProcessingUrl("/admin_login")
+ .failureUrl("/loginAdmin?error=loginError")
+ .defaultSuccessUrl("/adminPage")
+ // logout
+ .and()
+ .logout()
+ .logoutUrl("/admin_logout")
+ .logoutSuccessUrl("/protectedLinks")
+ .deleteCookies("JSESSIONID")
+ .and()
+ .exceptionHandling()
+ .accessDeniedPage("/403")
+ .and()
+ .csrf()
+ .disable();
+ }
+ }
+
+ @Configuration
+ @Order(2)
+ public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter {
+
+ public App2ConfigurationAdapter() {
+ super();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("user")
+ .password("user")
+ .roles("USER");
+ }
+
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/user*")
+ .authorizeRequests()
+ .anyRequest()
+ .hasRole("USER")
+ // log in
+ .and()
+ .formLogin()
+ .loginPage("/loginUser")
+ .loginProcessingUrl("/user_login")
+ .failureUrl("/loginUser?error=loginError")
+ .defaultSuccessUrl("/userPage")
+ // logout
+ .and()
+ .logout()
+ .logoutUrl("/user_logout")
+ .logoutSuccessUrl("/protectedLinks")
+ .deleteCookies("JSESSIONID")
+ .and()
+ .exceptionHandling()
+ .accessDeniedPage("/403")
+ .and()
+ .csrf()
+ .disable();
+ }
+ }
+
+}
diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/WebInitializer.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/WebInitializer.java
index c602ea6454..fdc155e101 100644
--- a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/WebInitializer.java
+++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/configuration/WebInitializer.java
@@ -3,6 +3,7 @@ package com.baeldung.springmvcforms.configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
@@ -24,6 +25,9 @@ public class WebInitializer implements WebApplicationInitializer {
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
+
+ container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
+ .addMappingForUrlPatterns(null, false, "/*");
}
// @Override
// public void onStartup(ServletContext container) {
diff --git a/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UsersController.java b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UsersController.java
new file mode 100644
index 0000000000..c0858d427f
--- /dev/null
+++ b/spring-mvc-forms/src/main/java/com/baeldung/springmvcforms/controller/UsersController.java
@@ -0,0 +1,38 @@
+package com.baeldung.springmvcforms.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class UsersController {
+
+ @RequestMapping("/protectedLinks")
+ public String getAnonymousPage() {
+ return "protectedLinks";
+ }
+
+ @RequestMapping("/userPage")
+ public String getUserPage() {
+ return "userPage";
+ }
+
+ @RequestMapping("/adminPage")
+ public String getAdminPage() {
+ return "adminPage";
+ }
+
+ @RequestMapping("/loginAdmin")
+ public String getAdminLoginPage() {
+ return "loginAdmin";
+ }
+
+ @RequestMapping("/loginUser")
+ public String getUserLoginPage() {
+ return "loginUser";
+ }
+
+ @RequestMapping("/403")
+ public String getAccessDeniedPage() {
+ return "403";
+ }
+}
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/403.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/403.jsp
new file mode 100644
index 0000000000..e665793e10
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/403.jsp
@@ -0,0 +1,12 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+
+
+
+
+
+
+
+Your do not have permission to view this page.
+
+
\ No newline at end of file
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/adminPage.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/adminPage.jsp
new file mode 100644
index 0000000000..a210b690b0
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/adminPage.jsp
@@ -0,0 +1,16 @@
+<%@ 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" %>
+
+
+
+
+Insert title here
+
+
+Welcome admin! Logout
+
+
+Back to links
+
+
\ No newline at end of file
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginAdmin.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginAdmin.jsp
new file mode 100644
index 0000000000..a6b2ee7914
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginAdmin.jsp
@@ -0,0 +1,38 @@
+<%@ 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"%>
+
+
+
+
+Insert title here
+
+
+
+ Admin login page
+
+
+ <%
+ if (request.getParameter("error") != null) {
+ out.println("Login failed!");
+ }
+ %>
+
+
+
\ No newline at end of file
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginUser.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginUser.jsp
new file mode 100644
index 0000000000..e65c11edaf
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/loginUser.jsp
@@ -0,0 +1,37 @@
+<%@ 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"%>
+
+
+
+
+Login
+
+
+
+ User login page
+
+
+ <%
+ if (request.getParameter("error") != null) {
+ out.println("Login failed!");
+ }
+ %>
+
+
+
\ No newline at end of file
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/protectedLinks.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/protectedLinks.jsp
new file mode 100644
index 0000000000..b8453903ba
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/protectedLinks.jsp
@@ -0,0 +1,16 @@
+<%@ 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" %>
+
+
+
+
+Insert title here
+
+
+
+">User page
+
+">Admin page
+
+
\ No newline at end of file
diff --git a/spring-mvc-forms/src/main/webapp/WEB-INF/views/userPage.jsp b/spring-mvc-forms/src/main/webapp/WEB-INF/views/userPage.jsp
new file mode 100644
index 0000000000..4c1bd47502
--- /dev/null
+++ b/spring-mvc-forms/src/main/webapp/WEB-INF/views/userPage.jsp
@@ -0,0 +1,15 @@
+<%@ 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" %>
+
+
+
+
+Insert title here
+
+
+Welcome user! Logout
+
+Back to links
+
+
\ No newline at end of file