Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
93434fdac4
@ -0,0 +1,52 @@
|
|||||||
|
package com.baeldung.algorithms.maze.solver;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BFSMazeSolver {
|
||||||
|
private static final int[][] DIRECTIONS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
|
||||||
|
|
||||||
|
public List<Coordinate> solve(Maze maze) {
|
||||||
|
LinkedList<Coordinate> nextToVisit = new LinkedList<>();
|
||||||
|
Coordinate start = maze.getEntry();
|
||||||
|
nextToVisit.add(start);
|
||||||
|
|
||||||
|
while (!nextToVisit.isEmpty()) {
|
||||||
|
Coordinate cur = nextToVisit.remove();
|
||||||
|
|
||||||
|
if (!maze.isValidLocation(cur.getX(), cur.getY()) || maze.isExplored(cur.getX(), cur.getY())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maze.isWall(cur.getX(), cur.getY())) {
|
||||||
|
maze.setVisited(cur.getX(), cur.getY(), true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maze.isExit(cur.getX(), cur.getY())) {
|
||||||
|
return backtrackPath(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int[] direction : DIRECTIONS) {
|
||||||
|
Coordinate coordinate = new Coordinate(cur.getX() + direction[0], cur.getY() + direction[1], cur);
|
||||||
|
nextToVisit.add(coordinate);
|
||||||
|
maze.setVisited(cur.getX(), cur.getY(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Coordinate> backtrackPath(Coordinate cur) {
|
||||||
|
List<Coordinate> path = new ArrayList<>();
|
||||||
|
Coordinate iter = cur;
|
||||||
|
|
||||||
|
while (iter != null) {
|
||||||
|
path.add(iter);
|
||||||
|
iter = iter.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.baeldung.algorithms.maze.solver;
|
||||||
|
|
||||||
|
public class Coordinate {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
Coordinate parent;
|
||||||
|
|
||||||
|
public Coordinate(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Coordinate(int x, int y, Coordinate parent) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coordinate getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.baeldung.algorithms.maze.solver;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DFSMazeSolver {
|
||||||
|
private static final int[][] DIRECTIONS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
|
||||||
|
|
||||||
|
public List<Coordinate> solve(Maze maze) {
|
||||||
|
List<Coordinate> path = new ArrayList<>();
|
||||||
|
if (explore(maze, maze.getEntry()
|
||||||
|
.getX(),
|
||||||
|
maze.getEntry()
|
||||||
|
.getY(),
|
||||||
|
path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean explore(Maze maze, int row, int col, List<Coordinate> path) {
|
||||||
|
if (!maze.isValidLocation(row, col) || maze.isWall(row, col) || maze.isExplored(row, col)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.add(new Coordinate(row, col));
|
||||||
|
maze.setVisited(row, col, true);
|
||||||
|
|
||||||
|
if (maze.isExit(row, col)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int[] direction : DIRECTIONS) {
|
||||||
|
Coordinate coordinate = getNextCoordinate(row, col, direction[0], direction[1]);
|
||||||
|
if (explore(maze, coordinate.getX(), coordinate.getY(), path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.remove(path.size() - 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Coordinate getNextCoordinate(int row, int col, int i, int j) {
|
||||||
|
return new Coordinate(row + i, col + j);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package com.baeldung.algorithms.maze.solver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Maze {
|
||||||
|
private static final int ROAD = 0;
|
||||||
|
private static final int WALL = 1;
|
||||||
|
private static final int START = 2;
|
||||||
|
private static final int EXIT = 3;
|
||||||
|
private static final int PATH = 4;
|
||||||
|
|
||||||
|
private int[][] maze;
|
||||||
|
private boolean[][] visited;
|
||||||
|
private Coordinate start;
|
||||||
|
private Coordinate end;
|
||||||
|
|
||||||
|
public Maze(File maze) throws FileNotFoundException {
|
||||||
|
String fileText = "";
|
||||||
|
try (Scanner input = new Scanner(maze)) {
|
||||||
|
while (input.hasNextLine()) {
|
||||||
|
fileText += input.nextLine() + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initializeMaze(fileText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMaze(String text) {
|
||||||
|
if (text == null || (text = text.trim()).length() == 0) {
|
||||||
|
throw new IllegalArgumentException("empty lines data");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] lines = text.split("[\r]?\n");
|
||||||
|
maze = new int[lines.length][lines[0].length()];
|
||||||
|
visited = new boolean[lines.length][lines[0].length()];
|
||||||
|
|
||||||
|
for (int row = 0; row < getHeight(); row++) {
|
||||||
|
if (lines[row].length() != getWidth()) {
|
||||||
|
throw new IllegalArgumentException("line " + (row + 1) + " wrong length (was " + lines[row].length() + " but should be " + getWidth() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int col = 0; col < getWidth(); col++) {
|
||||||
|
if (lines[row].charAt(col) == '#')
|
||||||
|
maze[row][col] = WALL;
|
||||||
|
else if (lines[row].charAt(col) == 'S') {
|
||||||
|
maze[row][col] = START;
|
||||||
|
start = new Coordinate(row, col);
|
||||||
|
} else if (lines[row].charAt(col) == 'E') {
|
||||||
|
maze[row][col] = EXIT;
|
||||||
|
end = new Coordinate(row, col);
|
||||||
|
} else
|
||||||
|
maze[row][col] = ROAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return maze.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return maze[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Coordinate getEntry() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Coordinate getExit() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExit(int x, int y) {
|
||||||
|
return x == end.getX() && y == end.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStart(int x, int y) {
|
||||||
|
return x == start.getX() && y == start.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExplored(int row, int col) {
|
||||||
|
return visited[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWall(int row, int col) {
|
||||||
|
return maze[row][col] == WALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisited(int row, int col, boolean value) {
|
||||||
|
visited[row][col] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidLocation(int row, int col) {
|
||||||
|
if (row < 0 || row >= getHeight() || col < 0 || col >= getWidth()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printPath(List<Coordinate> path) {
|
||||||
|
int[][] tempMaze = Arrays.stream(maze)
|
||||||
|
.map(int[]::clone)
|
||||||
|
.toArray(int[][]::new);
|
||||||
|
for (Coordinate coordinate : path) {
|
||||||
|
if (isStart(coordinate.getX(), coordinate.getY()) || isExit(coordinate.getX(), coordinate.getY())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tempMaze[coordinate.getX()][coordinate.getY()] = PATH;
|
||||||
|
}
|
||||||
|
System.out.println(toString(tempMaze));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(int[][] maze) {
|
||||||
|
StringBuilder result = new StringBuilder(getWidth() * (getHeight() + 1));
|
||||||
|
for (int row = 0; row < getHeight(); row++) {
|
||||||
|
for (int col = 0; col < getWidth(); col++) {
|
||||||
|
if (maze[row][col] == ROAD) {
|
||||||
|
result.append(' ');
|
||||||
|
} else if (maze[row][col] == WALL) {
|
||||||
|
result.append('#');
|
||||||
|
} else if (maze[row][col] == START) {
|
||||||
|
result.append('S');
|
||||||
|
} else if (maze[row][col] == EXIT) {
|
||||||
|
result.append('E');
|
||||||
|
} else {
|
||||||
|
result.append('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append('\n');
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
for (int i = 0; i < visited.length; i++)
|
||||||
|
Arrays.fill(visited[i], false);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.baeldung.algorithms.maze.solver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MazeDriver {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
File maze1 = new File("src/main/resources/maze/maze1.txt");
|
||||||
|
File maze2 = new File("src/main/resources/maze/maze2.txt");
|
||||||
|
|
||||||
|
execute(maze1);
|
||||||
|
execute(maze2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void execute(File file) throws Exception {
|
||||||
|
Maze maze = new Maze(file);
|
||||||
|
dfs(maze);
|
||||||
|
bfs(maze);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void bfs(Maze maze) {
|
||||||
|
BFSMazeSolver bfs = new BFSMazeSolver();
|
||||||
|
List<Coordinate> path = bfs.solve(maze);
|
||||||
|
maze.printPath(path);
|
||||||
|
maze.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dfs(Maze maze) {
|
||||||
|
DFSMazeSolver dfs = new DFSMazeSolver();
|
||||||
|
List<Coordinate> path = dfs.solve(maze);
|
||||||
|
maze.printPath(path);
|
||||||
|
maze.reset();
|
||||||
|
}
|
||||||
|
}
|
@ -120,10 +120,10 @@ public class DancingLinks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void printSolution(int[][] result) {
|
private static void printSolution(int[][] result) {
|
||||||
int N = result.length;
|
int size = result.length;
|
||||||
for (int[] aResult : result) {
|
for (int[] aResult : result) {
|
||||||
StringBuilder ret = new StringBuilder();
|
StringBuilder ret = new StringBuilder();
|
||||||
for (int j = 0; j < N; j++) {
|
for (int j = 0; j < size; j++) {
|
||||||
ret.append(aResult[j]).append(" ");
|
ret.append(aResult[j]).append(" ");
|
||||||
}
|
}
|
||||||
System.out.println(ret);
|
System.out.println(ret);
|
||||||
|
@ -39,70 +39,83 @@ public class DancingLinksAlgorithm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean[][] createExactCoverBoard() {
|
private boolean[][] createExactCoverBoard() {
|
||||||
boolean[][] R = new boolean[BOARD_SIZE * BOARD_SIZE * MAX_VALUE][BOARD_SIZE * BOARD_SIZE * CONSTRAINTS];
|
boolean[][] coverBoard = new boolean[BOARD_SIZE * BOARD_SIZE * MAX_VALUE][BOARD_SIZE * BOARD_SIZE * CONSTRAINTS];
|
||||||
|
|
||||||
int hBase = 0;
|
int hBase = 0;
|
||||||
|
hBase = checkCellConstraint(coverBoard, hBase);
|
||||||
|
hBase = checkRowConstraint(coverBoard, hBase);
|
||||||
|
hBase = checkColumnConstraint(coverBoard, hBase);
|
||||||
|
checkSubsectionConstraint(coverBoard, hBase);
|
||||||
|
|
||||||
// Cell constraint.
|
return coverBoard;
|
||||||
for (int r = COVER_START_INDEX; r <= BOARD_SIZE; r++) {
|
|
||||||
for (int c = COVER_START_INDEX; c <= BOARD_SIZE; c++, hBase++) {
|
|
||||||
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++) {
|
|
||||||
int index = getIndex(r, c, n);
|
|
||||||
R[index][hBase] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Row constrain.
|
private int checkSubsectionConstraint(boolean[][] coverBoard, int hBase) {
|
||||||
for (int r = COVER_START_INDEX; r <= BOARD_SIZE; r++) {
|
|
||||||
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
|
||||||
for (int c1 = COVER_START_INDEX; c1 <= BOARD_SIZE; c1++) {
|
|
||||||
int index = getIndex(r, c1, n);
|
|
||||||
R[index][hBase] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column constraint.
|
|
||||||
for (int c = COVER_START_INDEX; c <= BOARD_SIZE; c++) {
|
|
||||||
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
|
||||||
for (int r1 = COVER_START_INDEX; r1 <= BOARD_SIZE; r1++) {
|
|
||||||
int index = getIndex(r1, c, n);
|
|
||||||
R[index][hBase] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subsection constraint
|
|
||||||
for (int br = COVER_START_INDEX; br <= BOARD_SIZE; br += SUBSECTION_SIZE) {
|
for (int br = COVER_START_INDEX; br <= BOARD_SIZE; br += SUBSECTION_SIZE) {
|
||||||
for (int bc = COVER_START_INDEX; bc <= BOARD_SIZE; bc += SUBSECTION_SIZE) {
|
for (int bc = COVER_START_INDEX; bc <= BOARD_SIZE; bc += SUBSECTION_SIZE) {
|
||||||
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
||||||
for (int rDelta = 0; rDelta < SUBSECTION_SIZE; rDelta++) {
|
for (int rDelta = 0; rDelta < SUBSECTION_SIZE; rDelta++) {
|
||||||
for (int cDelta = 0; cDelta < SUBSECTION_SIZE; cDelta++) {
|
for (int cDelta = 0; cDelta < SUBSECTION_SIZE; cDelta++) {
|
||||||
int index = getIndex(br + rDelta, bc + cDelta, n);
|
int index = getIndex(br + rDelta, bc + cDelta, n);
|
||||||
R[index][hBase] = true;
|
coverBoard[index][hBase] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return R;
|
return hBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkColumnConstraint(boolean[][] coverBoard, int hBase) {
|
||||||
|
for (int c = COVER_START_INDEX; c <= BOARD_SIZE; c++) {
|
||||||
|
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
||||||
|
for (int r1 = COVER_START_INDEX; r1 <= BOARD_SIZE; r1++) {
|
||||||
|
int index = getIndex(r1, c, n);
|
||||||
|
coverBoard[index][hBase] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkRowConstraint(boolean[][] coverBoard, int hBase) {
|
||||||
|
for (int r = COVER_START_INDEX; r <= BOARD_SIZE; r++) {
|
||||||
|
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++, hBase++) {
|
||||||
|
for (int c1 = COVER_START_INDEX; c1 <= BOARD_SIZE; c1++) {
|
||||||
|
int index = getIndex(r, c1, n);
|
||||||
|
coverBoard[index][hBase] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkCellConstraint(boolean[][] coverBoard, int hBase) {
|
||||||
|
for (int r = COVER_START_INDEX; r <= BOARD_SIZE; r++) {
|
||||||
|
for (int c = COVER_START_INDEX; c <= BOARD_SIZE; c++, hBase++) {
|
||||||
|
for (int n = COVER_START_INDEX; n <= BOARD_SIZE; n++) {
|
||||||
|
int index = getIndex(r, c, n);
|
||||||
|
coverBoard[index][hBase] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean[][] initializeExactCoverBoard(int[][] board) {
|
private boolean[][] initializeExactCoverBoard(int[][] board) {
|
||||||
boolean[][] R = createExactCoverBoard();
|
boolean[][] coverBoard = createExactCoverBoard();
|
||||||
for (int i = COVER_START_INDEX; i <= BOARD_SIZE; i++) {
|
for (int i = COVER_START_INDEX; i <= BOARD_SIZE; i++) {
|
||||||
for (int j = COVER_START_INDEX; j <= BOARD_SIZE; j++) {
|
for (int j = COVER_START_INDEX; j <= BOARD_SIZE; j++) {
|
||||||
int n = board[i - 1][j - 1];
|
int n = board[i - 1][j - 1];
|
||||||
if (n != NO_VALUE) {
|
if (n != NO_VALUE) {
|
||||||
for (int num = MIN_VALUE; num <= MAX_VALUE; num++) {
|
for (int num = MIN_VALUE; num <= MAX_VALUE; num++) {
|
||||||
if (num != n) {
|
if (num != n) {
|
||||||
Arrays.fill(R[getIndex(i, j, num)], false);
|
Arrays.fill(coverBoard[getIndex(i, j, num)], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return R;
|
return coverBoard;
|
||||||
}
|
}
|
||||||
}
|
}
|
12
algorithms/src/main/resources/maze/maze1.txt
Normal file
12
algorithms/src/main/resources/maze/maze1.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
S ########
|
||||||
|
# #
|
||||||
|
# ### ## #
|
||||||
|
# # # #
|
||||||
|
# # # # #
|
||||||
|
# ## #####
|
||||||
|
# # #
|
||||||
|
# # # # #
|
||||||
|
##### ####
|
||||||
|
# # E
|
||||||
|
# # # #
|
||||||
|
##########
|
22
algorithms/src/main/resources/maze/maze2.txt
Normal file
22
algorithms/src/main/resources/maze/maze2.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
S ##########################
|
||||||
|
# # # #
|
||||||
|
# # #### ############### #
|
||||||
|
# # # # # #
|
||||||
|
# # #### # # ###############
|
||||||
|
# # # # # # #
|
||||||
|
# # # #### ### ########### #
|
||||||
|
# # # # # #
|
||||||
|
# ################## #
|
||||||
|
######### # # # # #
|
||||||
|
# # #### # ####### # #
|
||||||
|
# # ### ### # # # # #
|
||||||
|
# # ## # ##### # #
|
||||||
|
##### ####### # # # # #
|
||||||
|
# # ## ## #### # #
|
||||||
|
# ##### ####### # #
|
||||||
|
# # ############
|
||||||
|
####### ######### # #
|
||||||
|
# # ######## #
|
||||||
|
# ####### ###### ## # E
|
||||||
|
# # # ## #
|
||||||
|
############################
|
@ -6,10 +6,11 @@
|
|||||||
<version>0.1-SNAPSHOT</version>
|
<version>0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<validation-api.version>2.0.0.Final</validation-api.version>
|
<validation-api.version>2.0.1.Final</validation-api.version>
|
||||||
<hibernate-validator.version>6.0.2.Final</hibernate-validator.version>
|
<hibernate-validator.version>6.0.7.Final</hibernate-validator.version>
|
||||||
<javax.el-api.version>3.0.0</javax.el-api.version>
|
<javax.el-api.version>3.0.0</javax.el-api.version>
|
||||||
<javax.el.version>2.2.6</javax.el.version>
|
<javax.el.version>2.2.6</javax.el.version>
|
||||||
|
<org.springframework.version>5.0.2.RELEASE</org.springframework.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
@ -21,12 +22,6 @@
|
|||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.validation</groupId>
|
|
||||||
<artifactId>validation-api</artifactId>
|
|
||||||
<version>${validation-api.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate</groupId>
|
<groupId>org.hibernate</groupId>
|
||||||
<artifactId>hibernate-validator</artifactId>
|
<artifactId>hibernate-validator</artifactId>
|
||||||
@ -50,6 +45,16 @@
|
|||||||
<artifactId>javax.el</artifactId>
|
<artifactId>javax.el</artifactId>
|
||||||
<version>${javax.el.version}</version>
|
<version>${javax.el.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
<version>${org.springframework.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-test</artifactId>
|
||||||
|
<version>${org.springframework.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Customer;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Reservation;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" })
|
||||||
|
public class MethodValidationConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MethodValidationPostProcessor methodValidationPostProcessor() {
|
||||||
|
return new MethodValidationPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("customer")
|
||||||
|
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||||
|
public Customer customer(String firstName, String lastName) {
|
||||||
|
|
||||||
|
Customer customer = new Customer(firstName, lastName);
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("reservation")
|
||||||
|
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||||
|
public Reservation reservation(LocalDate begin, LocalDate end, Customer customer, int room) {
|
||||||
|
|
||||||
|
Reservation reservation = new Reservation(begin, end, customer, room);
|
||||||
|
return reservation;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.constraints;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
import javax.validation.constraintvalidation.SupportedValidationTarget;
|
||||||
|
import javax.validation.constraintvalidation.ValidationTarget;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
|
||||||
|
public class ConsistentDateParameterValidator implements ConstraintValidator<ConsistentDateParameters, Object[]> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
|
||||||
|
|
||||||
|
if (value[0] == null || value[1] == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(value[0] instanceof LocalDate) || !(value[1] instanceof LocalDate)) {
|
||||||
|
throw new IllegalArgumentException("Illegal method signature, expected two parameters of type LocalDate.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((LocalDate) value[0]).isAfter(LocalDate.now()) && ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.constraints;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Constraint(validatedBy = ConsistentDateParameterValidator.class)
|
||||||
|
@Target({ METHOD, CONSTRUCTOR })
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface ConsistentDateParameters {
|
||||||
|
|
||||||
|
String message() default "End date must be after begin date and both must be in the future";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.constraints;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Constraint(validatedBy = ValidReservationValidator.class)
|
||||||
|
@Target({ METHOD, CONSTRUCTOR })
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface ValidReservation {
|
||||||
|
|
||||||
|
String message() default "End date must be after begin date and both must be in the future, room number must be bigger than 0";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.constraints;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Reservation;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class ValidReservationValidator implements ConstraintValidator<ValidReservation, Reservation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Reservation reservation, ConstraintValidatorContext context) {
|
||||||
|
|
||||||
|
if (reservation == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(reservation instanceof Reservation)) {
|
||||||
|
throw new IllegalArgumentException("Illegal method signature, expected parameter of type Reservation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reservation.getBegin() == null || reservation.getEnd() == null || reservation.getCustomer() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (reservation.getBegin()
|
||||||
|
.isAfter(LocalDate.now())
|
||||||
|
&& reservation.getBegin()
|
||||||
|
.isBefore(reservation.getEnd())
|
||||||
|
&& reservation.getRoom() > 0);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.model;
|
||||||
|
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
public class Customer {
|
||||||
|
|
||||||
|
@Size(min = 5, max = 200)
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@Size(min = 5, max = 200)
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
public Customer(@Size(min = 5, max = 200) @NotNull String firstName, @Size(min = 5, max = 200) @NotNull String lastName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.model;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.constraints.ConsistentDateParameters;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.constraints.ValidReservation;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.Positive;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
public class Reservation {
|
||||||
|
|
||||||
|
private LocalDate begin;
|
||||||
|
|
||||||
|
private LocalDate end;
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
private Customer customer;
|
||||||
|
|
||||||
|
@Positive
|
||||||
|
private int room;
|
||||||
|
|
||||||
|
@ConsistentDateParameters
|
||||||
|
@ValidReservation
|
||||||
|
public Reservation(LocalDate begin, LocalDate end, Customer customer, int room) {
|
||||||
|
this.begin = begin;
|
||||||
|
this.end = end;
|
||||||
|
this.customer = customer;
|
||||||
|
this.room = room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getBegin() {
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBegin(LocalDate begin) {
|
||||||
|
this.begin = begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnd(LocalDate end) {
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer getCustomer() {
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomer(Customer customer) {
|
||||||
|
this.customer = customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoom() {
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoom(int room) {
|
||||||
|
this.room = room;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation.model;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.constraints.ConsistentDateParameters;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.constraints.ValidReservation;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@Validated
|
||||||
|
public class ReservationManagement {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@ConsistentDateParameters
|
||||||
|
public void createReservation(LocalDate begin, LocalDate end, @NotNull Customer customer) {
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createReservation(@NotNull @Future LocalDate begin, @Min(1) int duration, @NotNull Customer customer) {
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createReservation(@Valid Reservation reservation) {
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(min = 1)
|
||||||
|
public List<@NotNull Customer> getAllCustomers() {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
public Reservation getReservationById(int id) {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Customer;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Reservation;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.ReservationManagement;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = { MethodValidationConfig.class }, loader = AnnotationConfigContextLoader.class)
|
||||||
|
public class ContainerValidationIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ReservationManagement reservationManagement;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidMethodParameters_thenConstraintViolationException() {
|
||||||
|
|
||||||
|
exception.expect(ConstraintViolationException.class);
|
||||||
|
reservationManagement.createReservation(LocalDate.now(), 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidMethodParameters_thenNoException() {
|
||||||
|
|
||||||
|
reservationManagement.createReservation(LocalDate.now()
|
||||||
|
.plusDays(1), 1, new Customer("William", "Smith"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithInvalidParameters_thenConstraintViolationException() {
|
||||||
|
|
||||||
|
exception.expect(ConstraintViolationException.class);
|
||||||
|
reservationManagement.createReservation(LocalDate.now(), LocalDate.now(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithValidParameters_thenNoException() {
|
||||||
|
|
||||||
|
reservationManagement.createReservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
new Customer("William", "Smith"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidReturnValue_thenConstraintViolationException() {
|
||||||
|
|
||||||
|
exception.expect(ConstraintViolationException.class);
|
||||||
|
List<Customer> list = reservationManagement.getAllCustomers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidCascadedValue_thenConstraintViolationException() {
|
||||||
|
|
||||||
|
Customer customer = new Customer();
|
||||||
|
customer.setFirstName("John");
|
||||||
|
customer.setLastName("Doe");
|
||||||
|
Reservation reservation = new Reservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
customer, 1);
|
||||||
|
|
||||||
|
exception.expect(ConstraintViolationException.class);
|
||||||
|
reservationManagement.createReservation(reservation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidCascadedValue_thenCNoException() {
|
||||||
|
|
||||||
|
Customer customer = new Customer();
|
||||||
|
customer.setFirstName("William");
|
||||||
|
customer.setLastName("Smith");
|
||||||
|
Reservation reservation = new Reservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
customer, 1);
|
||||||
|
|
||||||
|
reservationManagement.createReservation(reservation);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
package org.baeldung.javaxval.methodvalidation;
|
||||||
|
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Customer;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.Reservation;
|
||||||
|
import org.baeldung.javaxval.methodvalidation.model.ReservationManagement;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.Validation;
|
||||||
|
import javax.validation.ValidatorFactory;
|
||||||
|
import javax.validation.executable.ExecutableValidator;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ValidationIntegrationTest {
|
||||||
|
|
||||||
|
private ExecutableValidator executableValidator;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void getExecutableValidator() {
|
||||||
|
|
||||||
|
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||||
|
this.executableValidator = factory.getValidator()
|
||||||
|
.forExecutables();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidMethodParameters_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now(), 0, null };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(3, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidMethodParameters_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now()
|
||||||
|
.plusDays(1), 1, new Customer("John", "Doe") };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithInvalidParameters_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", LocalDate.class, LocalDate.class, Customer.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now(), LocalDate.now(), new Customer("John", "Doe") };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(1, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithValidParameters_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", LocalDate.class, LocalDate.class, Customer.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
new Customer("John", "Doe") };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidConstructorParameters_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Customer> constructor = Customer.class.getConstructor(String.class, String.class);
|
||||||
|
Object[] parameterValues = { "John", "Doe" };
|
||||||
|
Set<ConstraintViolation<Customer>> violations = executableValidator.validateConstructorParameters(constructor, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(2, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidConstructorParameters_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Customer> constructor = Customer.class.getConstructor(String.class, String.class);
|
||||||
|
Object[] parameterValues = { "William", "Smith" };
|
||||||
|
Set<ConstraintViolation<Customer>> violations = executableValidator.validateConstructorParameters(constructor, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithInvalidConstructorParameters_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Reservation> constructor = Reservation.class.getConstructor(LocalDate.class, LocalDate.class, Customer.class, int.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now(), LocalDate.now(), new Customer("William", "Smith"), 1 };
|
||||||
|
Set<ConstraintViolation<Reservation>> violations = executableValidator.validateConstructorParameters(constructor, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(1, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenCrossParameterValidationWithValidConstructorParameters_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Reservation> constructor = Reservation.class.getConstructor(LocalDate.class, LocalDate.class, Customer.class, int.class);
|
||||||
|
Object[] parameterValues = { LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
new Customer("William", "Smith"), 1 };
|
||||||
|
Set<ConstraintViolation<Reservation>> violations = executableValidator.validateConstructorParameters(constructor, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidReturnValue_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("getAllCustomers");
|
||||||
|
Object returnValue = Collections.<Customer> emptyList();
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateReturnValue(object, method, returnValue);
|
||||||
|
|
||||||
|
assertEquals(1, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidReturnValue_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("getAllCustomers");
|
||||||
|
Object returnValue = Collections.singletonList(new Customer("William", "Smith"));
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateReturnValue(object, method, returnValue);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidConstructorReturnValue_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Reservation> constructor = Reservation.class.getConstructor(LocalDate.class, LocalDate.class, Customer.class, int.class);
|
||||||
|
Reservation createdObject = new Reservation(LocalDate.now(), LocalDate.now(), new Customer("William", "Smith"), 0);
|
||||||
|
Set<ConstraintViolation<Reservation>> violations = executableValidator.validateConstructorReturnValue(constructor, createdObject);
|
||||||
|
|
||||||
|
assertEquals(1, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidConstructorReturnValue_thenZeroVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
Constructor<Reservation> constructor = Reservation.class.getConstructor(LocalDate.class, LocalDate.class, Customer.class, int.class);
|
||||||
|
Reservation createdObject = new Reservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
new Customer("William", "Smith"), 1);
|
||||||
|
Set<ConstraintViolation<Reservation>> violations = executableValidator.validateConstructorReturnValue(constructor, createdObject);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithInvalidCascadedValue_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", Reservation.class);
|
||||||
|
Customer customer = new Customer();
|
||||||
|
customer.setFirstName("John");
|
||||||
|
customer.setLastName("Doe");
|
||||||
|
Reservation reservation = new Reservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
customer, 1);
|
||||||
|
Object[] parameterValues = { reservation };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(2, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenValidationWithValidCascadedValue_thenCorrectNumberOfVoilations() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
ReservationManagement object = new ReservationManagement();
|
||||||
|
Method method = ReservationManagement.class.getMethod("createReservation", Reservation.class);
|
||||||
|
Customer customer = new Customer();
|
||||||
|
customer.setFirstName("William");
|
||||||
|
customer.setLastName("Smith");
|
||||||
|
Reservation reservation = new Reservation(LocalDate.now()
|
||||||
|
.plusDays(1),
|
||||||
|
LocalDate.now()
|
||||||
|
.plusDays(2),
|
||||||
|
customer, 1);
|
||||||
|
Object[] parameterValues = { reservation };
|
||||||
|
Set<ConstraintViolation<ReservationManagement>> violations = executableValidator.validateParameters(object, method, parameterValues);
|
||||||
|
|
||||||
|
assertEquals(0, violations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -100,29 +100,24 @@ public class JGroupsMessenger extends ReceiverAdapter {
|
|||||||
System.out.println("Received initial view:");
|
System.out.println("Received initial view:");
|
||||||
newView.forEach(System.out::println);
|
newView.forEach(System.out::println);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Compare to last view
|
// Compare to last view
|
||||||
System.out.println("Received new view.");
|
System.out.println("Received new view.");
|
||||||
|
|
||||||
List<Address> newMembers = View.newMembers(lastView, newView);
|
List<Address> newMembers = View.newMembers(lastView, newView);
|
||||||
if (newMembers.size() > 0) {
|
|
||||||
System.out.println("New members: ");
|
System.out.println("New members: ");
|
||||||
newMembers.forEach(System.out::println);
|
newMembers.forEach(System.out::println);
|
||||||
}
|
|
||||||
|
|
||||||
List<Address> exMembers = View.leftMembers(lastView, newView);
|
List<Address> exMembers = View.leftMembers(lastView, newView);
|
||||||
if (exMembers.size() > 0) {
|
|
||||||
System.out.println("Exited members:");
|
System.out.println("Exited members:");
|
||||||
exMembers.forEach(System.out::println);
|
exMembers.forEach(System.out::println);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
lastView = newView;
|
lastView = newView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loop on console input until we see 'x' to exit
|
* Loop on console input until we see 'x' to exit
|
||||||
*/
|
*/
|
||||||
private void processInput() {
|
private void processInput() throws Exception {
|
||||||
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
|
||||||
while (running) {
|
while (running) {
|
||||||
@ -138,13 +133,8 @@ public class JGroupsMessenger extends ReceiverAdapter {
|
|||||||
running = false;
|
running = false;
|
||||||
continue;
|
continue;
|
||||||
} else if (!destinationName.isEmpty()) {
|
} else if (!destinationName.isEmpty()) {
|
||||||
Optional<Address> optDestination = getAddress(destinationName);
|
destination = getAddress(destinationName)
|
||||||
if (optDestination.isPresent()) {
|
. orElseThrow(() -> new Exception("Destination not found"));
|
||||||
destination = optDestination.get();
|
|
||||||
} else {
|
|
||||||
System.out.println("Destination not found, try again.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept a string to send
|
// Accept a string to send
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
- [Intro to JDO Queries 2/2](http://www.baeldung.com/jdo-queries)
|
- [Intro to JDO Queries 2/2](http://www.baeldung.com/jdo-queries)
|
||||||
- [Guide to google-http-client](http://www.baeldung.com/google-http-client)
|
- [Guide to google-http-client](http://www.baeldung.com/google-http-client)
|
||||||
- [Interact with Google Sheets from Java](http://www.baeldung.com/google-sheets-java-client)
|
- [Interact with Google Sheets from Java](http://www.baeldung.com/google-sheets-java-client)
|
||||||
|
- [Programatically Create, Configure, and Run a Tomcat Server] (http://www.baeldung.com/tomcat-programmatic-setup)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,3 +4,4 @@
|
|||||||
### Relevant Articles:
|
### Relevant Articles:
|
||||||
|
|
||||||
- [Creating a Custom Logback Appender](http://www.baeldung.com/custom-logback-appender)
|
- [Creating a Custom Logback Appender](http://www.baeldung.com/custom-logback-appender)
|
||||||
|
- [Get Log Output in JSON Format](http://www.baeldung.com/java-log-json-output)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
### Relevant articles
|
### Relevant articles
|
||||||
|
|
||||||
- [Simple Jenkins Pipeline with Marathon and Mesos](http://www.baeldung.com/jenkins-pipeline-with-marathon-mesos)
|
- [Simple Jenkins Pipeline with Marathon and Mesos](http://www.baeldung.com/jenkins-pipeline-with-marathon-mesos)
|
||||||
|
|
||||||
|
To run the pipeline, please modify the dockerise.sh file with your own useranema and password for docker login.
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<spring.integration.version>4.3.5.RELEASE</spring.integration.version>
|
<spring.version>5.0.1.RELEASE</spring.version>
|
||||||
<spring-social.version>1.1.4.RELEASE</spring-social.version>
|
<spring-social.version>1.1.4.RELEASE</spring-social.version>
|
||||||
<javax-mail.version>1.4.7</javax-mail.version>
|
<javax-mail.version>1.4.7</javax-mail.version>
|
||||||
<javax-activation.version>1.1.1</javax-activation.version>
|
<javax-activation.version>1.1.1</javax-activation.version>
|
||||||
@ -68,7 +68,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.integration</groupId>
|
<groupId>org.springframework.integration</groupId>
|
||||||
<artifactId>spring-integration-core</artifactId>
|
<artifactId>spring-integration-core</artifactId>
|
||||||
<version>${spring.integration.version}</version>
|
<version>${spring.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.activation</groupId>
|
<groupId>javax.activation</groupId>
|
||||||
@ -84,17 +84,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.integration</groupId>
|
<groupId>org.springframework.integration</groupId>
|
||||||
<artifactId>spring-integration-twitter</artifactId>
|
<artifactId>spring-integration-twitter</artifactId>
|
||||||
<version>${spring.integration.version}</version>
|
<version>${spring.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.integration</groupId>
|
<groupId>org.springframework.integration</groupId>
|
||||||
<artifactId>spring-integration-mail</artifactId>
|
<artifactId>spring-integration-mail</artifactId>
|
||||||
<version>${spring.integration.version}</version>
|
<version>${spring.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.integration</groupId>
|
<groupId>org.springframework.integration</groupId>
|
||||||
<artifactId>spring-integration-ftp</artifactId>
|
<artifactId>spring-integration-ftp</artifactId>
|
||||||
<version>${spring.integration.version}</version>
|
<version>${spring.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.social</groupId>
|
<groupId>org.springframework.social</groupId>
|
||||||
@ -104,7 +104,36 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.integration</groupId>
|
<groupId>org.springframework.integration</groupId>
|
||||||
<artifactId>spring-integration-file</artifactId>
|
<artifactId>spring-integration-file</artifactId>
|
||||||
<version>${spring.integration.version}</version>
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-core</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-config</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-security</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-test</artifactId>
|
||||||
|
<version>${spring.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.baeldung.si.security;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.springframework.integration.annotation.ServiceActivator;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MessageConsumer {
|
||||||
|
|
||||||
|
private String messageContent;
|
||||||
|
|
||||||
|
private Map<String, String> messagePSContent = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public String getMessageContent() {
|
||||||
|
return messageContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageContent(String messageContent) {
|
||||||
|
this.messageContent = messageContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMessagePSContent() {
|
||||||
|
return messagePSContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessagePSContent(Map<String, String> messagePSContent) {
|
||||||
|
this.messagePSContent = messagePSContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceActivator(inputChannel = "endDirectChannel")
|
||||||
|
public void endDirectFlow(Message<?> message) {
|
||||||
|
setMessageContent(message.getPayload().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceActivator(inputChannel = "finalPSResult")
|
||||||
|
public void endPSFlow(Message<?> message) {
|
||||||
|
Logger.getAnonymousLogger().info(Thread.currentThread().getName() + " has completed ---------------------------");
|
||||||
|
messagePSContent.put(Thread.currentThread().getName(), (String) message.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.baeldung.si.security;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.integration.annotation.ServiceActivator;
|
||||||
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
|
import org.springframework.integration.config.EnableIntegration;
|
||||||
|
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||||
|
import org.springframework.integration.security.channel.SecuredChannel;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableIntegration
|
||||||
|
public class SecuredDirectChannel {
|
||||||
|
|
||||||
|
@Bean(name = "startDirectChannel")
|
||||||
|
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = { "ROLE_VIEWER", "jane" })
|
||||||
|
public DirectChannel startDirectChannel() {
|
||||||
|
return new DirectChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceActivator(inputChannel = "startDirectChannel", outputChannel = "endDirectChannel")
|
||||||
|
@PreAuthorize("hasRole('ROLE_LOGGER')")
|
||||||
|
public Message<?> logMessage(Message<?> message) {
|
||||||
|
Logger.getAnonymousLogger().info(message.toString());
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "endDirectChannel")
|
||||||
|
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = { "ROLE_EDITOR" })
|
||||||
|
public DirectChannel endDirectChannel() {
|
||||||
|
return new DirectChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Bean
|
||||||
|
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager, AccessDecisionManager customAccessDecisionManager) {
|
||||||
|
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||||
|
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||||
|
channelSecurityInterceptor.setAccessDecisionManager(customAccessDecisionManager);
|
||||||
|
return channelSecurityInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.baeldung.si.security;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.vote.AffirmativeBased;
|
||||||
|
import org.springframework.security.access.vote.RoleVoter;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager() throws Exception {
|
||||||
|
return super.authenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AccessDecisionManager customAccessDecisionManager() {
|
||||||
|
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
|
||||||
|
decisionVoters.add(new RoleVoter());
|
||||||
|
decisionVoters.add(new UsernameAccessDecisionVoter());
|
||||||
|
AccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);
|
||||||
|
return accessDecisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Bean
|
||||||
|
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager, AccessDecisionManager customAccessDecisionManager) {
|
||||||
|
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||||
|
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||||
|
channelSecurityInterceptor.setAccessDecisionManager(customAccessDecisionManager);
|
||||||
|
return channelSecurityInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.baeldung.si.security;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.integration.annotation.ServiceActivator;
|
||||||
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
|
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||||
|
import org.springframework.integration.config.EnableIntegration;
|
||||||
|
import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||||
|
import org.springframework.integration.security.channel.SecuredChannel;
|
||||||
|
import org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor;
|
||||||
|
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||||
|
import org.springframework.integration.support.MessageBuilder;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.support.ChannelInterceptor;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableIntegration
|
||||||
|
public class SecurityPubSubChannel {
|
||||||
|
|
||||||
|
@Bean(name = "startPSChannel")
|
||||||
|
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = "ROLE_VIEWER")
|
||||||
|
public PublishSubscribeChannel startChannel() {
|
||||||
|
return new PublishSubscribeChannel(executor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceActivator(inputChannel = "startPSChannel", outputChannel = "finalPSResult")
|
||||||
|
@PreAuthorize("hasRole('ROLE_LOGGER')")
|
||||||
|
public Message<?> changeMessageToRole(Message<?> message) {
|
||||||
|
return buildNewMessage(getRoles(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServiceActivator(inputChannel = "startPSChannel", outputChannel = "finalPSResult")
|
||||||
|
@PreAuthorize("hasRole('ROLE_VIEWER')")
|
||||||
|
public Message<?> changeMessageToUserName(Message<?> message) {
|
||||||
|
return buildNewMessage(getUsername(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "finalPSResult")
|
||||||
|
public DirectChannel finalPSResult() {
|
||||||
|
return new DirectChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@GlobalChannelInterceptor(patterns = { "startPSChannel", "endDirectChannel" })
|
||||||
|
public ChannelInterceptor securityContextPropagationInterceptor() {
|
||||||
|
return new SecurityContextPropagationChannelInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ThreadPoolTaskExecutor executor() {
|
||||||
|
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
|
||||||
|
pool.setCorePoolSize(10);
|
||||||
|
pool.setMaxPoolSize(10);
|
||||||
|
pool.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoles() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return securityContext.getAuthentication().getAuthorities().stream().map(auth -> auth.getAuthority()).collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return securityContext.getAuthentication().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message<String> buildNewMessage(String content, Message<?> message) {
|
||||||
|
DefaultMessageBuilderFactory builderFactory = new DefaultMessageBuilderFactory();
|
||||||
|
MessageBuilder<String> messageBuilder = builderFactory.withPayload(content);
|
||||||
|
messageBuilder.copyHeaders(message.getHeaders());
|
||||||
|
return messageBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.baeldung.si.security;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
public class UsernameAccessDecisionVoter implements AccessDecisionVoter<Object> {
|
||||||
|
private String rolePrefix = "ROLE_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
if ((attribute.getAttribute() != null)
|
||||||
|
&& !attribute.getAttribute().startsWith(rolePrefix)) {
|
||||||
|
return true;
|
||||||
|
}else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
|
||||||
|
if (authentication == null) {
|
||||||
|
return ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
String name = authentication.getName();
|
||||||
|
int result = ACCESS_ABSTAIN;
|
||||||
|
for (ConfigAttribute attribute : attributes) {
|
||||||
|
if (this.supports(attribute)) {
|
||||||
|
result = ACCESS_DENIED;
|
||||||
|
if (attribute.getAttribute().equals(name)) {
|
||||||
|
return ACCESS_GRANTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.baeldung.si;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
|
import org.springframework.messaging.support.GenericMessage;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import com.baeldung.si.security.MessageConsumer;
|
||||||
|
import com.baeldung.si.security.SecuredDirectChannel;
|
||||||
|
import com.baeldung.si.security.SecurityConfig;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = { SecurityConfig.class, SecuredDirectChannel.class, MessageConsumer.class })
|
||||||
|
public class TestSpringIntegrationSecurity {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
SubscribableChannel startDirectChannel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MessageConsumer messageConsumer;
|
||||||
|
|
||||||
|
final String DIRECT_CHANNEL_MESSAGE = "Direct channel message";
|
||||||
|
|
||||||
|
@Test(expected = AuthenticationCredentialsNotFoundException.class)
|
||||||
|
public void givenNoUser_whenSendToDirectChannel_thenCredentialNotFound() {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithMockUser(username = "jane", roles = { "LOGGER" })
|
||||||
|
public void givenRoleLogger_whenSendMessageToDirectChannel_thenAccessDenied() throws Throwable {
|
||||||
|
try {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithMockUser(username = "jane")
|
||||||
|
public void givenJane_whenSendMessageToDirectChannel_thenAccessDenied() throws Throwable {
|
||||||
|
try {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AccessDeniedException.class)
|
||||||
|
@WithMockUser(roles = { "VIEWER" })
|
||||||
|
public void givenRoleViewer_whenSendToDirectChannel_thenAccessDenied() throws Throwable {
|
||||||
|
try {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "LOGGER", "VIEWER", "EDITOR" })
|
||||||
|
public void givenRoleLoggerAndUser_whenSendMessageToDirectChannel_thenFlowCompletedSuccessfully() {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
assertEquals(DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "jane", roles = { "LOGGER", "EDITOR" })
|
||||||
|
public void givenJaneLoggerEditor_whenSendToDirectChannel_thenFlowCompleted() {
|
||||||
|
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
assertEquals(DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.baeldung.si;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
|
import org.springframework.messaging.support.GenericMessage;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import com.baeldung.si.security.MessageConsumer;
|
||||||
|
import com.baeldung.si.security.SecurityConfig;
|
||||||
|
import com.baeldung.si.security.SecurityPubSubChannel;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = { SecurityPubSubChannel.class, MessageConsumer.class, SecurityConfig.class })
|
||||||
|
public class TestSpringIntegrationSecurityExecutor {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
SubscribableChannel startPSChannel;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MessageConsumer messageConsumer;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ThreadPoolTaskExecutor executor;
|
||||||
|
|
||||||
|
final String DIRECT_CHANNEL_MESSAGE = "Direct channel message";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void clearData() {
|
||||||
|
messageConsumer.setMessagePSContent(new ConcurrentHashMap<>());
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "user", roles = { "VIEWER" })
|
||||||
|
public void givenRoleUser_whenSendMessageToPSChannel_thenNoMessageArrived() throws IllegalStateException, InterruptedException {
|
||||||
|
startPSChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
|
||||||
|
executor.getThreadPoolExecutor().awaitTermination(2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
assertEquals(1, messageConsumer.getMessagePSContent().size());
|
||||||
|
assertTrue(messageConsumer.getMessagePSContent().values().contains("user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "user", roles = { "LOGGER", "VIEWER" })
|
||||||
|
public void givenRoleUserAndLogger_whenSendMessageToPSChannel_then2GetMessages() throws IllegalStateException, InterruptedException {
|
||||||
|
startPSChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||||
|
|
||||||
|
executor.getThreadPoolExecutor().awaitTermination(2, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
assertEquals(2, messageConsumer.getMessagePSContent().size());
|
||||||
|
assertTrue(messageConsumer.getMessagePSContent().values().contains("user"));
|
||||||
|
assertTrue(messageConsumer.getMessagePSContent().values().contains("ROLE_LOGGER,ROLE_VIEWER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,3 +17,4 @@
|
|||||||
- [Introduction to Jukito](http://www.baeldung.com/jukito)
|
- [Introduction to Jukito](http://www.baeldung.com/jukito)
|
||||||
- [Custom JUnit 4 Test Runners](http://www.baeldung.com/junit-4-custom-runners)
|
- [Custom JUnit 4 Test Runners](http://www.baeldung.com/junit-4-custom-runners)
|
||||||
- [Guide to JSpec](http://www.baeldung.com/jspec)
|
- [Guide to JSpec](http://www.baeldung.com/jspec)
|
||||||
|
- [Custom Assertions with AssertJ](http://www.baeldung.com/assertj-custom-assertion)
|
||||||
|
@ -173,7 +173,7 @@
|
|||||||
<jacoco.version>0.7.7.201606060606</jacoco.version>
|
<jacoco.version>0.7.7.201606060606</jacoco.version>
|
||||||
<guava.version>21.0</guava.version>
|
<guava.version>21.0</guava.version>
|
||||||
<assertj-guava.version>3.1.0</assertj-guava.version>
|
<assertj-guava.version>3.1.0</assertj-guava.version>
|
||||||
<assertj-core.version>3.6.1</assertj-core.version>
|
<assertj-core.version>3.9.0</assertj-core.version>
|
||||||
<assertj-generator.version>2.1.0</assertj-generator.version>
|
<assertj-generator.version>2.1.0</assertj-generator.version>
|
||||||
<truth.version>0.32</truth.version>
|
<truth.version>0.32</truth.version>
|
||||||
<jUnitParams.version>1.1.0</jUnitParams.version>
|
<jUnitParams.version>1.1.0</jUnitParams.version>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.baeldung.testing.assertj.exceptions;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class Java7StyleAssertions {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDividingByZero_thenArithmeticException() {
|
||||||
|
try {
|
||||||
|
int numerator = 10;
|
||||||
|
int denominator = 0;
|
||||||
|
int quotient = numerator / denominator;
|
||||||
|
fail("ArithmeticException expected because dividing by zero yields an ArithmeticException.");
|
||||||
|
failBecauseExceptionWasNotThrown(ArithmeticException.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).hasMessage("/ by zero");
|
||||||
|
assertThat(e).isInstanceOf(ArithmeticException.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.baeldung.testing.assertj.exceptions;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class Java8StyleAssertions {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDividingByZero_thenArithmeticException() {
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
int numerator = 10;
|
||||||
|
int denominator = 0;
|
||||||
|
int quotient = numerator / denominator;
|
||||||
|
}).isInstanceOf(ArithmeticException.class)
|
||||||
|
.hasMessageContaining("/ by zero");
|
||||||
|
|
||||||
|
assertThatExceptionOfType(ArithmeticException.class).isThrownBy(() -> {
|
||||||
|
int numerator = 10;
|
||||||
|
int denominator = 0;
|
||||||
|
int quotient = numerator / denominator;
|
||||||
|
})
|
||||||
|
.withMessageContaining("/ by zero");
|
||||||
|
|
||||||
|
// BDD style:
|
||||||
|
|
||||||
|
// when
|
||||||
|
Throwable thrown = catchThrowable(() -> {
|
||||||
|
int numerator = 10;
|
||||||
|
int denominator = 0;
|
||||||
|
int quotient = numerator / denominator;
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(thrown).isInstanceOf(ArithmeticException.class)
|
||||||
|
.hasMessageContaining("/ by zero");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user