Merge pull request #48 from eugenp/master

update
This commit is contained in:
Maiklins 2020-04-26 12:21:53 +02:00 committed by GitHub
commit 6c0a91ef6e
55 changed files with 1332 additions and 13 deletions

View File

@ -128,6 +128,21 @@
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.rosuda.REngine</groupId>
<artifactId>Rserve</artifactId>
<version>${rserve.version}</version>
</dependency>
<dependency>
<groupId>com.github.jbytecode</groupId>
<artifactId>RCaller</artifactId>
<version>${rcaller.version}</version>
</dependency>
<dependency>
<groupId>org.renjin</groupId>
<artifactId>renjin-script-engine</artifactId>
<version>${renjin.version}</version>
</dependency>
</dependencies>
<repositories>
@ -137,6 +152,13 @@
<url>http://repo.numericalmethod.com/maven/</url>
<layout>default</layout>
</repository>
<!-- Needed for Renjin -->
<repository>
<id>bedatadriven</id>
<name>bedatadriven public repo</name>
<url>https://nexus.bedatadriven.com/content/groups/public/</url>
</repository>
</repositories>
<properties>
@ -153,6 +175,27 @@
<assertj.version>3.6.2</assertj.version>
<slf4j.version>1.7.25</slf4j.version>
<awaitility.version>3.0.0</awaitility.version>
<renjin.version>RELEASE</renjin.version>
<rcaller.version>3.0</rcaller.version>
<rserve.version>1.8.1</rserve.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Excludes FastR classes from compilations since they require GraalVM -->
<excludes>
<exclude>com/baeldung/r/FastRMean.java</exclude>
</excludes>
<testExcludes>
<exclude>com/baeldung/r/FastRMeanUnitTest.java</exclude>
</testExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,33 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* FastR showcase.
*
* @author Donato Rimenti
*/
public class FastRMean {
/**
* Invokes the customMean R function passing the given values as arguments.
*
* @param values the input to the mean script
* @return the result of the R script
*/
public double mean(int[] values) {
Context polyglot = Context.newBuilder()
.allowAllAccess(true)
.build();
String meanScriptContent = RUtils.getMeanScriptContent();
polyglot.eval("R", meanScriptContent);
Value rBindings = polyglot.getBindings("R");
Value rInput = rBindings.getMember("c")
.execute(values);
return rBindings.getMember("customMean")
.execute(rInput)
.asDouble();
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URISyntaxException;
import com.github.rcaller.rstuff.RCaller;
import com.github.rcaller.rstuff.RCallerOptions;
import com.github.rcaller.rstuff.RCode;
/**
* RCaller showcase.
*
* @author Donato Rimenti
*/
public class RCallerMean {
/**
* Invokes the customMean R function passing the given values as arguments.
*
* @param values the input to the mean script
* @return the result of the R script
* @throws IOException if any error occurs
* @throws URISyntaxException if any error occurs
*/
public double mean(int[] values) throws IOException, URISyntaxException {
String fileContent = RUtils.getMeanScriptContent();
RCode code = RCode.create();
code.addRCode(fileContent);
code.addIntArray("input", values);
code.addRCode("result <- customMean(input)");
RCaller caller = RCaller.create(code, RCallerOptions.create());
caller.runAndReturnResult("result");
return caller.getParser()
.getAsDoubleArray("result")[0];
}
}

View File

@ -0,0 +1,33 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
/**
* Utility class for loading the script.R content.
*
* @author Donato Rimenti
*/
public class RUtils {
/**
* Loads the script.R and returns its content as a string.
*
* @return the script.R content as a string
* @throws IOException if any error occurs
* @throws URISyntaxException if any error occurs
*/
static String getMeanScriptContent() throws IOException, URISyntaxException {
URI rScriptUri = RUtils.class.getClassLoader()
.getResource("script.R")
.toURI();
Path inputScript = Paths.get(rScriptUri);
return Files.lines(inputScript)
.collect(Collectors.joining());
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.script.ScriptException;
import org.renjin.script.RenjinScriptEngine;
import org.renjin.sexp.DoubleArrayVector;
/**
* Renjin showcase.
*
* @author Donato Rimenti
*/
public class RenjinMean {
/**
* Invokes the customMean R function passing the given values as arguments.
*
* @param values the input to the mean script
* @return the result of the R script
* @throws IOException if any error occurs
* @throws URISyntaxException if any error occurs
* @throws ScriptException if any error occurs
*/
public double mean(int[] values) throws IOException, URISyntaxException, ScriptException {
RenjinScriptEngine engine = new RenjinScriptEngine();
String meanScriptContent = RUtils.getMeanScriptContent();
engine.put("input", values);
engine.eval(meanScriptContent);
DoubleArrayVector result = (DoubleArrayVector) engine.eval("customMean(input)");
return result.asReal();
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.r;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.REngineException;
import org.rosuda.REngine.Rserve.RConnection;
/**
* Rserve showcase.
*
* @author Donato Rimenti
*/
public class RserveMean {
/**
* Connects to the Rserve istance listening on 127.0.0.1:6311 and invokes the
* customMean R function passing the given values as arguments.
*
* @param values the input to the mean script
* @return the result of the R script
* @throws REngineException if any error occurs
* @throws REXPMismatchException if any error occurs
*/
public double mean(int[] values) throws REngineException, REXPMismatchException {
RConnection c = new RConnection();
c.assign("input", values);
return c.eval("customMean(input)")
.asDouble();
}
}

View File

@ -0,0 +1,29 @@
package com.baeldung.r;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
/**
* Test for {@link FastRMean}.
*
* @author Donato Rimenti
*/
@Ignore
public class FastRMeanUnitTest {
/**
* Object to test.
*/
private FastRMean fastrMean = new FastRMean();
/**
* Test for {@link FastRMeanUnitTest#mean(int[])}.
*/
@Test
public void givenValues_whenMean_thenCorrect() {
int[] input = { 1, 2, 3, 4, 5 };
double result = fastrMean.mean(input);
Assert.assertEquals(3.0, result, 0.000001);
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.script.ScriptException;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
/**
* Test for {@link RCallerMean}.
*
* @author Donato Rimenti
*/
@Ignore
public class RCallerMeanIntegrationTest {
/**
* Object to test.
*/
private RCallerMean rcallerMean = new RCallerMean();
/**
* Test for {@link RCallerMeanIntegrationTest#mean(int[])}.
*
* @throws ScriptException if an error occurs
* @throws URISyntaxException if an error occurs
*/
@Test
public void givenValues_whenMean_thenCorrect() throws IOException, URISyntaxException {
int[] input = { 1, 2, 3, 4, 5 };
double result = rcallerMean.mean(input);
Assert.assertEquals(3.0, result, 0.000001);
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.r;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.script.ScriptException;
import org.junit.Test;
import org.junit.Assert;
/**
* Test for {@link RenjinMean}.
*
* @author Donato Rimenti
*/
public class RenjinMeanUnitTest {
/**
* Object to test.
*/
private RenjinMean renjinMean = new RenjinMean();
/**
* Test for {@link RenjinMeanUnitTest#mean(int[])}.
*
* @throws ScriptException if an error occurs
* @throws URISyntaxException if an error occurs
* @throws IOException if an error occurs
*/
@Test
public void givenValues_whenMean_thenCorrect() throws IOException, URISyntaxException, ScriptException {
int[] input = { 1, 2, 3, 4, 5 };
double result = renjinMean.mean(input);
Assert.assertEquals(3.0, result, 0.000001);
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.r;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.REngineException;
/**
* Test for {@link RserveMean}.
*
* @author Donato Rimenti
*/
@Ignore
public class RserveMeanIntegrationTest {
/**
* Object to test.
*/
private RserveMean rserveMean = new RserveMean();
/**
* Test for {@link RserveMeanIntegrationTest#mean(int[])}.
*
* @throws REXPMismatchException if an error occurs
* @throws REngineException if an error occurs
*/
@Test
public void givenValues_whenMean_thenCorrect() throws REngineException, REXPMismatchException {
int[] input = { 1, 2, 3, 4, 5 };
double result = rserveMean.mean(input);
Assert.assertEquals(3.0, result, 0.000001);
}
}

View File

@ -0,0 +1,3 @@
customMean <- function(vector) {
mean(vector)
}

View File

@ -9,9 +9,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-1</relativePath>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
@ -19,7 +19,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Hopper-SR10</version>
<version>Lovelace-SR16</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -174,7 +174,7 @@
<start-class>com.baeldung.Application</start-class>
<spring.version>4.3.4.RELEASE</spring.version>
<httpclient.version>4.5.2</httpclient.version>
<spring-data-dynamodb.version>4.4.1</spring-data-dynamodb.version>
<spring-data-dynamodb.version>5.1.0</spring-data-dynamodb.version>
<aws-java-sdk-dynamodb.version>1.11.64</aws-java-sdk-dynamodb.version>
<bootstrap.version>3.3.7-1</bootstrap.version>
<sqlite4java.version>1.0.392</sqlite4java.version>

View File

@ -44,8 +44,8 @@ public class DynamoDBConfig {
return new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey);
}
@Bean(name = "mvcHandlerMappingIntrospector")
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
@Bean(name = "mvcHandlerMappingIntrospectorCustom")
public HandlerMappingIntrospector mvcHandlerMappingIntrospectorCustom() {
return new HandlerMappingIntrospector(context);
}
}

View File

@ -1,12 +1,13 @@
package com.baeldung.spring.data.dynamodb.repositories;
import com.baeldung.spring.data.dynamodb.model.ProductInfo;
import java.util.Optional;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
import com.baeldung.spring.data.dynamodb.model.ProductInfo;
@EnableScan
public interface ProductInfoRepository extends CrudRepository<ProductInfo, String> {
List<ProductInfo> findById(String id);
Optional<ProductInfo> findById(String id);
}

View File

@ -652,6 +652,7 @@
<module>spring-core</module>
<module>spring-core-2</module>
<module>spring-core-3</module>
<module>spring-core-4</module>
<module>spring-cucumber</module>
<module>spring-data-rest</module>
@ -1157,6 +1158,7 @@
<module>spring-core</module>
<module>spring-core-2</module>
<module>spring-core-3</module>
<module>spring-core-4</module>
<module>spring-cucumber</module>
<module>spring-data-rest</module>

View File

@ -0,0 +1,13 @@
package com.baeldung.springwithgroovy
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import com.baeldung.springwithgroovy.SpringBootGroovyApplication
@SpringBootApplication
class SpringBootGroovyApplication {
static void main(String[] args) {
SpringApplication.run SpringBootGroovyApplication, args
}
}

View File

@ -0,0 +1,48 @@
package com.baeldung.springwithgroovy.controller
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController
import com.baeldung.springwithgroovy.entity.Todo
import com.baeldung.springwithgroovy.service.TodoService
@RestController
@RequestMapping('todo')
public class TodoController {
@Autowired
TodoService todoService
@GetMapping
List<Todo> getAllTodoList(){
todoService.findAll()
}
@PostMapping
Todo saveTodo(@RequestBody Todo todo){
todoService.saveTodo todo
}
@PutMapping
Todo updateTodo(@RequestBody Todo todo){
todoService.updateTodo todo
}
@DeleteMapping('/{todoId}')
deleteTodo(@PathVariable Integer todoId){
todoService.deleteTodo todoId
}
@GetMapping('/{todoId}')
Todo getTodoById(@PathVariable Integer todoId){
todoService.findById todoId
}
}

View File

@ -0,0 +1,23 @@
package com.baeldung.springwithgroovy.entity
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table
@Entity
@Table(name = 'todo')
class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id
@Column
String task
@Column
Boolean isCompleted
}

View File

@ -0,0 +1,9 @@
package com.baeldung.springwithgroovy.repository
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import com.baeldung.springwithgroovy.entity.Todo
@Repository
interface TodoRepository extends JpaRepository<Todo, Integer> {}

View File

@ -0,0 +1,16 @@
package com.baeldung.springwithgroovy.service
import com.baeldung.springwithgroovy.entity.Todo
interface TodoService {
List<Todo> findAll()
Todo findById(Integer todoId)
Todo saveTodo(Todo todo)
Todo updateTodo(Todo todo)
Todo deleteTodo(Integer todoId)
}

View File

@ -0,0 +1,40 @@
package com.baeldung.springwithgroovy.service.impl
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import com.baeldung.springwithgroovy.entity.Todo
import com.baeldung.springwithgroovy.repository.TodoRepository
import com.baeldung.springwithgroovy.service.TodoService
@Service
class TodoServiceImpl implements TodoService {
@Autowired
TodoRepository todoRepository
@Override
List<Todo> findAll() {
todoRepository.findAll()
}
@Override
Todo findById(Integer todoId) {
todoRepository.findById todoId get()
}
@Override
Todo saveTodo(Todo todo){
todoRepository.save todo
}
@Override
Todo updateTodo(Todo todo){
todoRepository.save todo
}
@Override
Todo deleteTodo(Integer todoId){
todoRepository.deleteById todoId
}
}

View File

@ -0,0 +1,97 @@
package com.baeldung.springwithgroovy
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.test.context.event.annotation.BeforeTestClass
import org.springframework.test.context.junit4.SpringRunner
import com.baeldung.springwithgroovy.entity.Todo
import io.restassured.RestAssured
import io.restassured.response.Response
class TodoAppUnitTest {
static API_ROOT = 'http://localhost:8081/todo'
static readingTodoId
static writingTodoId
@BeforeClass
static void populateDummyData() {
Todo readingTodo = new Todo(task: 'Reading', isCompleted: false)
Todo writingTodo = new Todo(task: 'Writing', isCompleted: false)
final Response readingResponse =
RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(readingTodo).post(API_ROOT)
Todo cookingTodoResponse = readingResponse.as Todo.class
readingTodoId = cookingTodoResponse.getId()
final Response writingResponse =
RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(writingTodo).post(API_ROOT)
Todo writingTodoResponse = writingResponse.as Todo.class
writingTodoId = writingTodoResponse.getId()
}
@Test
void whenGetAllTodoList_thenOk(){
final Response response = RestAssured.get(API_ROOT)
assertEquals HttpStatus.OK.value(),response.getStatusCode()
assertTrue response.as(List.class).size() > 0
}
@Test
void whenGetTodoById_thenOk(){
final Response response =
RestAssured.get("$API_ROOT/$readingTodoId")
assertEquals HttpStatus.OK.value(),response.getStatusCode()
Todo todoResponse = response.as Todo.class
assertEquals readingTodoId,todoResponse.getId()
}
@Test
void whenUpdateTodoById_thenOk(){
Todo todo = new Todo(id:readingTodoId, isCompleted: true)
final Response response =
RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(todo).put(API_ROOT)
assertEquals HttpStatus.OK.value(),response.getStatusCode()
Todo todoResponse = response.as Todo.class
assertTrue todoResponse.getIsCompleted()
}
@Test
void whenDeleteTodoById_thenOk(){
final Response response =
RestAssured.given()
.delete("$API_ROOT/$writingTodoId")
assertEquals HttpStatus.OK.value(),response.getStatusCode()
}
@Test
void whenSaveTodo_thenOk(){
Todo todo = new Todo(task: 'Blogging', isCompleted: false)
final Response response =
RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(todo).post(API_ROOT)
assertEquals HttpStatus.OK.value(),response.getStatusCode()
}
}

View File

@ -8,9 +8,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
<dependencies>

View File

@ -8,9 +8,9 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-1</artifactId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
<relativePath>../../../parent-boot-2</relativePath>
</parent>
</project>

7
spring-core-4/README.md Normal file
View File

@ -0,0 +1,7 @@
## Spring Core
This module contains articles about core Spring functionality
## Relevant Articles:
- More articles: [[<-- prev]](/spring-core-3)

63
spring-core-4/pom.xml Normal file
View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-core-4</artifactId>
<name>spring-core-4</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-spring-5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../parent-spring-5</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
</plugins>
</build>
<properties>
<maven.surefire.version>2.22.1</maven.surefire.version>
<annotation-api.version>1.3.2</annotation-api.version>
<spring.boot.version>2.2.2.RELEASE</spring.boot.version>
</properties>
</project>

View File

@ -0,0 +1,18 @@
package com.baeldung.factorymethod;
public class Bar {
private String name;
public Bar(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,5 @@
package com.baeldung.factorymethod;
public class Foo {
}

View File

@ -0,0 +1,8 @@
package com.baeldung.factorymethod;
public class InstanceBarFactory {
public Bar createInstance(String name) {
return new Bar(name);
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.factorymethod;
public class InstanceFooFactory {
public Foo createInstance() {
return new Foo();
}
}

View File

@ -0,0 +1,11 @@
package com.baeldung.factorymethod;
public class SingletonBarFactory {
private static final Bar INSTANCE = new Bar("unnamed");
public static Bar createInstance(String name) {
INSTANCE.setName(name);
return INSTANCE;
}
}

View File

@ -0,0 +1,10 @@
package com.baeldung.factorymethod;
public class SingletonFooFactory {
private static final Foo INSTANCE = new Foo();
public static Foo createInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.factorymethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/instance-bar-config.xml")
public class InstanceBarFactoryIntegrationTest {
@Autowired
private Bar instance;
@Test
public void givenValidInstanceFactoryConfig_whenCreateInstance_thenNameIsCorrect() {
assertNotNull(instance);
assertEquals("someName", instance.getName());
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.factorymethod;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/instance-foo-config.xml")
public class InstanceFooFactoryIntegrationTest {
@Autowired
private Foo foo;
@Test
public void givenValidInstanceFactoryConfig_whenCreateFooInstance_thenInstanceIsNotNull() {
assertNotNull(foo);
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.factorymethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/static-bar-config.xml")
public class SingletonBarFactoryIntegrationTest {
@Autowired
private Bar instance;
@Test
public void givenValidStaticFactoryConfig_whenCreateInstance_thenNameIsCorrect() {
assertNotNull(instance);
assertEquals("someName", instance.getName());
}
}

View File

@ -0,0 +1,22 @@
package com.baeldung.factorymethod;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/factorymethod/static-foo-config.xml")
public class SingletonFooFactoryIntegrationTest {
@Autowired
private Foo singleton;
@Test
public void givenValidStaticFactoryConfig_whenCreateInstance_thenInstanceIsNotNull() {
assertNotNull(singleton);
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="instanceBarFactory"
class="com.baeldung.factorymethod.InstanceBarFactory" />
<bean id="bar"
factory-bean="instanceBarFactory"
factory-method="createInstance">
<constructor-arg value="someName" />
</bean>
</beans>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="instanceFooFactory"
class="com.baeldung.factorymethod.InstanceFooFactory" />
<bean id="foo"
factory-bean="instanceFooFactory"
factory-method="createInstance" />
</beans>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="bar"
class="com.baeldung.factorymethod.SingletonBarFactory"
factory-method="createInstance">
<constructor-arg value="someName" />
</bean>
</beans>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="foo"
class="com.baeldung.factorymethod.SingletonFooFactory"
factory-method="createInstance" />
</beans>

View File

@ -1,8 +1,15 @@
package com.baeldung.app.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -10,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import com.baeldung.app.entity.Task;
import com.baeldung.app.service.TaskService;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("api/tasks")
public class TaskController {
@ -17,6 +26,9 @@ public class TaskController {
@Autowired
private TaskService taskService;
@Autowired(required = false)
private UserDetailsService userDetailsService;
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Iterable<Task>> findAllTasks() {
Iterable<Task> tasks = taskService.findAll();
@ -30,4 +42,62 @@ public class TaskController {
return ResponseEntity.ok().body(tasks);
}
/**
* Example of restricting specific endpoints to specific roles using @PreAuthorize.
*/
@GetMapping("/manager")
@PreAuthorize("hasRole('ROLE_MANAGER')")
public ResponseEntity<Iterable<Task>> getAlManagerTasks() {
Iterable<Task> tasks = taskService.findAll();
return ResponseEntity.ok().body(tasks);
}
/**
* Example of restricting specific endpoints to specific roles using SecurityContext.
*/
@GetMapping("/actuator")
public ResponseEntity<Iterable<Task>> getAlActuatorTasks() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ACTUATOR")))
{
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
Iterable<Task> tasks = taskService.findAll();
return ResponseEntity.ok().body(tasks);
}
/**
* Example of restricting specific endpoints to specific roles using UserDetailsService.
*/
@GetMapping("/admin")
public ResponseEntity<Iterable<Task>> getAlAdminTasks() {
if(userDetailsService != null) {
UserDetails details = userDetailsService.loadUserByUsername("pam");
if (details != null && details.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ADMIN"))) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
}
Iterable<Task> tasks = taskService.findAll();
return ResponseEntity.ok().body(tasks);
}
/**
* Example of restricting specific endpoints to specific roles using HttpServletRequest.
*/
@GetMapping("/admin2")
public ResponseEntity<Iterable<Task>> getAlAdminTasksUsingServlet(HttpServletRequest request) {
if (!request.isUserInRole("ROLE_ADMIN")) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
Iterable<Task> tasks = taskService.findAll();
return ResponseEntity.ok().body(tasks);
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.customlogouthandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomLogoutApplication {
public static void main(String[] args) {
SpringApplication.run(CustomLogoutApplication.class, args);
}
}

View File

@ -0,0 +1,55 @@
package com.baeldung.customlogouthandler;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
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.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import com.baeldung.customlogouthandler.web.CustomLogoutHandler;
@Configuration
@EnableWebSecurity
public class MvcConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private CustomLogoutHandler logoutHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/user/**")
.hasRole("USER")
.and()
.logout()
.logoutUrl("/user/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
.permitAll()
.and()
.csrf()
.disable()
.formLogin()
.disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select login, password, true from users where login=?")
.authoritiesByUsernameQuery("select login, role from users where login=?");
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.customlogouthandler.services;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Service;
import com.baeldung.customlogouthandler.user.User;
@Service
public class UserCache {
@PersistenceContext
private EntityManager entityManager;
private final ConcurrentMap<String, User> store = new ConcurrentHashMap<>(256);
public User getByUserName(String userName) {
return store.computeIfAbsent(userName, k -> entityManager.createQuery("from User where login=:login", User.class)
.setParameter("login", k)
.getSingleResult());
}
public void evictUser(String userName) {
store.remove(userName);
}
public int size() {
return store.size();
}
}

View File

@ -0,0 +1,61 @@
package com.baeldung.customlogouthandler.user;
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(unique = true)
private String login;
private String password;
private String role;
private String language;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.customlogouthandler.user;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class UserUtils {
public static String getAuthenticatedUserName() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
return auth != null ? ((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername() : null;
}
}

View File

@ -0,0 +1,28 @@
package com.baeldung.customlogouthandler.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service;
import com.baeldung.customlogouthandler.services.UserCache;
import com.baeldung.customlogouthandler.user.UserUtils;
@Service
public class CustomLogoutHandler implements LogoutHandler {
private final UserCache userCache;
public CustomLogoutHandler(UserCache userCache) {
this.userCache = userCache;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String userName = UserUtils.getAuthenticatedUserName();
userCache.evictUser(userName);
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.customlogouthandler.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.baeldung.customlogouthandler.services.UserCache;
import com.baeldung.customlogouthandler.user.User;
import com.baeldung.customlogouthandler.user.UserUtils;
@Controller
@RequestMapping(path = "/user")
public class UserController {
private final UserCache userCache;
public UserController(UserCache userCache) {
this.userCache = userCache;
}
@GetMapping(path = "/language")
@ResponseBody
public String getLanguage() {
String userName = UserUtils.getAuthenticatedUserName();
User user = userCache.getByUserName(userName);
return user.getLanguage();
}
}

View File

@ -0,0 +1,5 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=test
spring.datasource.password=test
spring.jpa.hibernate.ddl-auto=create

View File

@ -0,0 +1,108 @@
package com.baeldung.customlogouthandler;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.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.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.baeldung.customlogouthandler.services.UserCache;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { CustomLogoutApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SqlGroup({ @Sql(value = "classpath:customlogouthandler/before.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD), @Sql(value = "classpath:customlogouthandler/after.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) })
@TestPropertySource(locations="classpath:customlogouthandler/application.properties")
class CustomLogoutHandlerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserCache userCache;
@LocalServerPort
private int port;
@Test
public void whenLogin_thenUseUserCache() {
// User cache should be empty on start
assertThat(userCache.size()).isEqualTo(0);
// Request using first login
ResponseEntity<String> response = restTemplate.withBasicAuth("user", "pass")
.getForEntity(getLanguageUrl(), String.class);
assertThat(response.getBody()).contains("english");
// User cache must contain the user
assertThat(userCache.size()).isEqualTo(1);
// Getting the session cookie
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", response.getHeaders()
.getFirst(HttpHeaders.SET_COOKIE));
// Request with the session cookie
response = restTemplate.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getBody()).contains("english");
// Logging out using the session cookies
response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode()
.value()).isEqualTo(200);
}
@Test
public void whenLogout_thenCacheIsEmpty() {
// User cache should be empty on start
assertThat(userCache.size()).isEqualTo(0);
// Request using first login
ResponseEntity<String> response = restTemplate.withBasicAuth("user", "pass")
.getForEntity(getLanguageUrl(), String.class);
assertThat(response.getBody()).contains("english");
// User cache must contain the user
assertThat(userCache.size()).isEqualTo(1);
// Getting the session cookie
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", response.getHeaders()
.getFirst(HttpHeaders.SET_COOKIE));
// Logging out using the session cookies
response = restTemplate.exchange(getLogoutUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode()
.value()).isEqualTo(200);
// User cache must be empty now
// this is the reaction on custom logout filter execution
assertThat(userCache.size()).isEqualTo(0);
// Assert unauthorized request
response = restTemplate.exchange(getLanguageUrl(), HttpMethod.GET, new HttpEntity<String>(requestHeaders), String.class);
assertThat(response.getStatusCode()
.value()).isEqualTo(401);
}
private String getLanguageUrl() {
return "http://localhost:" + port + "/user/language";
}
private String getLogoutUrl() {
return "http://localhost:" + port + "/user/logout";
}
}

View File

@ -0,0 +1,5 @@
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=test
spring.datasource.password=test
spring.jpa.hibernate.ddl-auto=create

View File

@ -0,0 +1 @@
insert into users (login, password, role, language) values ('user', '{noop}pass', 'ROLE_USER', 'english');