From 73026b65653fe38fded09d85212d108f5b43ab27 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 22 Feb 2017 20:32:38 +0000 Subject: [PATCH 1/9] Added reactor core --- pom.xml | 1 + reactor-core/pom.xml | 52 +++++++++ .../com/baeldung/reactor/ReactorTest.java | 107 ++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 reactor-core/pom.xml create mode 100644 reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java diff --git a/pom.xml b/pom.xml index 9d6c5931e3..014e4016c5 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,7 @@ querydsl + reactor-core redis rest-assured rest-testing diff --git a/reactor-core/pom.xml b/reactor-core/pom.xml new file mode 100644 index 0000000000..017b59f42e --- /dev/null +++ b/reactor-core/pom.xml @@ -0,0 +1,52 @@ + + 4.0.0 + + org.baeldung + reactor-core + 0.0.1-SNAPSHOT + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + + io.projectreactor + reactor-core + 3.0.4.RELEASE + + + + junit + junit + 4.12 + test + + + + org.assertj + assertj-core + 3.6.1 + test + + + + ch.qos.logback + logback-classic + 1.1.3 + + + + + diff --git a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java new file mode 100644 index 0000000000..a90346803e --- /dev/null +++ b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java @@ -0,0 +1,107 @@ +package com.baeldung.reactor; + +import org.junit.Test; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.publisher.ConnectableFlux; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.scheduler.Schedulers; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static java.time.Duration.ofSeconds; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +public class ReactorTest { + + @Test + public void givenFlux_whenSubscribing_shouldStream() throws InterruptedException { + + List elements = new ArrayList<>(); + + Flux.just(1, 2, 3, 4) + .log() + .map(i -> i * 2) + .subscribe(elements::add); + + assertThat(elements).containsExactly(2, 4, 6, 8); + } + + @Test + public void givenFlux_whenZipping_shouldCombine() { + List elements = new ArrayList<>(); + + Flux.just(1, 2, 3, 4) + .log() + .map(i -> i * 2) + .zipWith(Flux.range(0, Integer.MAX_VALUE).log(), (two, one) -> String.format("First Flux: %d, Second Flux: %d", one, two)) + .subscribe(elements::add); + + assertThat(elements).containsExactly( + "First Flux: 0, Second Flux: 2", + "First Flux: 1, Second Flux: 4", + "First Flux: 2, Second Flux: 6", + "First Flux: 3, Second Flux: 8"); + } + + @Test + public void givenFlux_whenApplyingBackPressure_shouldPushLessElements() throws InterruptedException { + + List elements = new ArrayList<>(); + + Flux.just(1, 2, 3, 4) + .log() + .map(i -> i * 2) + .onBackpressureBuffer() + .subscribe(new Subscriber() { + private Subscription s; + int onNextAmount; + + @Override + public void onSubscribe(final Subscription s) { + this.s = s; + s.request(2); + } + + @Override + public void onNext(final Integer integer) { + elements.add(integer); + onNextAmount++; + if (onNextAmount % 2 == 0) { + s.request(2); + } + } + + @Override + public void onError(final Throwable t) { + } + + @Override + public void onComplete() { + int ham = 2; + } + }); + + assertThat(elements).containsExactly(2, 4, 6, 8); + } + + @Test + public void givenFlux_whenInParalle_shouldSubscribeInDifferentThreads() { + List elements = new ArrayList<>(); + + Flux.just(1, 2, 3, 4) + .log() + .map(i -> i * 2) + .subscribeOn(Schedulers.parallel()) + .subscribe(elements::add); + + assertThat(elements).containsExactly(2, 4, 6, 8); + } + +} From 6bbb4cfef447868e6378f6995a9ac2d9c2e73cfe Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 22 Feb 2017 20:36:42 +0000 Subject: [PATCH 2/9] Fixed failing reactive test --- .../src/test/java/com/baeldung/reactor/ReactorTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java index a90346803e..6c30691f26 100644 --- a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java +++ b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java @@ -92,7 +92,7 @@ public class ReactorTest { } @Test - public void givenFlux_whenInParalle_shouldSubscribeInDifferentThreads() { + public void givenFlux_whenInParalle_shouldSubscribeInDifferentThreads() throws InterruptedException { List elements = new ArrayList<>(); Flux.just(1, 2, 3, 4) @@ -101,6 +101,8 @@ public class ReactorTest { .subscribeOn(Schedulers.parallel()) .subscribe(elements::add); + Thread.sleep(1000); + assertThat(elements).containsExactly(2, 4, 6, 8); } From 13b639cdff56ee436917c6cb2160892109540eb3 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sun, 26 Feb 2017 12:32:23 +0000 Subject: [PATCH 3/9] Renamed tests and added connectableFlux test --- reactor-core/pom.xml | 15 +++++-- .../com/baeldung/reactor/ReactorTest.java | 43 ++++++++++++------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/reactor-core/pom.xml b/reactor-core/pom.xml index 017b59f42e..2be8892983 100644 --- a/reactor-core/pom.xml +++ b/reactor-core/pom.xml @@ -24,29 +24,36 @@ io.projectreactor reactor-core - 3.0.4.RELEASE + ${reactor-core.version} junit junit - 4.12 + ${junit.version} test org.assertj assertj-core - 3.6.1 + ${assertj.version} test ch.qos.logback logback-classic - 1.1.3 + ${logback.version} + + 3.0.4.RELEASE + 4.12 + 3.6.1 + 1.1.3 + + diff --git a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java index 6c30691f26..46b774c30e 100644 --- a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java +++ b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java @@ -5,36 +5,33 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.publisher.ConnectableFlux; import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxSink; import reactor.core.scheduler.Schedulers; -import java.time.Duration; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; -import static java.time.Duration.ofSeconds; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; public class ReactorTest { @Test - public void givenFlux_whenSubscribing_shouldStream() throws InterruptedException { + public void givenFlux_whenSubscribing_thenStream() throws InterruptedException { List elements = new ArrayList<>(); Flux.just(1, 2, 3, 4) .log() - .map(i -> i * 2) + .map(i -> { + System.out.println(i + ":" + Thread.currentThread()); + return i * 2; + }) .subscribe(elements::add); assertThat(elements).containsExactly(2, 4, 6, 8); } @Test - public void givenFlux_whenZipping_shouldCombine() { + public void givenFlux_whenZipping_thenCombine() { List elements = new ArrayList<>(); Flux.just(1, 2, 3, 4) @@ -51,7 +48,7 @@ public class ReactorTest { } @Test - public void givenFlux_whenApplyingBackPressure_shouldPushLessElements() throws InterruptedException { + public void givenFlux_whenApplyingBackPressure_thenPushElementsInBatches() throws InterruptedException { List elements = new ArrayList<>(); @@ -92,18 +89,34 @@ public class ReactorTest { } @Test - public void givenFlux_whenInParalle_shouldSubscribeInDifferentThreads() throws InterruptedException { - List elements = new ArrayList<>(); + public void givenFlux_whenInParallel_thenSubscribeInDifferentThreads() throws InterruptedException { + List threadNames = new ArrayList<>(); Flux.just(1, 2, 3, 4) .log() - .map(i -> i * 2) + .map(i -> Thread.currentThread().getName()) .subscribeOn(Schedulers.parallel()) - .subscribe(elements::add); + .subscribe(threadNames::add); Thread.sleep(1000); - assertThat(elements).containsExactly(2, 4, 6, 8); + assertThat(threadNames).containsExactly("parallel-1", "parallel-1", "parallel-1", "parallel-1"); + } + + @Test + public void givenConnectableFlux_thenShouldStream_onConnect() { + + List elements = new ArrayList<>(); + + final ConnectableFlux publish = Flux.just(1, 2, 3, 4).publish(); + + publish.subscribe(elements::add); + + assertThat(elements).isEmpty(); + + publish.connect(); + + assertThat(elements).containsExactly(1, 2, 3, 4); } } From ef610affc9c9d20ef3cb02f6415ebe02b8297ea2 Mon Sep 17 00:00:00 2001 From: pedja4 Date: Mon, 27 Feb 2017 10:18:31 +0100 Subject: [PATCH 4/9] BAEL-343 Renamed one test --- .../src/test/java/com/baeldung/reactor/ReactorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java index 46b774c30e..0e534e7d61 100644 --- a/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java +++ b/reactor-core/src/test/java/com/baeldung/reactor/ReactorTest.java @@ -104,7 +104,7 @@ public class ReactorTest { } @Test - public void givenConnectableFlux_thenShouldStream_onConnect() { + public void givenConnectableFlux_whenConnected_thenShouldStream() { List elements = new ArrayList<>(); From 8617faf9cf7005eb4014e07fc41364547e3716b0 Mon Sep 17 00:00:00 2001 From: lor6 Date: Mon, 27 Feb 2017 17:04:00 +0200 Subject: [PATCH 5/9] data rest crud, angular fe (#1199) * data rest crud, angular fe * fix formatting, remove extra imports * fix config * change student entity to employee * expose ids * update boot version, removes ids, check null id --- spring-rest-angular/pom.xml | 11 +- .../web/controller/EmployeeController.java | 14 ++ .../web/dao/EmployeeCRUDRepository.java | 13 ++ .../baeldung/web/dao/StudentRepository.java | 3 +- .../org/baeldung/web/entity/Employee.java | 57 +++++++ .../java/org/baeldung/web/main/MvcConfig.java | 36 +++++ .../baeldung/web/main/PersistenceConfig.java | 4 +- .../src/main/resources/db/sql/employees.sql | 16 ++ .../main/webapp/WEB-INF/pages/employee.html | 55 +++++++ .../src/main/webapp/view/app.js | 144 +++++++++++++++++- ...EmployeeCRUDRepositoryIntegrationTest.java | 47 ++++++ 11 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 spring-rest-angular/src/main/java/org/baeldung/web/controller/EmployeeController.java create mode 100644 spring-rest-angular/src/main/java/org/baeldung/web/dao/EmployeeCRUDRepository.java create mode 100644 spring-rest-angular/src/main/java/org/baeldung/web/entity/Employee.java create mode 100644 spring-rest-angular/src/main/java/org/baeldung/web/main/MvcConfig.java create mode 100644 spring-rest-angular/src/main/resources/db/sql/employees.sql create mode 100644 spring-rest-angular/src/main/webapp/WEB-INF/pages/employee.html create mode 100644 spring-rest-angular/src/test/java/org/baeldung/web/service/EmployeeCRUDRepositoryIntegrationTest.java diff --git a/spring-rest-angular/pom.xml b/spring-rest-angular/pom.xml index 099867d19b..62ab03522d 100644 --- a/spring-rest-angular/pom.xml +++ b/spring-rest-angular/pom.xml @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 1.4.4.RELEASE + 1.5.1.RELEASE @@ -74,6 +74,15 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-data-rest + + + javax.servlet + jstl + 1.2 + diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/controller/EmployeeController.java b/spring-rest-angular/src/main/java/org/baeldung/web/controller/EmployeeController.java new file mode 100644 index 0000000000..a8bfc254c3 --- /dev/null +++ b/spring-rest-angular/src/main/java/org/baeldung/web/controller/EmployeeController.java @@ -0,0 +1,14 @@ +package org.baeldung.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class EmployeeController { + + @RequestMapping(value = "/employeePage") + public String getEmployeePage() { + return "employee"; + } + +} diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/dao/EmployeeCRUDRepository.java b/spring-rest-angular/src/main/java/org/baeldung/web/dao/EmployeeCRUDRepository.java new file mode 100644 index 0000000000..1e5f81ed45 --- /dev/null +++ b/spring-rest-angular/src/main/java/org/baeldung/web/dao/EmployeeCRUDRepository.java @@ -0,0 +1,13 @@ +package org.baeldung.web.dao; + +import java.util.List; + +import org.baeldung.web.entity.Employee; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(collectionResourceRel = "employee", path = "employees") +public interface EmployeeCRUDRepository extends CrudRepository { + List findByName(@Param("name") String name); +} diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/dao/StudentRepository.java b/spring-rest-angular/src/main/java/org/baeldung/web/dao/StudentRepository.java index b1aafb583a..566d95da00 100644 --- a/spring-rest-angular/src/main/java/org/baeldung/web/dao/StudentRepository.java +++ b/spring-rest-angular/src/main/java/org/baeldung/web/dao/StudentRepository.java @@ -3,6 +3,7 @@ package org.baeldung.web.dao; import org.baeldung.web.entity.Student; import org.springframework.data.jpa.repository.JpaRepository; -public interface StudentRepository extends JpaRepository { +public interface StudentRepository extends JpaRepository +{ } diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/entity/Employee.java b/spring-rest-angular/src/main/java/org/baeldung/web/entity/Employee.java new file mode 100644 index 0000000000..8d6831726c --- /dev/null +++ b/spring-rest-angular/src/main/java/org/baeldung/web/entity/Employee.java @@ -0,0 +1,57 @@ +package org.baeldung.web.entity; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class Employee implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private Integer age; + + public Employee() { + } + + public Employee(long id, String name, Integer age) { + super(); + this.id = id; + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/main/MvcConfig.java b/spring-rest-angular/src/main/java/org/baeldung/web/main/MvcConfig.java new file mode 100644 index 0000000000..b24aad1177 --- /dev/null +++ b/spring-rest-angular/src/main/java/org/baeldung/web/main/MvcConfig.java @@ -0,0 +1,36 @@ +package org.baeldung.web.main; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +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.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +@Configuration +@EnableWebMvc +@ComponentScan("org.baeldung.web.controller") +public class MvcConfig extends WebMvcConfigurerAdapter{ + + public MvcConfig(){ + super(); + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Bean + public ViewResolver viewResolver() { + final InternalResourceViewResolver bean = new InternalResourceViewResolver(); + + bean.setPrefix("/WEB-INF/pages/"); + bean.setSuffix(".html"); + + return bean; + } + +} diff --git a/spring-rest-angular/src/main/java/org/baeldung/web/main/PersistenceConfig.java b/spring-rest-angular/src/main/java/org/baeldung/web/main/PersistenceConfig.java index df1240f270..8454ce155a 100644 --- a/spring-rest-angular/src/main/java/org/baeldung/web/main/PersistenceConfig.java +++ b/spring-rest-angular/src/main/java/org/baeldung/web/main/PersistenceConfig.java @@ -2,7 +2,7 @@ package org.baeldung.web.main; import javax.sql.DataSource; -import org.springframework.boot.orm.jpa.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -26,7 +26,7 @@ public class PersistenceConfig { @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); - EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.HSQL).addScript("db/sql/data.sql").build(); + EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.HSQL).addScript("db/sql/data.sql").addScript("db/sql/employees.sql").build(); return db; } diff --git a/spring-rest-angular/src/main/resources/db/sql/employees.sql b/spring-rest-angular/src/main/resources/db/sql/employees.sql new file mode 100644 index 0000000000..366c0c309a --- /dev/null +++ b/spring-rest-angular/src/main/resources/db/sql/employees.sql @@ -0,0 +1,16 @@ +CREATE TABLE employee ( + id INTEGER PRIMARY KEY, + name VARCHAR(30), + age INTEGER +); + +INSERT INTO employee (id,name,age) +VALUES (1,'Bryan',20); +INSERT INTO employee (id,name,age) +VALUES (2,'Lisa',30); +INSERT INTO employee (id,name,age) +VALUES (3,'Laura',40); +INSERT INTO employee (id,name,age) +VALUES (4,'Alex',35); +INSERT INTO employee (id,name,age) +VALUES (5,'John',47); diff --git a/spring-rest-angular/src/main/webapp/WEB-INF/pages/employee.html b/spring-rest-angular/src/main/webapp/WEB-INF/pages/employee.html new file mode 100644 index 0000000000..510e981f25 --- /dev/null +++ b/spring-rest-angular/src/main/webapp/WEB-INF/pages/employee.html @@ -0,0 +1,55 @@ + + + + +Employee CRUD + + + + + + +
+ + + + + + + + + + + + + +
ID:
Name:
Age:
+

+ Get employee + Update employee + Add employee + Delete employee + +

+

{{message}}

+

{{errorMessage}}

+ +
+
+ Get all Employees

+ Name: + Get employees by name +

+
+ {{emp.name}} {{emp.age}} +
+
+ + \ No newline at end of file diff --git a/spring-rest-angular/src/main/webapp/view/app.js b/spring-rest-angular/src/main/webapp/view/app.js index a41026d2c3..9f78d5adf7 100644 --- a/spring-rest-angular/src/main/webapp/view/app.js +++ b/spring-rest-angular/src/main/webapp/view/app.js @@ -1,5 +1,5 @@ var app = angular.module('app', ['ui.grid','ui.grid.pagination']); - + app.controller('StudentCtrl', ['$scope','StudentService', function ($scope,StudentService) { var paginationOptions = { pageNumber: 1, @@ -53,4 +53,146 @@ app.service('StudentService',['$http', function ($http) { getStudents:getStudents }; +}]); + +app.controller('EmployeeCRUDCtrl', ['$scope','EmployeeCRUDService', function ($scope,EmployeeCRUDService) { + + $scope.updateEmployee = function () { + EmployeeCRUDService.updateEmployee($scope.employee.id,$scope.employee.name,$scope.employee.age) + .then(function success(response){ + $scope.message = 'Employee data updated!'; + $scope.errorMessage = ''; + }, + function error(response){ + $scope.errorMessage = 'Error updating Employee!'; + $scope.message = ''; + }); + } + + $scope.getEmployee = function () { + var id = $scope.employee.id; + EmployeeCRUDService.getEmployee($scope.employee.id) + .then(function success(response){ + $scope.employee = response.data; + $scope.employee.id = id; + $scope.message=''; + $scope.errorMessage = ''; + }, + function error (response ){ + $scope.message = ''; + if (response.status === 404){ + $scope.errorMessage = 'Employee not found!'; + } + else { + $scope.errorMessage = "Error getting Employee!"; + } + }); + } + + $scope.addEmployee = function () { + if ($scope.employee != null && $scope.employee.id) { + EmployeeCRUDService.addEmployee($scope.employee.id, $scope.employee.name, $scope.employee.age) + .then (function success(response){ + $scope.message = 'Employee added!'; + $scope.errorMessage = ''; + }, + function error(response){ + $scope.errorMessage = 'Error adding Employee!'; + $scope.message = ''; + }); + } + else { + $scope.errorMessage = 'Please enter an id!'; + $scope.message = ''; + } + } + + $scope.deleteEmployee = function () { + EmployeeCRUDService.deleteEmployee($scope.employee.id) + .then (function success(response){ + $scope.message = 'Employee deleted!'; + $scope.employee = null; + $scope.errorMessage=''; + }, + function error(response){ + $scope.errorMessage = 'Error deleting Employee!'; + $scope.message=''; + }) + } + + $scope.getAllEmployees = function () { + EmployeeCRUDService.getAllEmployees() + .then(function success(response){ + $scope.employees = response.data._embedded.employee; + $scope.message=''; + $scope.errorMessage = ''; + }, + function error (response ){ + $scope.message=''; + $scope.errorMessage = 'Error getting Employees!'; + }); + } + + $scope.getEmployeesByName = function () { + EmployeeCRUDService.getEmployeesByName($scope.name) + .then(function success(response){ + $scope.employees = response.data._embedded.employee; + $scope.message=''; + $scope.errorMessage = ''; + }, + function error (response ){ + $scope.message=''; + $scope.errorMessage = 'Error getting Employees!'; + }); + } + +}]); + +app.service('EmployeeCRUDService',['$http', function ($http) { + + this.getEmployee = function getEmployee(employeeId){ + return $http({ + method: 'GET', + url:'employees/'+employeeId + }); + } + + this.addEmployee = function addEmployee(id, name, age, gender){ + return $http({ + method: 'POST', + url:'employees', + data: {id:id, name:name, age:age} + }); + } + + this.deleteEmployee = function deleteEmployee(id){ + return $http({ + method: 'DELETE', + url: 'employees/'+id + }) + } + + this.updateEmployee = function updateEmployee(id,name,age){ + return $http({ + method: 'PATCH', + url: 'employees/'+id, + data: {name:name, age:age} + }) + } + + this.getAllEmployees = function getAllEmployees(){ + return $http({ + method: 'GET', + url:'employees' + }); + } + + this.getEmployeesByName = function getEmployeesByName(name){ + return $http({ + method: 'GET', + url:'employees/search/findByName', + params:{name:name} + }); + } + }]); \ No newline at end of file diff --git a/spring-rest-angular/src/test/java/org/baeldung/web/service/EmployeeCRUDRepositoryIntegrationTest.java b/spring-rest-angular/src/test/java/org/baeldung/web/service/EmployeeCRUDRepositoryIntegrationTest.java new file mode 100644 index 0000000000..57fe793596 --- /dev/null +++ b/spring-rest-angular/src/test/java/org/baeldung/web/service/EmployeeCRUDRepositoryIntegrationTest.java @@ -0,0 +1,47 @@ +package org.baeldung.web.service; + +import static org.junit.Assert.*; + +import org.baeldung.web.entity.Employee; +import org.baeldung.web.main.Application; +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 = Application.class, webEnvironment = WebEnvironment.DEFINED_PORT) +public class EmployeeCRUDRepositoryIntegrationTest { + + @Autowired + private TestRestTemplate template; + + private static final String EMPLOYEE_ENDPOINT = "http://localhost:8080/employees/"; + private static int EMPLOYEE_ID = 1; + private static int EMPLOYEE_AGE = 25; + + @Test + public void whenEmployeeCRUDOperations_thenCorrect() { + Employee Employee = new Employee(EMPLOYEE_ID, "Bryan", 20); + ResponseEntity postResponse = template.postForEntity(EMPLOYEE_ENDPOINT, Employee, Employee.class, ""); + assertEquals("status is not 201", HttpStatus.CREATED, postResponse.getStatusCode()); + + Employee.setAge(EMPLOYEE_AGE); + Employee patchResponse = template.patchForObject(EMPLOYEE_ENDPOINT + "/" + EMPLOYEE_ID, Employee, Employee.class); + assertEquals("age is not 25", Integer.valueOf(EMPLOYEE_AGE), patchResponse.getAge()); + + ResponseEntity getResponse = template.getForEntity(EMPLOYEE_ENDPOINT + "/" + EMPLOYEE_ID, Employee.class, ""); + assertEquals("status is not 200", HttpStatus.OK, getResponse.getStatusCode()); + + template.delete(EMPLOYEE_ENDPOINT + "/" + EMPLOYEE_ID); + + getResponse = template.getForEntity(EMPLOYEE_ENDPOINT + "/" + EMPLOYEE_ID, Employee.class, ""); + assertEquals("status is not 404", HttpStatus.NOT_FOUND, getResponse.getStatusCode()); + + } +} From 5930f9dcbb97005230fdc8181c2663e321d0a73f Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Mon, 27 Feb 2017 19:31:02 +0100 Subject: [PATCH 6/9] Refactor SpringDataRelationshipsTest (#1258) --- .../SpringDataRelationshipsTest.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java b/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java index ea2e70a4e4..21a067a645 100644 --- a/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java +++ b/spring-data-rest/src/test/java/com/baeldung/relationships/SpringDataRelationshipsTest.java @@ -1,5 +1,13 @@ package com.baeldung.relationships; +import com.baeldung.SpringDataRestApplication; +import com.baeldung.models.Address; +import com.baeldung.models.Author; +import com.baeldung.models.Book; +import com.baeldung.models.Library; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -11,17 +19,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; -import com.baeldung.SpringDataRestApplication; -import com.baeldung.models.Address; -import com.baeldung.models.Author; -import com.baeldung.models.Book; -import com.baeldung.models.Library; - -import org.junit.Test; -import static org.junit.Assert.*; - -import org.json.JSONArray; -import org.json.JSONObject; +import static org.junit.Assert.assertEquals; @RunWith(SpringRunner.class) @SpringBootTest(classes = SpringDataRestApplication.class, webEnvironment = WebEnvironment.DEFINED_PORT) @@ -48,7 +46,7 @@ public class SpringDataRelationshipsTest { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-type", "text/uri-list"); - HttpEntity httpEntity = new HttpEntity(ADDRESS_ENDPOINT + "/1", requestHeaders); + HttpEntity httpEntity = new HttpEntity<>(ADDRESS_ENDPOINT + "/1", requestHeaders); template.exchange(LIBRARY_ENDPOINT + "/1/libraryAddress", HttpMethod.PUT, httpEntity, String.class); ResponseEntity libraryGetResponse = template.getForEntity(ADDRESS_ENDPOINT + "/1/library", Library.class); @@ -69,7 +67,7 @@ public class SpringDataRelationshipsTest { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-type", "text/uri-list"); - HttpEntity bookHttpEntity = new HttpEntity(LIBRARY_ENDPOINT + "/1", requestHeaders); + HttpEntity bookHttpEntity = new HttpEntity<>(LIBRARY_ENDPOINT + "/1", requestHeaders); template.exchange(BOOK_ENDPOINT + "/1/library", HttpMethod.PUT, bookHttpEntity, String.class); template.exchange(BOOK_ENDPOINT + "/2/library", HttpMethod.PUT, bookHttpEntity, String.class); @@ -91,7 +89,7 @@ public class SpringDataRelationshipsTest { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-type", "text/uri-list"); - HttpEntity httpEntity = new HttpEntity(BOOK_ENDPOINT + "/1\n" + BOOK_ENDPOINT + "/2", requestHeaders); + HttpEntity httpEntity = new HttpEntity<>(BOOK_ENDPOINT + "/1\n" + BOOK_ENDPOINT + "/2", requestHeaders); template.exchange(AUTHOR_ENDPOINT + "/1/books", HttpMethod.PUT, httpEntity, String.class); String jsonResponse = template.getForObject(BOOK_ENDPOINT + "/1/authors", String.class); From 29882a1f06e02faf90ade160996829ad953a1cf9 Mon Sep 17 00:00:00 2001 From: maibin Date: Mon, 27 Feb 2017 19:59:20 +0100 Subject: [PATCH 7/9] Ant Colony Optimization (#1237) --- .../com/baeldung/algorithms/RunAlgorithm.java | 8 +- .../algorithms/{ => ga}/annealing/City.java | 2 +- .../annealing/SimulatedAnnealing.java | 2 +- .../algorithms/{ => ga}/annealing/Travel.java | 2 +- .../algorithms/ga/ant_colony/Ant.java | 37 +++ .../ga/ant_colony/AntColonyOptimization.java | 212 ++++++++++++++++++ .../algorithms/AntColonyOptimizationTest.java | 22 ++ .../algorithms/SimulatedAnnealingTest.java | 2 +- 8 files changed, 282 insertions(+), 5 deletions(-) rename core-java/src/main/java/com/baeldung/algorithms/{ => ga}/annealing/City.java (90%) rename core-java/src/main/java/com/baeldung/algorithms/{ => ga}/annealing/SimulatedAnnealing.java (96%) rename core-java/src/main/java/com/baeldung/algorithms/{ => ga}/annealing/Travel.java (97%) create mode 100644 core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/Ant.java create mode 100644 core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/AntColonyOptimization.java create mode 100644 core-java/src/test/java/com/baeldung/algorithms/AntColonyOptimizationTest.java diff --git a/core-java/src/main/java/com/baeldung/algorithms/RunAlgorithm.java b/core-java/src/main/java/com/baeldung/algorithms/RunAlgorithm.java index 3f7c8bf4b3..22c5776293 100644 --- a/core-java/src/main/java/com/baeldung/algorithms/RunAlgorithm.java +++ b/core-java/src/main/java/com/baeldung/algorithms/RunAlgorithm.java @@ -2,7 +2,8 @@ package com.baeldung.algorithms; import java.util.Scanner; -import com.baeldung.algorithms.annealing.SimulatedAnnealing; +import com.baeldung.algorithms.ga.annealing.SimulatedAnnealing; +import com.baeldung.algorithms.ga.ant_colony.AntColonyOptimization; import com.baeldung.algorithms.ga.binary.SimpleGeneticAlgorithm; import com.baeldung.algorithms.slope_one.SlopeOne; @@ -14,6 +15,7 @@ public class RunAlgorithm { System.out.println("1 - Simulated Annealing"); System.out.println("2 - Slope One"); System.out.println("3 - Simple Genetic Algorithm"); + System.out.println("4 - Ant Colony"); int decision = in.nextInt(); switch (decision) { case 1: @@ -27,6 +29,10 @@ public class RunAlgorithm { SimpleGeneticAlgorithm ga = new SimpleGeneticAlgorithm(); ga.runAlgorithm(50, "1011000100000100010000100000100111001000000100000100000000001111"); break; + case 4: + AntColonyOptimization antColony = new AntColonyOptimization(21); + antColony.startAntOptimization(); + break; default: System.out.println("Unknown option"); break; diff --git a/core-java/src/main/java/com/baeldung/algorithms/annealing/City.java b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/City.java similarity index 90% rename from core-java/src/main/java/com/baeldung/algorithms/annealing/City.java rename to core-java/src/main/java/com/baeldung/algorithms/ga/annealing/City.java index 77e8652df0..cb5647f4d2 100644 --- a/core-java/src/main/java/com/baeldung/algorithms/annealing/City.java +++ b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/City.java @@ -1,4 +1,4 @@ -package com.baeldung.algorithms.annealing; +package com.baeldung.algorithms.ga.annealing; import lombok.Data; diff --git a/core-java/src/main/java/com/baeldung/algorithms/annealing/SimulatedAnnealing.java b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/SimulatedAnnealing.java similarity index 96% rename from core-java/src/main/java/com/baeldung/algorithms/annealing/SimulatedAnnealing.java rename to core-java/src/main/java/com/baeldung/algorithms/ga/annealing/SimulatedAnnealing.java index a7dc974e97..bff64fc239 100644 --- a/core-java/src/main/java/com/baeldung/algorithms/annealing/SimulatedAnnealing.java +++ b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/SimulatedAnnealing.java @@ -1,4 +1,4 @@ -package com.baeldung.algorithms.annealing; +package com.baeldung.algorithms.ga.annealing; public class SimulatedAnnealing { diff --git a/core-java/src/main/java/com/baeldung/algorithms/annealing/Travel.java b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/Travel.java similarity index 97% rename from core-java/src/main/java/com/baeldung/algorithms/annealing/Travel.java rename to core-java/src/main/java/com/baeldung/algorithms/ga/annealing/Travel.java index 9bf341fbbe..3139b49586 100644 --- a/core-java/src/main/java/com/baeldung/algorithms/annealing/Travel.java +++ b/core-java/src/main/java/com/baeldung/algorithms/ga/annealing/Travel.java @@ -1,4 +1,4 @@ -package com.baeldung.algorithms.annealing; +package com.baeldung.algorithms.ga.annealing; import java.util.ArrayList; import java.util.Collections; diff --git a/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/Ant.java b/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/Ant.java new file mode 100644 index 0000000000..4ea23b799f --- /dev/null +++ b/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/Ant.java @@ -0,0 +1,37 @@ +package com.baeldung.algorithms.ga.ant_colony; + +public class Ant { + + protected int trailSize; + protected int trail[]; + protected boolean visited[]; + + public Ant(int tourSize) { + this.trailSize = tourSize; + this.trail = new int[tourSize]; + this.visited = new boolean[tourSize]; + } + + protected void visitCity(int currentIndex, int city) { + trail[currentIndex + 1] = city; + visited[city] = true; + } + + protected boolean visited(int i) { + return visited[i]; + } + + protected double trailLength(double graph[][]) { + double length = graph[trail[trailSize - 1]][trail[0]]; + for (int i = 0; i < trailSize - 1; i++) { + length += graph[trail[i]][trail[i + 1]]; + } + return length; + } + + protected void clear() { + for (int i = 0; i < trailSize; i++) + visited[i] = false; + } + +} diff --git a/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/AntColonyOptimization.java b/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/AntColonyOptimization.java new file mode 100644 index 0000000000..e46ac77e84 --- /dev/null +++ b/core-java/src/main/java/com/baeldung/algorithms/ga/ant_colony/AntColonyOptimization.java @@ -0,0 +1,212 @@ +package com.baeldung.algorithms.ga.ant_colony; + +import java.util.Arrays; +import java.util.Random; + +public class AntColonyOptimization { + + private double c = 1.0; + private double alpha = 1; + private double beta = 5; + private double evaporation = 0.5; + private double Q = 500; + private double antFactor = 0.8; + private double randomFactor = 0.01; + + private int maxIterations = 1000; + + public int numberOfCities; + public int numberOfAnts; + private double graph[][]; + private double trails[][]; + private Ant ants[]; + private Random random = new Random(); + private double probabilities[]; + + private int currentIndex; + + public int[] bestTourOrder; + public double bestTourLength; + + public AntColonyOptimization(int noOfCities) { + graph = generateRandomMatrix(noOfCities); + numberOfCities = graph.length; + numberOfAnts = (int) (numberOfCities * antFactor); + + trails = new double[numberOfCities][numberOfCities]; + probabilities = new double[numberOfCities]; + ants = new Ant[numberOfAnts]; + for (int j = 0; j < numberOfAnts; j++) { + ants[j] = new Ant(numberOfCities); + } + } + + /** + * Generate initial solution + * @param n + * @return + */ + public double[][] generateRandomMatrix(int n) { + double[][] randomMatrix = new double[n][n]; + random.setSeed(System.currentTimeMillis()); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + Integer r = random.nextInt(100) + 1; + randomMatrix[i][j] = Math.abs(r); + } + } + return randomMatrix; + } + + /** + * Perform ant optimization + * @return + */ + public int[] startAntOptimization() { + int[] finalResult = null; + for (int i = 1; i <= 3; i++) { + System.out.println("Attempt #" + i); + finalResult = solve(); + } + return finalResult; + } + + /** + * Use this method to run the main logic + * @return + */ + private int[] solve() { + setupAnts(); + clearTrails(); + int iteration = 0; + while (iteration < maxIterations) { + moveAnts(); + updateTrails(); + updateBest(); + iteration++; + } + System.out.println("Best tour length: " + (bestTourLength - numberOfCities)); + System.out.println("Best tour order: " + Arrays.toString(bestTourOrder)); + return bestTourOrder.clone(); + } + + /** + * Prepare ants for the simulation + */ + private void setupAnts() { + currentIndex = -1; + for (int i = 0; i < numberOfAnts; i++) { + ants[i].clear(); + ants[i].visitCity(currentIndex, random.nextInt(numberOfCities)); + } + currentIndex++; + } + + /** + * At each iteration, move ants + */ + private void moveAnts() { + while (currentIndex < numberOfCities - 1) { + for (Ant a : ants) + a.visitCity(currentIndex, selectNextCity(a)); + currentIndex++; + } + } + + /** + * Select next city for each ant + * @param ant + * @return + */ + private int selectNextCity(Ant ant) { + if (random.nextDouble() < randomFactor) { + int t = random.nextInt(numberOfCities - currentIndex); + int j = -1; + for (int i = 0; i < numberOfCities; i++) { + if (!ant.visited(i)) { + j++; + } + if (j == t) { + return i; + } + } + } + calculateProbabilities(ant); + double r = random.nextDouble(); + double total = 0; + for (int i = 0; i < numberOfCities; i++) { + total += probabilities[i]; + if (total >= r) { + return i; + } + } + + throw new RuntimeException("There are no other cities"); + } + + /** + * Calculate the next city picks probabilites + * @param ant + */ + private void calculateProbabilities(Ant ant) { + int i = ant.trail[currentIndex]; + double pheromone = 0.0; + for (int l = 0; l < numberOfCities; l++) { + if (!ant.visited(l)) { + pheromone += Math.pow(trails[i][l], alpha) * Math.pow(1.0 / graph[i][l], beta); + } + } + for (int j = 0; j < numberOfCities; j++) { + if (ant.visited(j)) { + probabilities[j] = 0.0; + } else { + double numerator = Math.pow(trails[i][j], alpha) * Math.pow(1.0 / graph[i][j], beta); + probabilities[j] = numerator / pheromone; + } + } + } + + /** + * Update trails that ants used + */ + private void updateTrails() { + for (int i = 0; i < numberOfCities; i++) { + for (int j = 0; j < numberOfCities; j++) { + trails[i][j] *= evaporation; + } + } + for (Ant a : ants) { + double contribution = Q / a.trailLength(graph); + for (int i = 0; i < numberOfCities - 1; i++) { + trails[a.trail[i]][a.trail[i + 1]] += contribution; + } + trails[a.trail[numberOfCities - 1]][a.trail[0]] += contribution; + } + } + + /** + * Update the best solution + */ + private void updateBest() { + if (bestTourOrder == null) { + bestTourOrder = ants[0].trail; + bestTourLength = ants[0].trailLength(graph); + } + for (Ant a : ants) { + if (a.trailLength(graph) < bestTourLength) { + bestTourLength = a.trailLength(graph); + bestTourOrder = a.trail.clone(); + } + } + } + + /** + * Clear trails after simulation + */ + private void clearTrails() { + for (int i = 0; i < numberOfCities; i++) + for (int j = 0; j < numberOfCities; j++) + trails[i][j] = c; + } + +} diff --git a/core-java/src/test/java/com/baeldung/algorithms/AntColonyOptimizationTest.java b/core-java/src/test/java/com/baeldung/algorithms/AntColonyOptimizationTest.java new file mode 100644 index 0000000000..cd8efaa106 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/algorithms/AntColonyOptimizationTest.java @@ -0,0 +1,22 @@ +package com.baeldung.algorithms; + +import org.junit.Assert; +import org.junit.Test; + +import com.baeldung.algorithms.ga.ant_colony.AntColonyOptimization; + +public class AntColonyOptimizationTest { + + @Test + public void testGenerateRandomMatrix() { + AntColonyOptimization antTSP = new AntColonyOptimization(5); + Assert.assertNotNull(antTSP.generateRandomMatrix(5)); + } + + @Test + public void testStartAntOptimization() { + AntColonyOptimization antTSP = new AntColonyOptimization(5); + Assert.assertNotNull(antTSP.startAntOptimization()); + } + +} diff --git a/core-java/src/test/java/com/baeldung/algorithms/SimulatedAnnealingTest.java b/core-java/src/test/java/com/baeldung/algorithms/SimulatedAnnealingTest.java index 2fc7ea9b92..6822bae990 100644 --- a/core-java/src/test/java/com/baeldung/algorithms/SimulatedAnnealingTest.java +++ b/core-java/src/test/java/com/baeldung/algorithms/SimulatedAnnealingTest.java @@ -3,7 +3,7 @@ package com.baeldung.algorithms; import org.junit.Assert; import org.junit.Test; -import com.baeldung.algorithms.annealing.SimulatedAnnealing; +import com.baeldung.algorithms.ga.annealing.SimulatedAnnealing; public class SimulatedAnnealingTest { From 1bfc944c5a5db228a6bd648794360df6aeb58225 Mon Sep 17 00:00:00 2001 From: Nancy Bosecker Date: Mon, 27 Feb 2017 20:26:30 -0800 Subject: [PATCH 8/9] Added more indexing/delete/query examples to code (#1251) * 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 --- .../com/baeldung/solrjava/ProductBean.java | 44 +++++++++++++ .../solrjava/SolrJavaIntegration.java | 15 ++++- .../solrjava/SolrJavaIntegrationTest.java | 62 +++++++++++++++++-- 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 apache-solrj/src/main/java/com/baeldung/solrjava/ProductBean.java diff --git a/apache-solrj/src/main/java/com/baeldung/solrjava/ProductBean.java b/apache-solrj/src/main/java/com/baeldung/solrjava/ProductBean.java new file mode 100644 index 0000000000..14eea8f2f9 --- /dev/null +++ b/apache-solrj/src/main/java/com/baeldung/solrjava/ProductBean.java @@ -0,0 +1,44 @@ +package com.baeldung.solrjava; + +import org.apache.solr.client.solrj.beans.Field; + +public class ProductBean { + + String id; + String name; + String price; + + public ProductBean(String id, String name, String price) { + super(); + this.id = id; + this.name = name; + this.price = price; + } + + public String getId() { + return id; + } + + @Field("id") + protected void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + @Field("name") + protected void setName(String name) { + this.name = name; + } + + public String getPrice() { + return price; + } + + @Field("price") + protected void setPrice(String price) { + this.price = price; + } +} diff --git a/apache-solrj/src/main/java/com/baeldung/solrjava/SolrJavaIntegration.java b/apache-solrj/src/main/java/com/baeldung/solrjava/SolrJavaIntegration.java index f2d21f0993..c55e1c9ada 100644 --- a/apache-solrj/src/main/java/com/baeldung/solrjava/SolrJavaIntegration.java +++ b/apache-solrj/src/main/java/com/baeldung/solrjava/SolrJavaIntegration.java @@ -17,6 +17,12 @@ public class SolrJavaIntegration { solrClient.setParser(new XMLResponseParser()); } + public void addProductBean(ProductBean pBean) throws IOException, SolrServerException { + + solrClient.addBean(pBean); + solrClient.commit(); + } + public void addSolrDocument(String documentId, String itemName, String itemPrice) throws SolrServerException, IOException { SolrInputDocument document = new SolrInputDocument(); @@ -27,12 +33,18 @@ public class SolrJavaIntegration { solrClient.commit(); } - public void deleteSolrDocument(String documentId) throws SolrServerException, IOException { + public void deleteSolrDocumentById(String documentId) throws SolrServerException, IOException { solrClient.deleteById(documentId); solrClient.commit(); } + public void deleteSolrDocumentByQuery(String query) throws SolrServerException, IOException { + + solrClient.deleteByQuery(query); + solrClient.commit(); + } + protected HttpSolrClient getSolrClient() { return solrClient; } @@ -40,4 +52,5 @@ public class SolrJavaIntegration { protected void setSolrClient(HttpSolrClient solrClient) { this.solrClient = solrClient; } + } diff --git a/apache-solrj/src/test/java/com/baeldung/solrjava/SolrJavaIntegrationTest.java b/apache-solrj/src/test/java/com/baeldung/solrjava/SolrJavaIntegrationTest.java index 22f9eae8ee..7f4599a91d 100644 --- a/apache-solrj/src/test/java/com/baeldung/solrjava/SolrJavaIntegrationTest.java +++ b/apache-solrj/src/test/java/com/baeldung/solrjava/SolrJavaIntegrationTest.java @@ -24,7 +24,7 @@ public class SolrJavaIntegrationTest { } @Test - public void whenAdd_thenVerifyAdded() throws SolrServerException, IOException { + public void whenAdd_thenVerifyAddedByQueryOnId() throws SolrServerException, IOException { SolrQuery query = new SolrQuery(); query.set("q", "id:123456"); @@ -36,15 +36,65 @@ public class SolrJavaIntegrationTest { assertEquals(docList.getNumFound(), 1); for (SolrDocument doc : docList) { - assertEquals((String) doc.getFieldValue("id"), "123456"); - assertEquals((Double) doc.getFieldValue("price"), (Double) 599.99); + assertEquals("Kenmore Dishwasher", (String) doc.getFieldValue("name")); + assertEquals((Double) 599.99, (Double) doc.getFieldValue("price")); } } @Test - public void whenDelete_thenVerifyDeleted() throws SolrServerException, IOException { + public void whenAdd_thenVerifyAddedByQueryOnPrice() throws SolrServerException, IOException { - solrJavaIntegration.deleteSolrDocument("123456"); + SolrQuery query = new SolrQuery(); + query.set("q", "price:599.99"); + QueryResponse response = null; + + response = solrJavaIntegration.getSolrClient().query(query); + + SolrDocumentList docList = response.getResults(); + assertEquals(1, docList.getNumFound()); + + for (SolrDocument doc : docList) { + assertEquals("123456", (String) doc.getFieldValue("id")); + assertEquals((Double) 599.99, (Double) doc.getFieldValue("price")); + } + } + + @Test + public void whenAdd_thenVerifyAddedByQuery() throws SolrServerException, IOException { + + SolrDocument doc = solrJavaIntegration.getSolrClient().getById("123456"); + assertEquals("Kenmore Dishwasher", (String) doc.getFieldValue("name")); + assertEquals((Double) 599.99, (Double) doc.getFieldValue("price")); + } + + @Test + public void whenAddBean_thenVerifyAddedByQuery() throws SolrServerException, IOException { + + ProductBean pBean = new ProductBean("888", "Apple iPhone 6s", "299.99"); + solrJavaIntegration.addProductBean(pBean); + + SolrDocument doc = solrJavaIntegration.getSolrClient().getById("888"); + assertEquals("Apple iPhone 6s", (String) doc.getFieldValue("name")); + assertEquals((Double) 299.99, (Double) doc.getFieldValue("price")); + } + + @Test + public void whenDeleteById_thenVerifyDeleted() throws SolrServerException, IOException { + + solrJavaIntegration.deleteSolrDocumentById("123456"); + + SolrQuery query = new SolrQuery(); + query.set("q", "id:123456"); + QueryResponse response = solrJavaIntegration.getSolrClient().query(query); + + SolrDocumentList docList = response.getResults(); + assertEquals(0, docList.getNumFound()); + } + + @Test + public void whenDeleteByQuery_thenVerifyDeleted() throws SolrServerException, IOException { + + solrJavaIntegration.deleteSolrDocumentByQuery("name:Kenmore Dishwasher"); SolrQuery query = new SolrQuery(); query.set("q", "id:123456"); @@ -53,6 +103,6 @@ public class SolrJavaIntegrationTest { response = solrJavaIntegration.getSolrClient().query(query); SolrDocumentList docList = response.getResults(); - assertEquals(docList.getNumFound(), 0); + assertEquals(0, docList.getNumFound()); } } From 442c00545c4fe7109c971d2dd6414dc619df301e Mon Sep 17 00:00:00 2001 From: mujahid Date: Wed, 1 Mar 2017 02:00:13 +0800 Subject: [PATCH 9/9] BAEL-347-Full-text search with SOLR (#1254) * solr-fulltext-search module created * solr-fulltext-search modue created * solr-fulltext-search change s * pom changes merged from upstream --- solr-fulltext-search/pom.xml | 2 +- .../solr/fulltext/search/model/Item.java | 51 +++ .../search/service/ItemSearchService.java | 15 + .../search/service/ItemSearchServiceImpl.java | 34 ++ .../ItemSearchServiceIntegrationTest.java | 374 ++++++++++++++++++ 5 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java create mode 100644 solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java create mode 100644 solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java create mode 100644 solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.java diff --git a/solr-fulltext-search/pom.xml b/solr-fulltext-search/pom.xml index 4afcb5838a..bed6afd48f 100644 --- a/solr-fulltext-search/pom.xml +++ b/solr-fulltext-search/pom.xml @@ -18,7 +18,7 @@ org.apache.solr solr-solrj - 6.1.0 + 6.4.1 log4j diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java new file mode 100644 index 0000000000..d7a0524ca9 --- /dev/null +++ b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/model/Item.java @@ -0,0 +1,51 @@ +package com.baeldung.solr.fulltext.search.model; + +import org.apache.solr.client.solrj.beans.Field; + +public class Item { + + @Field + private String id; + + @Field + private String description; + + @Field + private String category; + + @Field + private float price; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public float getPrice() { + return price; + } + + public void setPrice(float price) { + this.price = price; + } + +} diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java new file mode 100644 index 0000000000..fa906fc975 --- /dev/null +++ b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchService.java @@ -0,0 +1,15 @@ +package com.baeldung.solr.fulltext.search.service; + +import java.io.IOException; + +import org.apache.solr.client.solrj.SolrServerException; + +import com.baeldung.solr.fulltext.search.model.Item; + +public interface ItemSearchService { + + public void index(String id, String description, String category, float price) throws SolrServerException, IOException; + + public void indexBean(Item item) throws IOException, SolrServerException; + +} diff --git a/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java new file mode 100644 index 0000000000..573cc58bc0 --- /dev/null +++ b/solr-fulltext-search/src/main/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceImpl.java @@ -0,0 +1,34 @@ +package com.baeldung.solr.fulltext.search.service; + +import java.io.IOException; + +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.common.SolrInputDocument; + +import com.baeldung.solr.fulltext.search.model.Item; + +public class ItemSearchServiceImpl implements ItemSearchService { + + private final SolrClient solrClient; + + public ItemSearchServiceImpl(SolrClient solrClient) { + this.solrClient = solrClient; + } + + public void index(String id, String description, String category, float price) throws SolrServerException, IOException { + SolrInputDocument doc = new SolrInputDocument(); + doc.addField("id", id); + doc.addField("description", description); + doc.addField("category", category); + doc.addField("price", price); + solrClient.add(doc); + solrClient.commit(); + } + + public void indexBean(Item item) throws IOException, SolrServerException { + solrClient.addBean(item); + solrClient.commit(); + } + +} 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 new file mode 100644 index 0000000000..94661ffc2e --- /dev/null +++ b/solr-fulltext-search/src/test/java/com/baeldung/solr/fulltext/search/service/ItemSearchServiceIntegrationTest.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 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"); + } + } + + } + +}