Merge branch 'eugenp:master' into JAVA-20167_1
This commit is contained in:
commit
4000ff6ce9
|
@ -0,0 +1,56 @@
|
|||
package com.baeldung.listandset.benchmark;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.openjdk.jmh.runner.options.Options;
|
||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.SingleShotTime)
|
||||
@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.MILLISECONDS)
|
||||
public class ListAndSetAddBenchmark {
|
||||
|
||||
public static void main(String[] args) throws IOException, RunnerException {
|
||||
Options opt = new OptionsBuilder()
|
||||
.include(ListAndSetAddBenchmark.class.getSimpleName())
|
||||
.forks(1)
|
||||
.addProfiler("gc")
|
||||
.build();
|
||||
new Runner(opt).run();
|
||||
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void addElementToArrayList(Params param, Blackhole blackhole) {
|
||||
param.arrayList.clear();
|
||||
for (int i = 0; i < param.addNumber; i++) {
|
||||
blackhole.consume(param.arrayList.add(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void addElementToHashSet(Params param, Blackhole blackhole) {
|
||||
param.hashSet.clear();
|
||||
for (int i = 0; i < param.addNumber; i++) {
|
||||
blackhole.consume(param.hashSet.add(i));
|
||||
}
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class Params {
|
||||
public int addNumber = 10000000;
|
||||
|
||||
public List<Integer> arrayList = new ArrayList<>();
|
||||
public Set<Integer> hashSet = new HashSet<>();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.baeldung.listandset.benchmark;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
import org.openjdk.jmh.runner.options.Options;
|
||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.SingleShotTime)
|
||||
@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.MILLISECONDS)
|
||||
public class ListAndSetContainsBenchmark {
|
||||
|
||||
public static void main(String[] args) throws IOException, RunnerException {
|
||||
Options opt = new OptionsBuilder()
|
||||
.include(ListAndSetContainsBenchmark.class.getSimpleName())
|
||||
.forks(1)
|
||||
.addProfiler("gc")
|
||||
.build();
|
||||
new Runner(opt).run();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Benchmark
|
||||
public void searchElementInArrayList(Params param, Blackhole blackhole) {
|
||||
|
||||
blackhole.consume(param.arrayList.contains(param.searchElement));
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void searchElementInHashSet(Params param, Blackhole blackhole) {
|
||||
|
||||
blackhole.consume(param.hashSet.contains(param.searchElement));
|
||||
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class Params {
|
||||
@Param({"5000000"})
|
||||
public int searchElement;
|
||||
|
||||
@Param({"10000000"})
|
||||
public int collectionSize;
|
||||
|
||||
public List<Integer> arrayList;
|
||||
public Set<Integer> hashSet;
|
||||
|
||||
@Setup(Level.Iteration)
|
||||
public void setup() {
|
||||
arrayList = new ArrayList<>();
|
||||
hashSet = new HashSet<>();
|
||||
for (int i = 0; i < collectionSize; i++) {
|
||||
arrayList.add(i);
|
||||
hashSet.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.listtojson;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
import org.json.JSONArray;
|
||||
import org.junit.Assert;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
import java.util.List;
|
||||
|
||||
public class ListToJsonArrayUnitTest {
|
||||
public List<String> list = Arrays.asList("Article 1", "Article 2", "Article 3");
|
||||
public String expectedJsonArray = "[\"Article 1\",\"Article 2\",\"Article 3\"]";
|
||||
|
||||
@Test
|
||||
public void given_JavaList_whenUsingJacksonLibrary_thenOutJsonArray() throws JsonProcessingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String jsonArray = objectMapper.writeValueAsString(list);
|
||||
Assert.assertEquals(expectedJsonArray, jsonArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void given_JavaList_whenUsingGsonLibrary_thenOutJsonArray() {
|
||||
Gson gson = new Gson();
|
||||
String jsonArray = gson.toJson(list);
|
||||
Assert.assertEquals(expectedJsonArray, jsonArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void given_JavaList_whenOrgJsonLibrary_thenOutJsonAray() {
|
||||
JSONArray jsonArray = new JSONArray(list);
|
||||
Assert.assertEquals(expectedJsonArray, jsonArray.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,14 @@
|
|||
<artifactId>core-java-modules</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${apache.commons-lang.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -29,6 +37,7 @@
|
|||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<apache.commons-lang.version>3.12.0</apache.commons-lang.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,104 @@
|
|||
package com.baeldung.checkcase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CaseChecker {
|
||||
|
||||
static boolean allUpper1(String input) {
|
||||
return input.equals(input.toUpperCase());
|
||||
}
|
||||
|
||||
static boolean allLower1(String input) {
|
||||
return input.equals(input.toLowerCase());
|
||||
}
|
||||
|
||||
static boolean allUpper2(String input) {
|
||||
for (char c : input.toCharArray()) {
|
||||
// don't write in this way: if (!Character.isUpperCase(c))
|
||||
if (Character.isLowerCase(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean allLower2(String input) {
|
||||
for (char c : input.toCharArray()) {
|
||||
// don't write in this way: if (!Character.isLowerCase(c))
|
||||
if (Character.isUpperCase(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean allUpper3(String input) {
|
||||
return input.chars()
|
||||
.noneMatch(Character::isLowerCase);
|
||||
}
|
||||
|
||||
static boolean allLower3(String input) {
|
||||
return input.chars()
|
||||
.noneMatch(Character::isUpperCase);
|
||||
}
|
||||
}
|
||||
|
||||
public class StringAllUpperOrLowercaseUnitTest {
|
||||
private static final String UPPER_INPUT = "1: COOL!";
|
||||
private static final String LOWER_INPUT = "2: cool!";
|
||||
private static final String MIXED_INPUT = "3: Cool!";
|
||||
|
||||
@Test
|
||||
void whenComparingToConvertedString_thenGetExpectedResult() {
|
||||
assertTrue(CaseChecker.allLower1(LOWER_INPUT));
|
||||
assertFalse(CaseChecker.allLower1(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allLower1(MIXED_INPUT));
|
||||
|
||||
assertFalse(CaseChecker.allUpper1(LOWER_INPUT));
|
||||
assertTrue(CaseChecker.allUpper1(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allUpper1(MIXED_INPUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenCheckCharInArray_thenGetExpectedResult() {
|
||||
assertTrue(CaseChecker.allLower2(LOWER_INPUT));
|
||||
assertFalse(CaseChecker.allLower2(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allLower2(MIXED_INPUT));
|
||||
|
||||
assertFalse(CaseChecker.allUpper2(LOWER_INPUT));
|
||||
assertTrue(CaseChecker.allUpper2(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allUpper2(MIXED_INPUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUsingStream_thenGetExpectedResult() {
|
||||
assertTrue(CaseChecker.allLower3(LOWER_INPUT));
|
||||
assertFalse(CaseChecker.allLower3(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allLower3(MIXED_INPUT));
|
||||
|
||||
assertFalse(CaseChecker.allUpper3(LOWER_INPUT));
|
||||
assertTrue(CaseChecker.allUpper3(UPPER_INPUT));
|
||||
assertFalse(CaseChecker.allUpper3(MIXED_INPUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUsingApacheCommons_thenGetExpectedResult() {
|
||||
assertFalse(StringUtils.isAllLowerCase(LOWER_INPUT));
|
||||
assertFalse(StringUtils.isAllLowerCase(UPPER_INPUT));
|
||||
assertFalse(StringUtils.isAllLowerCase(MIXED_INPUT));
|
||||
|
||||
assertFalse(StringUtils.isAllLowerCase("a b"));
|
||||
assertTrue(StringUtils.isAllLowerCase("ab"));
|
||||
|
||||
assertFalse(StringUtils.isAllUpperCase(LOWER_INPUT));
|
||||
assertFalse(StringUtils.isAllUpperCase(UPPER_INPUT));
|
||||
assertFalse(StringUtils.isAllUpperCase(MIXED_INPUT));
|
||||
|
||||
assertFalse(StringUtils.isAllUpperCase("A B"));
|
||||
assertTrue(StringUtils.isAllUpperCase("AB"));
|
||||
}
|
||||
}
|
|
@ -12,10 +12,8 @@ public class Citizen {
|
|||
public Citizen() {
|
||||
}
|
||||
|
||||
public Citizen(EncryptedCitizen encryptedCitizen) {
|
||||
if (encryptedCitizen != null) {
|
||||
this.name = encryptedCitizen.getName();
|
||||
}
|
||||
public Citizen(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
|
|
@ -13,8 +13,8 @@ public class EncryptedCitizen {
|
|||
public EncryptedCitizen() {
|
||||
}
|
||||
|
||||
public EncryptedCitizen(Citizen citizen) {
|
||||
this.name = citizen.getName();
|
||||
public EncryptedCitizen(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
|
|
@ -39,7 +39,7 @@ public class CitizenService {
|
|||
if (encryptionConfig.isAutoEncryption()) {
|
||||
return mongo.save(citizen);
|
||||
} else {
|
||||
EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen);
|
||||
EncryptedCitizen encryptedCitizen = new EncryptedCitizen(citizen.getName());
|
||||
encryptedCitizen.setEmail(encrypt(citizen.getEmail(), DETERMINISTIC_ALGORITHM));
|
||||
encryptedCitizen.setBirthYear(encrypt(citizen.getBirthYear(), RANDOM_ALGORITHM));
|
||||
|
||||
|
@ -77,19 +77,10 @@ public class CitizenService {
|
|||
}
|
||||
}
|
||||
|
||||
public Binary encrypt(Object value, String algorithm) {
|
||||
if (value == null)
|
||||
public Binary encrypt(BsonValue bsonValue, String algorithm) {
|
||||
if (bsonValue == null)
|
||||
return null;
|
||||
|
||||
BsonValue bsonValue;
|
||||
if (value instanceof Integer) {
|
||||
bsonValue = new BsonInt32((Integer) value);
|
||||
} else if (value instanceof String) {
|
||||
bsonValue = new BsonString((String) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported type: " + value.getClass());
|
||||
}
|
||||
|
||||
EncryptOptions options = new EncryptOptions(algorithm);
|
||||
options.keyId(encryptionConfig.getDataKeyId());
|
||||
|
||||
|
@ -97,6 +88,20 @@ public class CitizenService {
|
|||
return new Binary(encryptedValue.getType(), encryptedValue.getData());
|
||||
}
|
||||
|
||||
public Binary encrypt(String value, String algorithm) {
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return encrypt(new BsonString(value), algorithm);
|
||||
}
|
||||
|
||||
public Binary encrypt(Integer value, String algorithm) {
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return encrypt(new BsonInt32(value), algorithm);
|
||||
}
|
||||
|
||||
public BsonValue decryptProperty(Binary value) {
|
||||
if (value == null)
|
||||
return null;
|
||||
|
@ -108,7 +113,7 @@ public class CitizenService {
|
|||
if (encrypted == null)
|
||||
return null;
|
||||
|
||||
Citizen citizen = new Citizen(encrypted);
|
||||
Citizen citizen = new Citizen(encrypted.getName());
|
||||
|
||||
BsonValue decryptedBirthYear = decryptProperty(encrypted.getBirthYear());
|
||||
if (decryptedBirthYear != null) {
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<module>spring-mvc-forms-thymeleaf</module>
|
||||
<module>spring-mvc-java</module>
|
||||
<module>spring-mvc-java-2</module>
|
||||
<module>spring-mvc-java-3</module>
|
||||
<module>spring-mvc-velocity</module>
|
||||
<module>spring-mvc-views</module>
|
||||
<module>spring-mvc-webflow</module>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
### Relevant Articles:
|
|
@ -0,0 +1,40 @@
|
|||
<?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-mvc-java-3</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<name>spring-mvc-java-3</name>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>spring-mvc-java-3</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.filters;
|
||||
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
@WebFilter(urlPatterns = "/*")
|
||||
public class CacheRequestContentFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||
if (request instanceof HttpServletRequest) {
|
||||
String contentType = request.getContentType();
|
||||
if (contentType == null || !contentType.contains("multipart/form-data")) {
|
||||
request = new ContentCachingRequestWrapper((HttpServletRequest) request);
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.baeldung.filters;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class HttpRequestUnitTest {
|
||||
|
||||
@Test
|
||||
public void givenHttpServletRequest_whenCalling_getReaderAfter_getInputStream_thenThrowIllegalStateException() throws IOException {
|
||||
HttpServletRequest request = new MockHttpServletRequest();
|
||||
try (ServletInputStream ignored = request.getInputStream()) {
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, request::getReader);
|
||||
assertEquals("Cannot call getReader() after getInputStream() has already been called for the current request",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenServletRequest_whenDoFilter_thenCanCallBoth() throws ServletException, IOException {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
Filter filter = new CacheRequestContentFilter();
|
||||
filter.doFilter(req, res, chain);
|
||||
|
||||
ServletRequest request = chain.getRequest();
|
||||
assertTrue(request instanceof ContentCachingRequestWrapper);
|
||||
|
||||
// now we can call both getInputStream() and getReader()
|
||||
request.getInputStream();
|
||||
request.getReader();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?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"
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>mockito-2</artifactId>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.baeldung.wantedbutnotinvocked;
|
||||
|
||||
class Helper {
|
||||
|
||||
String getBaeldungString() {
|
||||
return "Baeldung";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.baeldung.wantedbutnotinvocked;
|
||||
|
||||
class Main {
|
||||
|
||||
Helper helper = new Helper();
|
||||
|
||||
String methodUnderTest(int i) {
|
||||
if (i > 5) {
|
||||
return helper.getBaeldungString();
|
||||
}
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.baeldung.wantedbutnotinvocked;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
class MainUnitTest {
|
||||
|
||||
@Mock
|
||||
Helper helper;
|
||||
|
||||
@InjectMocks
|
||||
Main main = new Main();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenValueUpperThan5_WhenMethodUnderTest_ThenDelegatesToHelperClass() {
|
||||
main.methodUnderTest(7);
|
||||
Mockito.verify(helper)
|
||||
.getBaeldungString();
|
||||
}
|
||||
|
||||
// Uncomment the next line to see the error
|
||||
// @Test
|
||||
void givenValueLowerThan5_WhenMethodUnderTest_ThenDelegatesToGetBaeldungString() {
|
||||
main.methodUnderTest(3);
|
||||
Mockito.verify(helper)
|
||||
.getBaeldungString();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue