Merge remote-tracking branch 'eugenp/master'
This commit is contained in:
commit
c23f5e1840
7
.gitignore
vendored
7
.gitignore
vendored
@ -66,3 +66,10 @@ jmeter/src/main/resources/*-JMeter.csv
|
||||
**/nb-configuration.xml
|
||||
core-scala/.cache-main
|
||||
core-scala/.cache-tests
|
||||
|
||||
|
||||
persistence-modules/hibernate5/transaction.log
|
||||
apache-avro/src/main/java/com/baeldung/avro/model/
|
||||
jta/transaction-logs/
|
||||
software-security/sql-injection-samples/derby.log
|
||||
spring-soap/src/main/java/com/baeldung/springsoap/gen/
|
@ -0,0 +1,8 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
trait AnimalTrait {
|
||||
|
||||
String basicBehavior() {
|
||||
return "Animalistic!!"
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
class Dog implements WalkingTrait, SpeakingTrait {
|
||||
|
||||
String speakAndWalk() {
|
||||
WalkingTrait.super.speakAndWalk()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
class Employee implements UserTrait {
|
||||
|
||||
String name() {
|
||||
return 'Bob'
|
||||
}
|
||||
|
||||
String lastName() {
|
||||
return "Marley"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
interface Human {
|
||||
|
||||
String lastName()
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
trait SpeakingTrait {
|
||||
|
||||
String basicAbility() {
|
||||
return "Speaking!!"
|
||||
}
|
||||
|
||||
String speakAndWalk() {
|
||||
return "Speak and walk!!"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
trait UserTrait implements Human {
|
||||
|
||||
String sayHello() {
|
||||
return "Hello!"
|
||||
}
|
||||
|
||||
abstract String name()
|
||||
|
||||
String showName() {
|
||||
return "Hello, ${name()}!"
|
||||
}
|
||||
|
||||
private String greetingMessage() {
|
||||
return 'Hello, from a private method!'
|
||||
}
|
||||
|
||||
String greet() {
|
||||
def msg = greetingMessage()
|
||||
println msg
|
||||
msg
|
||||
}
|
||||
|
||||
def whoAmI() {
|
||||
return this
|
||||
}
|
||||
|
||||
String showLastName() {
|
||||
return "Hello, ${lastName()}!"
|
||||
}
|
||||
|
||||
String email
|
||||
String address
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
trait WalkingTrait {
|
||||
|
||||
String basicAbility() {
|
||||
return "Walking!!"
|
||||
}
|
||||
|
||||
String speakAndWalk() {
|
||||
return "Walk and speak!!"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.baeldung.traits
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class TraitsUnitTest extends Specification {
|
||||
|
||||
Employee employee
|
||||
Dog dog
|
||||
|
||||
void setup () {
|
||||
employee = new Employee()
|
||||
dog = new Dog()
|
||||
}
|
||||
|
||||
def 'Should return msg string when using Employee.sayHello method provided by UserTrait' () {
|
||||
when:
|
||||
def msg = employee.sayHello()
|
||||
then:
|
||||
msg
|
||||
msg instanceof String
|
||||
assert msg == "Hello!"
|
||||
}
|
||||
|
||||
def 'Should return displayMsg string when using Employee.showName method' () {
|
||||
when:
|
||||
def displayMsg = employee.showName()
|
||||
then:
|
||||
displayMsg
|
||||
displayMsg instanceof String
|
||||
assert displayMsg == "Hello, Bob!"
|
||||
}
|
||||
|
||||
def 'Should return greetMsg string when using Employee.greet method' () {
|
||||
when:
|
||||
def greetMsg = employee.greet()
|
||||
then:
|
||||
greetMsg
|
||||
greetMsg instanceof String
|
||||
assert greetMsg == "Hello, from a private method!"
|
||||
}
|
||||
|
||||
def 'Should return MissingMethodException when using Employee.greetingMessage method' () {
|
||||
when:
|
||||
def exception
|
||||
try {
|
||||
employee.greetingMessage()
|
||||
}catch(Exception e) {
|
||||
exception = e
|
||||
}
|
||||
|
||||
then:
|
||||
exception
|
||||
exception instanceof groovy.lang.MissingMethodException
|
||||
assert exception.message == "No signature of method: com.baeldung.traits.Employee.greetingMessage()"+
|
||||
" is applicable for argument types: () values: []"
|
||||
}
|
||||
|
||||
def 'Should return employee instance when using Employee.whoAmI method' () {
|
||||
when:
|
||||
def emp = employee.whoAmI()
|
||||
then:
|
||||
emp
|
||||
emp instanceof Employee
|
||||
assert emp.is(employee)
|
||||
}
|
||||
|
||||
def 'Should display lastName when using Employee.showLastName method' () {
|
||||
when:
|
||||
def lastNameMsg = employee.showLastName()
|
||||
then:
|
||||
lastNameMsg
|
||||
lastNameMsg instanceof String
|
||||
assert lastNameMsg == "Hello, Marley!"
|
||||
}
|
||||
|
||||
def 'Should be able to define properties of UserTrait in Employee instance' () {
|
||||
when:
|
||||
employee = new Employee(email: "a@e.com", address: "baeldung.com")
|
||||
then:
|
||||
employee
|
||||
employee instanceof Employee
|
||||
assert employee.email == "a@e.com"
|
||||
assert employee.address == "baeldung.com"
|
||||
}
|
||||
|
||||
def 'Should execute basicAbility method from SpeakingTrait and return msg string' () {
|
||||
when:
|
||||
def speakMsg = dog.basicAbility()
|
||||
then:
|
||||
speakMsg
|
||||
speakMsg instanceof String
|
||||
assert speakMsg == "Speaking!!"
|
||||
}
|
||||
|
||||
def 'Should verify multiple inheritance with traits and execute overridden traits method' () {
|
||||
when:
|
||||
def walkSpeakMsg = dog.speakAndWalk()
|
||||
println walkSpeakMsg
|
||||
then:
|
||||
walkSpeakMsg
|
||||
walkSpeakMsg instanceof String
|
||||
assert walkSpeakMsg == "Walk and speak!!"
|
||||
}
|
||||
|
||||
def 'Should implement AnimalTrait at runtime and access basicBehavior method' () {
|
||||
when:
|
||||
def dogInstance = new Dog() as AnimalTrait
|
||||
def basicBehaviorMsg = dogInstance.basicBehavior()
|
||||
then:
|
||||
basicBehaviorMsg
|
||||
basicBehaviorMsg instanceof String
|
||||
assert basicBehaviorMsg == "Animalistic!!"
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
public @interface Init {
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target({ FIELD })
|
||||
public @interface JsonElement {
|
||||
public String key() default "";
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(TYPE)
|
||||
public @interface JsonSerializable {
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
public class JsonSerializationException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JsonSerializationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ObjectToJsonConverter {
|
||||
public String convertToJson(Object object) throws JsonSerializationException {
|
||||
try {
|
||||
|
||||
checkIfSerializable(object);
|
||||
initializeObject(object);
|
||||
return getJsonString(object);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new JsonSerializationException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfSerializable(Object object) {
|
||||
if (Objects.isNull(object)) {
|
||||
throw new JsonSerializationException("Can't serialize a null object");
|
||||
}
|
||||
|
||||
Class<?> clazz = object.getClass();
|
||||
if (!clazz.isAnnotationPresent(JsonSerializable.class)) {
|
||||
throw new JsonSerializationException("The class " + clazz.getSimpleName() + " is not annotated with JsonSerializable");
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeObject(Object object) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Class<?> clazz = object.getClass();
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.isAnnotationPresent(Init.class)) {
|
||||
method.setAccessible(true);
|
||||
method.invoke(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getJsonString(Object object) throws IllegalArgumentException, IllegalAccessException {
|
||||
Class<?> clazz = object.getClass();
|
||||
Map<String, String> jsonElementsMap = new HashMap<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
if (field.isAnnotationPresent(JsonElement.class)) {
|
||||
jsonElementsMap.put(getKey(field), (String) field.get(object));
|
||||
}
|
||||
}
|
||||
|
||||
String jsonString = jsonElementsMap.entrySet()
|
||||
.stream()
|
||||
.map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"")
|
||||
.collect(Collectors.joining(","));
|
||||
return "{" + jsonString + "}";
|
||||
}
|
||||
|
||||
private String getKey(Field field) {
|
||||
String value = field.getAnnotation(JsonElement.class)
|
||||
.key();
|
||||
return value.isEmpty() ? field.getName() : value;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
@JsonSerializable
|
||||
public class Person {
|
||||
@JsonElement
|
||||
private String firstName;
|
||||
@JsonElement
|
||||
private String lastName;
|
||||
@JsonElement(key = "personAge")
|
||||
private String age;
|
||||
|
||||
private String address;
|
||||
|
||||
public Person(String firstName, String lastName) {
|
||||
super();
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person(String firstName, String lastName, String age) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Init
|
||||
private void initNames() {
|
||||
this.firstName = this.firstName.substring(0, 1)
|
||||
.toUpperCase() + this.firstName.substring(1);
|
||||
this.lastName = this.lastName.substring(0, 1)
|
||||
.toUpperCase() + this.lastName.substring(1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(String age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
}
|
@ -85,10 +85,16 @@ public class Java8CollectorsUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenCollectingToMap_shouldCollectToMapMerging() throws Exception {
|
||||
final Map<String, Integer> result = givenList.stream().collect(toMap(Function.identity(), String::length, (i1, i2) -> i1));
|
||||
public void whenCollectingToMapwWithDuplicates_shouldCollectToMapMergingTheIdenticalItems() throws Exception {
|
||||
final Map<String, Integer> result = listWithDuplicates.stream().collect(
|
||||
toMap(
|
||||
Function.identity(),
|
||||
String::length,
|
||||
(item, identicalItem) -> item
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(result).containsEntry("a", 1).containsEntry("bb", 2).containsEntry("ccc", 3).containsEntry("dd", 2);
|
||||
assertThat(result).containsEntry("a", 1).containsEntry("bb", 2).containsEntry("c", 1).containsEntry("d", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.baeldung.customannotations;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class JsonSerializerUnitTest {
|
||||
|
||||
@Test
|
||||
public void givenObjectNotSerializedThenExceptionThrown() throws JsonSerializationException {
|
||||
Object object = new Object();
|
||||
ObjectToJsonConverter serializer = new ObjectToJsonConverter();
|
||||
assertThrows(JsonSerializationException.class, () -> {
|
||||
serializer.convertToJson(object);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException {
|
||||
Person person = new Person("soufiane", "cheouati", "34");
|
||||
ObjectToJsonConverter serializer = new ObjectToJsonConverter();
|
||||
String jsonString = serializer.convertToJson(person);
|
||||
assertEquals("{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}", jsonString);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.baeldung.multireleaseapp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class App {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String dateToCheck = args[0];
|
||||
boolean isLeapYear = DateHelper.checkIfLeapYear(dateToCheck);
|
||||
logger.info("Date given " + dateToCheck + " is leap year: " + isLeapYear);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.baeldung.multireleaseapp;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DateHelper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DateHelper.class);
|
||||
|
||||
public static boolean checkIfLeapYear(String dateStr) throws Exception {
|
||||
logger.info("Checking for leap year using Java 1 calendar API");
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(dateStr));
|
||||
int year = cal.get(Calendar.YEAR);
|
||||
return (new GregorianCalendar()).isLeapYear(year);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.baeldung.multireleaseapp;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DateHelper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DateHelper.class);
|
||||
|
||||
public static boolean checkIfLeapYear(String dateStr) throws Exception {
|
||||
logger.info("Checking for leap year using Java 9 Date Api");
|
||||
return LocalDate.parse(dateStr)
|
||||
.isLeapYear();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.baeldung.allequalelements;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
public class VerifyAllEqualListElements {
|
||||
|
||||
public boolean verifyAllEqualUsingALoop(List<String> list) {
|
||||
for (String s : list) {
|
||||
if (!s.equals(list.get(0)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualUsingHashSet(List<String> list) {
|
||||
return new HashSet<String>(list).size() <= 1;
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualUsingFrequency(List<String> list) {
|
||||
return list.isEmpty() || Collections.frequency(list, list.get(0)) == list.size();
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualUsingStream(List<String> list) {
|
||||
return list.stream()
|
||||
.distinct()
|
||||
.count() <= 1;
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualAnotherUsingStream(List<String> list) {
|
||||
return list.isEmpty() || list.stream()
|
||||
.allMatch(list.get(0)::equals);
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualUsingGuava(List<String> list) {
|
||||
return Iterables.all(list, new Predicate<String>() {
|
||||
public boolean apply(String s) {
|
||||
return s.equals(list.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean verifyAllEqualUsingApacheCommon(List<String> list) {
|
||||
return IterableUtils.matchesAll(list, new org.apache.commons.collections4.Predicate<String>() {
|
||||
public boolean evaluate(String s) {
|
||||
return s.equals(list.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
package com.baeldung.allequalelements;
|
||||
|
||||
import org.junit.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class VerifyAllEqualListElementsUnitTest {
|
||||
|
||||
private static List<String> notAllEqualList = new ArrayList<>();
|
||||
|
||||
private static List<String> emptyList = new ArrayList<>();
|
||||
|
||||
private static List<String> allEqualList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
notAllEqualList = Arrays.asList("Jack", "James", "Sam", "James");
|
||||
emptyList = Arrays.asList();
|
||||
allEqualList = Arrays.asList("Jack", "Jack", "Jack", "Jack");
|
||||
}
|
||||
|
||||
private static VerifyAllEqualListElements verifyAllEqualListElements = new VerifyAllEqualListElements();
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingALoop_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingALoop(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingALoop_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingALoop(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingALoop_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingALoop(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingHashSet_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingHashSet(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingHashSet_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingHashSet(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingHashSet_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingHashSet(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingFrequency_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingFrequency(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingFrequency_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingFrequency(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingFrequency_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingFrequency(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingStream_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingStream(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingStream_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingStream(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingStream_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingStream(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingAnotherStream_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualAnotherUsingStream(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingAnotherStream_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualAnotherUsingStream(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingAnotherStream_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualAnotherUsingStream(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingGuava_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingGuava(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingGuava_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingGuava(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingGuava_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingGuava(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotAllEqualList_whenUsingApacheCommon_thenReturnFalse() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingApacheCommon(notAllEqualList);
|
||||
|
||||
assertFalse(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmptyList_whenUsingApacheCommon_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingApacheCommon(emptyList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAllEqualList_whenUsingApacheCommon_thenReturnTrue() {
|
||||
boolean allEqual = verifyAllEqualListElements.verifyAllEqualUsingApacheCommon(allEqualList);
|
||||
|
||||
assertTrue(allEqual);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -17,7 +18,6 @@ import org.slf4j.LoggerFactory;
|
||||
public class WriteCsvFileExampleUnitTest {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WriteCsvFileExampleUnitTest.class);
|
||||
|
||||
private static final String CSV_FILE_NAME = "src/test/resources/exampleOutput.csv";
|
||||
private WriteCsvFileExample csvExample;
|
||||
|
||||
@Before
|
||||
@ -65,12 +65,12 @@ public class WriteCsvFileExampleUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenDataArray_whenConvertToCSV_thenOutputCreated() {
|
||||
public void givenDataArray_whenConvertToCSV_thenOutputCreated() throws IOException {
|
||||
List<String[]> dataLines = new ArrayList<String[]>();
|
||||
dataLines.add(new String[] { "John", "Doe", "38", "Comment Data\nAnother line of comment data" });
|
||||
dataLines.add(new String[] { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });
|
||||
|
||||
File csvOutputFile = new File(CSV_FILE_NAME);
|
||||
File csvOutputFile = File.createTempFile("exampleOutput", ".csv");
|
||||
try (PrintWriter pw = new PrintWriter(csvOutputFile)) {
|
||||
dataLines.stream()
|
||||
.map(csvExample::convertToCSV)
|
||||
@ -80,5 +80,6 @@ public class WriteCsvFileExampleUnitTest {
|
||||
}
|
||||
|
||||
assertTrue(csvOutputFile.exists());
|
||||
csvOutputFile.deleteOnExit();
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
John,Doe,38,Comment Data Another line of comment data
|
||||
Jane,"Doe, Jr.",19,"She said ""I'm being quoted"""
|
|
@ -0,0 +1,83 @@
|
||||
package com.baeldung.stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class StreamMapUnitTest {
|
||||
|
||||
private Map<String, String> books;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
books = new HashMap<>();
|
||||
books.put("978-0201633610", "Design patterns : elements of reusable object-oriented software");
|
||||
books.put("978-1617291999", "Java 8 in Action: Lambdas, Streams, and functional-style programming");
|
||||
books.put("978-0134685991", "Effective Java");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void whenOptionalVersionCalledForExistingTitle_thenReturnOptionalWithISBN() {
|
||||
Optional<String> optionalIsbn = books.entrySet().stream()
|
||||
.filter(e -> "Effective Java".equals(e.getValue()))
|
||||
.map(Map.Entry::getKey).findFirst();
|
||||
|
||||
assertEquals("978-0134685991", optionalIsbn.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenOptionalVersionCalledForNonExistingTitle_thenReturnEmptyOptionalForISBN() {
|
||||
Optional<String> optionalIsbn = books.entrySet().stream()
|
||||
.filter(e -> "Non Existent Title".equals(e.getValue()))
|
||||
.map(Map.Entry::getKey).findFirst();
|
||||
|
||||
assertEquals(false, optionalIsbn.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenMultipleResultsVersionCalledForExistingTitle_aCollectionWithMultipleValuesIsReturned() {
|
||||
books.put("978-0321356680", "Effective Java: Second Edition");
|
||||
|
||||
List<String> isbnCodes = books.entrySet().stream()
|
||||
.filter(e -> e.getValue().startsWith("Effective Java"))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertTrue(isbnCodes.contains("978-0321356680"));
|
||||
assertTrue(isbnCodes.contains("978-0134685991"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenMultipleResultsVersionCalledForNonExistingTitle_aCollectionWithNoValuesIsReturned() {
|
||||
List<String> isbnCodes = books.entrySet().stream()
|
||||
.filter(e -> e.getValue().startsWith("Spring"))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertTrue(isbnCodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenKeysFollowingPatternReturnsAllValuesForThoseKeys() {
|
||||
List<String> titlesForKeyPattern = books.entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith("978-0"))
|
||||
.map(Map.Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
assertEquals(2, titlesForKeyPattern.size());
|
||||
assertTrue(titlesForKeyPattern.contains("Design patterns : elements of reusable object-oriented software"));
|
||||
assertTrue(titlesForKeyPattern.contains("Effective Java"));
|
||||
}
|
||||
|
||||
}
|
@ -73,6 +73,16 @@
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-core</artifactId>
|
||||
<version>${openjdk-jmh.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-generator-annprocess</artifactId>
|
||||
<version>${openjdk-jmh.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -92,6 +102,7 @@
|
||||
<h2database.version>1.4.196</h2database.version>
|
||||
<assertj-core.version>3.8.0</assertj-core.version>
|
||||
<jackson.version>2.9.7</jackson.version>
|
||||
<openjdk-jmh.version>1.21</openjdk-jmh.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
@ -68,6 +68,11 @@ public class HibernateUtil {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
public static SessionFactory getSessionFactoryByProperties(Properties properties) throws IOException {
|
||||
ServiceRegistry serviceRegistry = configureServiceRegistry(properties);
|
||||
return makeSessionFactory(serviceRegistry);
|
||||
}
|
||||
|
||||
private static SessionFactory makeSessionFactory(ServiceRegistry serviceRegistry) {
|
||||
MetadataSources metadataSources = new MetadataSources(serviceRegistry);
|
||||
|
||||
@ -119,12 +124,15 @@ public class HibernateUtil {
|
||||
}
|
||||
|
||||
private static ServiceRegistry configureServiceRegistry() throws IOException {
|
||||
Properties properties = getProperties();
|
||||
return configureServiceRegistry(getProperties());
|
||||
}
|
||||
|
||||
private static ServiceRegistry configureServiceRegistry(Properties properties) throws IOException {
|
||||
return new StandardServiceRegistryBuilder().applySettings(properties)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Properties getProperties() throws IOException {
|
||||
public static Properties getProperties() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
URL propertiesURL = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
|
@ -0,0 +1,106 @@
|
||||
package com.baeldung.hibernate.queryplancache;
|
||||
|
||||
import com.baeldung.hibernate.HibernateUtil;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.jpa.QueryHints;
|
||||
import org.hibernate.query.Query;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.TearDown;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class QueryPlanCacheBenchmark {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryPlanCacheBenchmark.class);
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class QueryPlanCacheBenchMarkState {
|
||||
@Param({"1", "2", "3"})
|
||||
public int planCacheSize;
|
||||
|
||||
public Session session;
|
||||
|
||||
@Setup
|
||||
public void stateSetup() throws IOException {
|
||||
LOGGER.info("State - Setup");
|
||||
session = initSession(planCacheSize);
|
||||
LOGGER.info("State - Setup Complete");
|
||||
}
|
||||
|
||||
private Session initSession(int planCacheSize) throws IOException {
|
||||
Properties properties = HibernateUtil.getProperties();
|
||||
properties.put("hibernate.query.plan_cache_max_size", planCacheSize);
|
||||
properties.put("hibernate.query.plan_parameter_metadata_max_size", planCacheSize);
|
||||
SessionFactory sessionFactory = HibernateUtil.getSessionFactoryByProperties(properties);
|
||||
return sessionFactory.openSession();
|
||||
}
|
||||
|
||||
@TearDown
|
||||
public void tearDownState() {
|
||||
LOGGER.info("State - Teardown");
|
||||
SessionFactory sessionFactory = session.getSessionFactory();
|
||||
session.close();
|
||||
sessionFactory.close();
|
||||
LOGGER.info("State - Teardown complete");
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 2)
|
||||
@Measurement(iterations = 5)
|
||||
public void givenQueryPlanCacheSize_thenCompileQueries(QueryPlanCacheBenchMarkState state, Blackhole blackhole) {
|
||||
|
||||
Query query1 = findEmployeesByDepartmentNameQuery(state.session);
|
||||
Query query2 = findEmployeesByDesignationQuery(state.session);
|
||||
Query query3 = findDepartmentOfAnEmployeeQuery(state.session);
|
||||
|
||||
blackhole.consume(query1);
|
||||
blackhole.consume(query2);
|
||||
blackhole.consume(query3);
|
||||
|
||||
}
|
||||
|
||||
private Query findEmployeesByDepartmentNameQuery(Session session) {
|
||||
return session.createQuery("SELECT e FROM DeptEmployee e " +
|
||||
"JOIN e.department WHERE e.department.name = :deptName")
|
||||
.setMaxResults(30)
|
||||
.setHint(QueryHints.HINT_FETCH_SIZE, 30);
|
||||
}
|
||||
|
||||
private Query findEmployeesByDesignationQuery(Session session) {
|
||||
return session.createQuery("SELECT e FROM DeptEmployee e " +
|
||||
"WHERE e.title = :designation")
|
||||
.setHint(QueryHints.SPEC_HINT_TIMEOUT, 1000);
|
||||
}
|
||||
|
||||
private Query findDepartmentOfAnEmployeeQuery(Session session) {
|
||||
return session.createQuery("SELECT e.department FROM DeptEmployee e " +
|
||||
"JOIN e.department WHERE e.employeeNumber = :empId");
|
||||
|
||||
}
|
||||
|
||||
public static void main(String... args) throws IOException, RunnerException {
|
||||
//main-class to run the benchmark
|
||||
org.openjdk.jmh.Main.main(args);
|
||||
}
|
||||
}
|
@ -70,4 +70,7 @@ public interface UserRepository extends JpaRepository<User, Integer> , UserRepos
|
||||
@Modifying
|
||||
@Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?", nativeQuery = true)
|
||||
int updateUserSetStatusForNameNativePostgres(Integer status, String name);
|
||||
|
||||
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
|
||||
List<User> findUserByNameList(@Param("names") Collection<String> names);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import org.springframework.data.jpa.domain.JpaSort;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -364,6 +365,26 @@ class UserRepositoryCommon {
|
||||
assertThat(usersWithEmails.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUsersInDBWhenFindByNameListReturnCollection() {
|
||||
|
||||
User user1 = new User();
|
||||
user1.setName(USER_NAME_ADAM);
|
||||
user1.setEmail(USER_EMAIL);
|
||||
userRepository.save(user1);
|
||||
|
||||
User user2 = new User();
|
||||
user2.setName(USER_NAME_PETER);
|
||||
user2.setEmail(USER_EMAIL2);
|
||||
userRepository.save(user2);
|
||||
|
||||
List<String> names = Arrays.asList(USER_NAME_ADAM, USER_NAME_PETER);
|
||||
|
||||
List<User> usersWithNames = userRepository.findUserByNameList(names);
|
||||
|
||||
assertThat(usersWithNames.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
userRepository.deleteAll();
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
|
||||
### Relevant Articles:
|
||||
- [Spring 3 and JPA with Hibernate](http://www.baeldung.com/2011/12/13/the-persistence-layer-with-spring-3-1-and-jpa/)
|
||||
- [Transactions with Spring 3 and JPA](http://www.baeldung.com/2011/12/26/transaction-configuration-with-jpa-and-spring-3-1/)
|
||||
- [A Guide to JPA with Spring](https://www.baeldung.com/the-persistence-layer-with-spring-and-jpa)
|
||||
- [Transactions with Spring and JPA](https://www.baeldung.com/transaction-configuration-with-jpa-and-spring)
|
||||
- [The DAO with JPA and Spring](http://www.baeldung.com/spring-dao-jpa)
|
||||
- [JPA Pagination](http://www.baeldung.com/jpa-pagination)
|
||||
- [Sorting with JPA](http://www.baeldung.com/jpa-sort)
|
||||
@ -21,6 +21,7 @@
|
||||
- [Use Criteria Queries in a Spring Data Application](https://www.baeldung.com/spring-data-criteria-queries)
|
||||
- [Many-To-Many Relationship in JPA](https://www.baeldung.com/jpa-many-to-many)
|
||||
|
||||
|
||||
### Eclipse Config
|
||||
After importing the project into Eclipse, you may see the following error:
|
||||
"No persistence xml file found in project"
|
||||
|
2
pom.xml
2
pom.xml
@ -1594,7 +1594,7 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<gib.referenceBranch>refs/heads/master</gib.referenceBranch>
|
||||
<gib.referenceBranch>refs/remotes/origin/master</gib.referenceBranch>
|
||||
<gib.skipTestsForNotImpactedModules>true</gib.skipTestsForNotImpactedModules>
|
||||
<gib.failOnMissingGitDir>false</gib.failOnMissingGitDir>
|
||||
<gib.failOnError>false</gib.failOnError>
|
||||
|
@ -16,6 +16,8 @@
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
@ -42,6 +44,17 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -57,4 +70,4 @@
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
</project>
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package com.baeldung.examples.security.sql;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Philippe
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="Accounts")
|
||||
@Data
|
||||
public class Account {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String customerId;
|
||||
private String accNumber;
|
||||
private String branchId;
|
||||
private BigDecimal balance;
|
||||
|
||||
}
|
@ -7,14 +7,24 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -27,9 +37,11 @@ import org.springframework.stereotype.Component;
|
||||
public class AccountDAO {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final EntityManager em;
|
||||
|
||||
public AccountDAO(DataSource dataSource) {
|
||||
public AccountDAO(DataSource dataSource, EntityManager em) {
|
||||
this.dataSource = dataSource;
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,6 +75,26 @@ public class AccountDAO {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id - JPA version
|
||||
*
|
||||
* @param customerId
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> unsafeJpaFindAccountsByCustomerId(String customerId) {
|
||||
String jql = "from Account where customerId = '" + customerId + "'";
|
||||
TypedQuery<Account> q = em.createQuery(jql, Account.class);
|
||||
return q.getResultList()
|
||||
.stream()
|
||||
.map(a -> AccountDTO.builder()
|
||||
.accNumber(a.getAccNumber())
|
||||
.balance(a.getBalance())
|
||||
.branchId(a.getAccNumber())
|
||||
.customerId(a.getCustomerId())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id
|
||||
*
|
||||
@ -71,7 +103,7 @@ public class AccountDAO {
|
||||
*/
|
||||
public List<AccountDTO> safeFindAccountsByCustomerId(String customerId) {
|
||||
|
||||
String sql = "select " + "customer_id,acc_number,branch_id,balance from Accounts where customer_id = ?";
|
||||
String sql = "select customer_id, branch_id,acc_number,balance from Accounts where customer_id = ?";
|
||||
|
||||
try (Connection c = dataSource.getConnection(); PreparedStatement p = c.prepareStatement(sql)) {
|
||||
p.setString(1, customerId);
|
||||
@ -93,23 +125,73 @@ public class AccountDAO {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id - JPA version
|
||||
*
|
||||
* @param customerId
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> safeJpaFindAccountsByCustomerId(String customerId) {
|
||||
|
||||
String jql = "from Account where customerId = :customerId";
|
||||
TypedQuery<Account> q = em.createQuery(jql, Account.class)
|
||||
.setParameter("customerId", customerId);
|
||||
|
||||
return q.getResultList()
|
||||
.stream()
|
||||
.map(a -> AccountDTO.builder()
|
||||
.accNumber(a.getAccNumber())
|
||||
.balance(a.getBalance())
|
||||
.branchId(a.getAccNumber())
|
||||
.customerId(a.getCustomerId())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id - JPA version
|
||||
*
|
||||
* @param customerId
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> safeJpaCriteriaFindAccountsByCustomerId(String customerId) {
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Account> cq = cb.createQuery(Account.class);
|
||||
Root<Account> root = cq.from(Account.class);
|
||||
cq.select(root)
|
||||
.where(cb.equal(root.get(Account_.customerId), customerId));
|
||||
|
||||
TypedQuery<Account> q = em.createQuery(cq);
|
||||
|
||||
return q.getResultList()
|
||||
.stream()
|
||||
.map(a -> AccountDTO.builder()
|
||||
.accNumber(a.getAccNumber())
|
||||
.balance(a.getBalance())
|
||||
.branchId(a.getAccNumber())
|
||||
.customerId(a.getCustomerId())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static final Set<String> VALID_COLUMNS_FOR_ORDER_BY = Stream.of("acc_number", "branch_id", "balance")
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id
|
||||
*
|
||||
* @param customerId
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> safeFindAccountsByCustomerId(String customerId, String orderBy) {
|
||||
public List<AccountDTO> safeFindAccountsByCustomerId(String customerId, String orderBy) {
|
||||
|
||||
String sql = "select " + "customer_id,acc_number,branch_id,balance from Accounts where customer_id = ? ";
|
||||
|
||||
if (VALID_COLUMNS_FOR_ORDER_BY.contains(orderBy)) {
|
||||
sql = sql + " order by " + orderBy;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new IllegalArgumentException("Nice try!");
|
||||
}
|
||||
|
||||
@ -135,35 +217,82 @@ public class AccountDAO {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Map<String,SingularAttribute<Account,?>> VALID_JPA_COLUMNS_FOR_ORDER_BY = Stream.of(
|
||||
new AbstractMap.SimpleEntry<>(Account_.ACC_NUMBER, Account_.accNumber),
|
||||
new AbstractMap.SimpleEntry<>(Account_.BRANCH_ID, Account_.branchId),
|
||||
new AbstractMap.SimpleEntry<>(Account_.BALANCE, Account_.balance)
|
||||
)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
/**
|
||||
* Return all accounts owned by a given customer,given his/her external id
|
||||
*
|
||||
* @param customerId
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> safeJpaFindAccountsByCustomerId(String customerId, String orderBy) {
|
||||
|
||||
SingularAttribute<Account,?> orderByAttribute = VALID_JPA_COLUMNS_FOR_ORDER_BY.get(orderBy);
|
||||
if ( orderByAttribute == null) {
|
||||
throw new IllegalArgumentException("Nice try!");
|
||||
}
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Account> cq = cb.createQuery(Account.class);
|
||||
Root<Account> root = cq.from(Account.class);
|
||||
cq.select(root)
|
||||
.where(cb.equal(root.get(Account_.customerId), customerId))
|
||||
.orderBy(cb.asc(root.get(orderByAttribute)));
|
||||
|
||||
TypedQuery<Account> q = em.createQuery(cq);
|
||||
|
||||
return q.getResultList()
|
||||
.stream()
|
||||
.map(a -> AccountDTO.builder()
|
||||
.accNumber(a.getAccNumber())
|
||||
.balance(a.getBalance())
|
||||
.branchId(a.getAccNumber())
|
||||
.customerId(a.getCustomerId())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid placeholder usage example
|
||||
*
|
||||
* @param tableName
|
||||
* @return
|
||||
*/
|
||||
public List<AccountDTO> wrongCountRecordsByTableName(String tableName) {
|
||||
public Long wrongCountRecordsByTableName(String tableName) {
|
||||
|
||||
try (Connection c = dataSource.getConnection(); PreparedStatement p = c.prepareStatement("select count(*) from ?")) {
|
||||
|
||||
try (Connection c = dataSource.getConnection();
|
||||
PreparedStatement p = c.prepareStatement("select count(*) from ?")) {
|
||||
|
||||
p.setString(1, tableName);
|
||||
ResultSet rs = p.executeQuery();
|
||||
List<AccountDTO> accounts = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
AccountDTO acc = AccountDTO.builder()
|
||||
.customerId(rs.getString("customerId"))
|
||||
.branchId(rs.getString("branch_id"))
|
||||
.accNumber(rs.getString("acc_number"))
|
||||
.balance(rs.getBigDecimal("balance"))
|
||||
.build();
|
||||
rs.next();
|
||||
return rs.getLong(1);
|
||||
|
||||
accounts.add(acc);
|
||||
}
|
||||
|
||||
return accounts;
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid placeholder usage example - JPA
|
||||
*
|
||||
* @param tableName
|
||||
* @return
|
||||
*/
|
||||
public Long wrongJpaCountRecordsByTableName(String tableName) {
|
||||
|
||||
String jql = "select count(*) from :tableName";
|
||||
TypedQuery<Long> q = em.createQuery(jql, Long.class)
|
||||
.setParameter("tableName", tableName);
|
||||
|
||||
return q.getSingleResult();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
|
@ -1,19 +0,0 @@
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet id="create-tables" author="baeldung">
|
||||
<createTable tableName="Accounts" >
|
||||
<column name="id" autoIncrement="true" type="BIGINT" remarks="Internal account PK" >
|
||||
<constraints primaryKey="true"/>
|
||||
</column>
|
||||
<column name="customer_id" type="java.sql.Types.VARCHAR(32)" remarks="External Customer Id"></column>
|
||||
<column name="acc_number" type="java.sql.Types.VARCHAR(128)" remarks="External Account Number"></column>
|
||||
<column name="branch_id" type="java.sql.Types.VARCHAR(32)"></column>
|
||||
<column name="balance" type="CURRENCY"></column>
|
||||
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
@ -1,8 +0,0 @@
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<include file="changelog/create-tables.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
@ -40,6 +40,15 @@ public class SqlInjectionSamplesApplicationUnitTest {
|
||||
assertThat(accounts).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAVulnerableJpaMethod_whenHackedCustomerId_thenReturnAllAccounts() {
|
||||
|
||||
List<AccountDTO> accounts = target.unsafeJpaFindAccountsByCustomerId("C1' or '1'='1");
|
||||
assertThat(accounts).isNotNull();
|
||||
assertThat(accounts).isNotEmpty();
|
||||
assertThat(accounts).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenASafeMethod_whenHackedCustomerId_thenReturnNoAccounts() {
|
||||
|
||||
@ -48,13 +57,36 @@ public class SqlInjectionSamplesApplicationUnitTest {
|
||||
assertThat(accounts).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenASafeJpaMethod_whenHackedCustomerId_thenReturnNoAccounts() {
|
||||
|
||||
List<AccountDTO> accounts = target.safeJpaFindAccountsByCustomerId("C1' or '1'='1");
|
||||
assertThat(accounts).isNotNull();
|
||||
assertThat(accounts).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void givenASafeJpaCriteriaMethod_whenHackedCustomerId_thenReturnNoAccounts() {
|
||||
|
||||
List<AccountDTO> accounts = target.safeJpaCriteriaFindAccountsByCustomerId("C1' or '1'='1");
|
||||
assertThat(accounts).isNotNull();
|
||||
assertThat(accounts).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void givenASafeMethod_whenInvalidOrderBy_thenThroweException() {
|
||||
target.safeFindAccountsByCustomerId("C1", "INVALID");
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
@Test(expected = Exception.class)
|
||||
public void givenWrongPlaceholderUsageMethod_whenNormalCall_thenThrowsException() {
|
||||
target.wrongCountRecordsByTableName("Accounts");
|
||||
}
|
||||
|
||||
@Test(expected = Exception.class)
|
||||
public void givenWrongJpaPlaceholderUsageMethod_whenNormalCall_thenThrowsException() {
|
||||
target.wrongJpaCountRecordsByTableName("Accounts");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,5 +2,17 @@
|
||||
# Test profile configuration
|
||||
#
|
||||
spring:
|
||||
liquibase:
|
||||
change-log: db/changelog/db.changelog-master.xml
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
|
||||
datasource:
|
||||
initialization-mode: always
|
||||
initialization-mode: embedded
|
||||
|
||||
logging:
|
||||
level:
|
||||
sql: DEBUG
|
||||
|
@ -1,4 +1,5 @@
|
||||
create table Accounts (
|
||||
id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
|
||||
customer_id varchar(16) not null,
|
||||
acc_number varchar(16) not null,
|
||||
branch_id decimal(8,0),
|
||||
|
@ -3,15 +3,14 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung.spring-boot-crud</groupId>
|
||||
<artifactId>spring-boot-crud</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<name>spring-boot-crud</name>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.6.RELEASE</version>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
@ -41,11 +40,7 @@
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>spring-boot-crud</finalName>
|
||||
@ -62,4 +57,10 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -6,10 +6,10 @@
|
||||
<!-- this needs to use the boot parent directly in order to not inherit logback dependencies -->
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
</parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -6,10 +6,10 @@
|
||||
<!-- this needs to use the boot parent directly in order to not inherit logback dependencies -->
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
</parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -1,66 +1,77 @@
|
||||
<?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-boot-mvc</artifactId>
|
||||
<name>spring-boot-mvc</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Module For Spring Boot MVC</description>
|
||||
<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-boot-mvc</artifactId>
|
||||
<name>spring-boot-mvc</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Module For Spring Boot MVC</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
<parent>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--JSF -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.faces</artifactId>
|
||||
<version>2.3.7</version>
|
||||
</dependency>
|
||||
|
||||
<!--Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--JSF -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.faces</artifactId>
|
||||
<version>2.3.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ROME for RSS -->
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
</dependency>
|
||||
<!--Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Validation -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- ROME for RSS -->
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
<version>${rome.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Fox 2 -->
|
||||
<!--Validation -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Fox 2 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
@ -77,31 +88,27 @@
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.baeldung.springbootmvc.SpringBootMvcApplication</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.baeldung.springbootmvc.SpringBootMvcApplication</mainClass>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<spring.fox.version>2.9.2</spring.fox.version>
|
||||
<!-- ROME for RSS -->
|
||||
<rome.version>1.10.0</rome.version>
|
||||
<start-class>com.baeldung.springbootmvc.SpringBootMvcApplication</start-class>
|
||||
</properties>
|
||||
<properties>
|
||||
<spring.fox.version>2.9.2</spring.fox.version>
|
||||
<!-- ROME for RSS -->
|
||||
<rome.version>1.10.0</rome.version>
|
||||
<start-class>com.baeldung.springbootmvc.SpringBootMvcApplication</start-class>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
@ -9,5 +9,4 @@ public class App {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ public class Controller {
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/index")
|
||||
public ModelAndView index(Map<String, Object> model) {
|
||||
public ModelAndView thymeleafView(Map<String, Object> model) {
|
||||
model.put("number", 1234);
|
||||
model.put("message", "Hello from Spring MVC");
|
||||
return new ModelAndView("/index");
|
||||
return new ModelAndView("thymeleaf/index");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,2 @@
|
||||
spring.main.allow-bean-definition-overriding=true
|
||||
spring.mvc.view.prefix=/WEB-INF/jsp/
|
||||
spring.mvc.view.suffix=.jsp
|
||||
spring.thymeleaf.view-names=thymeleaf/*
|
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Access Spring MVC params</title>
|
||||
<script src="/js/jquery.js"></script>
|
||||
<script src="/js/script-async.js"></script>
|
||||
<script src="/js/script-async-jquery.js"></script>
|
||||
<script>
|
||||
var number = [[${number}]];
|
||||
var message = "[[${message}]]";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Number=
|
||||
<span th:text="${number}" th:remove="tag"></span>
|
||||
<br /> Message=
|
||||
<span th:text="${message}" th:remove="tag"></span>
|
||||
<h2>Data from the external JS file (due to loading order)</h2>
|
||||
<div id="number-ext"></div>
|
||||
<div id="message-ext"></div>
|
||||
<h2>Asynchronous loading from external JS file (plain JS)</h2>
|
||||
<div id="number-async"></div>
|
||||
<div id="message-async"></div>
|
||||
<h2>Asynchronous loading from external JS file (jQuery)</h2>
|
||||
<div id="number-async-jquery"></div>
|
||||
<div id="message-async-jquery"></div>
|
||||
</body>
|
||||
<script src="/js/script.js"></script>
|
||||
</html>
|
@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Access Spring MVC params</title>
|
||||
<script src="/js/jquery.js"></script>
|
||||
<script src="/js/script-async.js"></script>
|
||||
<script src="/js/script-async-jquery.js"></script>
|
||||
<script>
|
||||
var number = <c:out value="${number}"></c:out>;
|
||||
var message = "<c:out value="${message}"></c:out>";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Data from the external JS file (due to loading order)</h2>
|
||||
<div id="number-ext"></div>
|
||||
<div id="message-ext"></div>
|
||||
<h2>Asynchronous loading from external JS file (plain JS)</h2>
|
||||
<div id="number-async"></div>
|
||||
<div id="message-async"></div>
|
||||
<h2>Asynchronous loading from external JS file (jQuery)</h2>
|
||||
<div id="number-async-jquery"></div>
|
||||
<div id="message-async-jquery"></div>
|
||||
|
||||
</body>
|
||||
<script src="/js/script.js"></script>
|
||||
</html>
|
@ -20,9 +20,10 @@ public class ControllerUnitTest {
|
||||
private MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void whenRequestIndex_thenStatusOk() throws Exception {
|
||||
public void whenRequestThymeleaf_thenStatusOk() throws Exception {
|
||||
mvc.perform(MockMvcRequestBuilders.get("/index")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,3 +4,5 @@ Module for the articles that are part of the Spring REST E-book:
|
||||
2. [Error Handling for REST with Spring](http://www.baeldung.com/exception-handling-for-rest-with-spring)
|
||||
3. [REST Pagination in Spring](http://www.baeldung.com/rest-api-pagination-in-spring)
|
||||
4. [Build a REST API with Spring and Java Config](http://www.baeldung.com/building-a-restful-web-service-with-spring-and-java-based-configuration)
|
||||
5. [HATEOAS for a Spring REST Service](http://www.baeldung.com/rest-api-discoverability-with-spring)
|
||||
6. [REST API Discoverability and HATEOAS](http://www.baeldung.com/restful-web-service-discoverability)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.baeldung.common.web;
|
||||
package com.baeldung.common.web;
|
||||
|
||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
@ -8,8 +8,8 @@ import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.baeldung.persistence.model.Foo;
|
||||
import org.baeldung.web.util.HTTPLinkHeaderUtil;
|
||||
import com.baeldung.persistence.model.Foo;
|
||||
import com.baeldung.web.util.HTTPLinkHeaderUtil;
|
||||
import org.hamcrest.core.AnyOf;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
@ -1,10 +1,10 @@
|
||||
package org.baeldung.web;
|
||||
package com.baeldung.web;
|
||||
|
||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||
|
||||
import org.baeldung.common.web.AbstractDiscoverabilityLiveTest;
|
||||
import org.baeldung.persistence.model.Foo;
|
||||
import org.baeldung.spring.ConfigIntegrationTest;
|
||||
import com.baeldung.common.web.AbstractDiscoverabilityLiveTest;
|
||||
import com.baeldung.persistence.model.Foo;
|
||||
import com.baeldung.spring.ConfigIntegrationTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
@ -1,11 +1,13 @@
|
||||
package com.baeldung.web;
|
||||
|
||||
import com.baeldung.web.FooDiscoverabilityLiveTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
// @formatter:off
|
||||
FooDiscoverabilityLiveTest.class,
|
||||
FooLiveTest.class
|
||||
,FooPageableLiveTest.class
|
||||
}) //
|
||||
|
@ -8,8 +8,6 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||
The "Learn Spring Security" Classes: http://github.learnspringsecurity.com
|
||||
|
||||
### Relevant Articles:
|
||||
- [HATEOAS for a Spring REST Service](http://www.baeldung.com/rest-api-discoverability-with-spring)
|
||||
- [REST API Discoverability and HATEOAS](http://www.baeldung.com/restful-web-service-discoverability)
|
||||
- [ETags for REST with Spring](http://www.baeldung.com/etags-for-rest-with-spring)
|
||||
- [Integration Testing with the Maven Cargo plugin](http://www.baeldung.com/integration-testing-with-the-maven-cargo-plugin)
|
||||
- [Introduction to Spring Data JPA](http://www.baeldung.com/the-persistence-layer-with-spring-data-jpa)
|
||||
|
@ -7,7 +7,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.baeldung.persistence.model.Foo;
|
||||
import org.baeldung.persistence.service.IFooService;
|
||||
import org.baeldung.web.hateoas.event.ResourceCreatedEvent;
|
||||
import org.baeldung.web.hateoas.event.SingleResourceRetrievedEvent;
|
||||
import org.baeldung.web.util.RestPreconditions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@ -53,7 +52,6 @@ public class FooController {
|
||||
public Foo findById(@PathVariable("id") final Long id, final HttpServletResponse response) {
|
||||
final Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
|
||||
|
||||
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
|
||||
return resourceById;
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,14 @@
|
||||
package org.baeldung.web.controller;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.baeldung.web.metric.IActuatorMetricService;
|
||||
import org.baeldung.web.metric.IMetricService;
|
||||
import org.baeldung.web.util.LinkUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = "/auth/")
|
||||
@ -34,18 +26,6 @@ public class RootController {
|
||||
|
||||
// API
|
||||
|
||||
// discover
|
||||
|
||||
@RequestMapping(value = "admin", method = RequestMethod.GET)
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
|
||||
final String rootUri = request.getRequestURL().toString();
|
||||
|
||||
final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo");
|
||||
final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
|
||||
response.addHeader("Link", linkToFoo);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/metric", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public Map getMetric() {
|
||||
|
@ -1,22 +0,0 @@
|
||||
package org.baeldung.web.hateoas.event;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class SingleResourceRetrievedEvent extends ApplicationEvent {
|
||||
private final HttpServletResponse response;
|
||||
|
||||
public SingleResourceRetrievedEvent(final Object source, final HttpServletResponse response) {
|
||||
super(source);
|
||||
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
public HttpServletResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package org.baeldung.web.hateoas.listener;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.baeldung.web.hateoas.event.SingleResourceRetrievedEvent;
|
||||
import org.baeldung.web.util.LinkUtil;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
@Component
|
||||
class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener<SingleResourceRetrievedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(final SingleResourceRetrievedEvent resourceRetrievedEvent) {
|
||||
Preconditions.checkNotNull(resourceRetrievedEvent);
|
||||
|
||||
final HttpServletResponse response = resourceRetrievedEvent.getResponse();
|
||||
addLinkHeaderOnSingleResourceRetrieval(response);
|
||||
}
|
||||
|
||||
void addLinkHeaderOnSingleResourceRetrieval(final HttpServletResponse response) {
|
||||
final String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri().build().toUri().toASCIIString();
|
||||
final int positionOfLastSlash = requestURL.lastIndexOf("/");
|
||||
final String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash);
|
||||
|
||||
final String linkHeaderValue = LinkUtil.createLinkHeader(uriForResourceCreation, "collection");
|
||||
response.addHeader(HttpHeaders.LINK, linkHeaderValue);
|
||||
}
|
||||
|
||||
}
|
@ -6,8 +6,7 @@ import org.junit.runners.Suite;
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
// @formatter:off
|
||||
FooDiscoverabilityLiveTest.class
|
||||
,FooLiveTest.class
|
||||
FooLiveTest.class
|
||||
}) //
|
||||
public class LiveTestSuiteLiveTest {
|
||||
|
||||
|
@ -1,36 +0,0 @@
|
||||
package org.baeldung.web.util;
|
||||
|
||||
public final class HTTPLinkHeaderUtil {
|
||||
|
||||
private HTTPLinkHeaderUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static String extractURIByRel(final String linkHeader, final String rel) {
|
||||
if (linkHeader == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String uriWithSpecifiedRel = null;
|
||||
final String[] links = linkHeader.split(", ");
|
||||
String linkRelation;
|
||||
for (final String link : links) {
|
||||
final int positionOfSeparator = link.indexOf(';');
|
||||
linkRelation = link.substring(positionOfSeparator + 1, link.length()).trim();
|
||||
if (extractTypeOfRelation(linkRelation).equals(rel)) {
|
||||
uriWithSpecifiedRel = link.substring(1, positionOfSeparator - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return uriWithSpecifiedRel;
|
||||
}
|
||||
|
||||
private static Object extractTypeOfRelation(final String linkRelation) {
|
||||
final int positionOfEquals = linkRelation.indexOf('=');
|
||||
return linkRelation.substring(positionOfEquals + 2, linkRelation.length() - 1).trim();
|
||||
}
|
||||
|
||||
}
|
66
spring-security-cors/pom.xml
Normal file
66
spring-security-cors/pom.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?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>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>spring-security-cors</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-security-cors</name>
|
||||
<description>Spring Security CORS</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,14 @@
|
||||
package com.baeldung.springbootsecuritycors.basicauth;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.baeldung.springbootsecuritycors")
|
||||
@EnableAutoConfiguration
|
||||
public class SpringBootSecurityApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSecurityApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.baeldung.springbootsecuritycors.basicauth.config;
|
||||
|
||||
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;
|
||||
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
http.cors(); //disable this line to reproduce the CORS 401
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.baeldung.springbootsecuritycors.controller;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin("http://localhost:4200")
|
||||
public class ResourceController {
|
||||
|
||||
@GetMapping("/user")
|
||||
public String user(Principal principal) {
|
||||
return principal.getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.baeldung.springbootsecuritycors;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import com.baeldung.springbootsecuritycors.basicauth.SpringBootSecurityApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = { SpringBootSecurityApplication.class })
|
||||
public class ResourceControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext wac;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
|
||||
.apply(SecurityMockMvcConfigurers.springSecurity())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPreFlightRequest_whenPerfomed_shouldReturnOK() throws Exception {
|
||||
mockMvc.perform(options("/user")
|
||||
.header("Access-Control-Request-Method", "GET")
|
||||
.header("Origin", "http://localhost:4200"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
@ -8,13 +8,15 @@
|
||||
<version>1.0.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-boot.version>2.1.2.RELEASE</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
Loading…
x
Reference in New Issue
Block a user