commit
6c0a91ef6e
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
customMean <- function(vector) {
|
||||
mean(vector)
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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> {}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
## Spring Core
|
||||
|
||||
This module contains articles about core Spring functionality
|
||||
|
||||
## Relevant Articles:
|
||||
|
||||
- More articles: [[<-- prev]](/spring-core-3)
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.baeldung.factorymethod;
|
||||
|
||||
public class Foo {
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.baeldung.factorymethod;
|
||||
|
||||
public class InstanceBarFactory {
|
||||
|
||||
public Bar createInstance(String name) {
|
||||
return new Bar(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.baeldung.factorymethod;
|
||||
|
||||
public class InstanceFooFactory {
|
||||
|
||||
public Foo createInstance() {
|
||||
return new Foo();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.factorymethod;
|
||||
|
||||
public class SingletonFooFactory {
|
||||
|
||||
private static final Foo INSTANCE = new Foo();
|
||||
|
||||
public static Foo createInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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=?");
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
delete from users;
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
insert into users (login, password, role, language) values ('user', '{noop}pass', 'ROLE_USER', 'english');
|
Loading…
Reference in New Issue