initial commit for json-path
This commit is contained in:
parent
dffa1d2504
commit
596486aea0
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
|
@ -0,0 +1,13 @@
|
||||||
|
*.class
|
||||||
|
|
||||||
|
#folders#
|
||||||
|
/target
|
||||||
|
/neoDb*
|
||||||
|
/data
|
||||||
|
/src/main/webapp/WEB-INF/classes
|
||||||
|
*/META-INF/*
|
||||||
|
|
||||||
|
# Packaged files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>json-path</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<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>org.baeldung</groupId>
|
||||||
|
<artifactId>json-path</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>json-path</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- json-path -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jayway.jsonpath</groupId>
|
||||||
|
<artifactId>json-path</artifactId>
|
||||||
|
<version>${json-path.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- utilities -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>joda-time</groupId>
|
||||||
|
<artifactId>joda-time</artifactId>
|
||||||
|
<version>${joda-time.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>${logback.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- json-path -->
|
||||||
|
<json-path.version>2.1.0</json-path.version>
|
||||||
|
|
||||||
|
<!-- utilities -->
|
||||||
|
<joda-time.version>2.9.2</joda-time.version>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<junit.version>4.12</junit.version>
|
||||||
|
|
||||||
|
<!-- Logging -->
|
||||||
|
<slf4j.version>1.7.14</slf4j.version>
|
||||||
|
<logback.version>1.1.3</logback.version>
|
||||||
|
</properties>
|
||||||
|
</project>
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"tool":
|
||||||
|
{
|
||||||
|
"jsonpath":
|
||||||
|
{
|
||||||
|
"creator":
|
||||||
|
{
|
||||||
|
"name": "Jayway Inc.",
|
||||||
|
"location":
|
||||||
|
[
|
||||||
|
"Malmo",
|
||||||
|
"Stockholm",
|
||||||
|
"Copenhagen",
|
||||||
|
"San Francisco",
|
||||||
|
"Karlskrona",
|
||||||
|
"Halmstad",
|
||||||
|
"Helsingborg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"current release": "2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"book":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Beginning JSON",
|
||||||
|
"author": "Ben Smith",
|
||||||
|
"price": 49.99
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "JSON at Work",
|
||||||
|
"author": "Tom Marrs",
|
||||||
|
"price": 29.99
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "Learn JSON in a DAY",
|
||||||
|
"author": "Acodemy",
|
||||||
|
"price": 8.99
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "JSON: Questions and Answers",
|
||||||
|
"author": "George Duckett",
|
||||||
|
"price": 6.00
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"price range":
|
||||||
|
{
|
||||||
|
"cheap": 10.00,
|
||||||
|
"medium": 20.00
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"username": "oracle",
|
||||||
|
"password":
|
||||||
|
{
|
||||||
|
"current":
|
||||||
|
{
|
||||||
|
"value": "Java_SE_8",
|
||||||
|
"created": 1397754000000
|
||||||
|
},
|
||||||
|
|
||||||
|
"old":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"value": "Java_SE_7",
|
||||||
|
"created": 1312650000000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "sun",
|
||||||
|
"password":
|
||||||
|
{
|
||||||
|
"current":
|
||||||
|
{
|
||||||
|
"value": "Java_SE_6",
|
||||||
|
"created": 1168448400000
|
||||||
|
},
|
||||||
|
|
||||||
|
"old":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"value": "J2SE_5.0",
|
||||||
|
"created": 1099069200000
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"value": "J2SE_1.4",
|
||||||
|
"created": 1025542800000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.baeldung.jsonpath.introduction;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Years;
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.Configuration;
|
||||||
|
import com.jayway.jsonpath.DocumentContext;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
import com.jayway.jsonpath.Option;
|
||||||
|
|
||||||
|
public class ChangingPasswordTest {
|
||||||
|
|
||||||
|
enum Result {
|
||||||
|
SUCCESS, FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream jsonValueInputStream = this.getClass().getClassLoader().getResourceAsStream("intro_user.json");
|
||||||
|
String jsonDataSourceString = new Scanner(jsonValueInputStream, "UTF-8").useDelimiter("\\Z").next();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenChangingPasswordOfNonExistentUser_thenFail() {
|
||||||
|
String failedRequestBody = "{\"username\":\"jayway\", \"new_password\":\"JsonPath\"}";
|
||||||
|
Result result = changingPasswordHelper(failedRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.FAILURE, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenChangingToUnusedPassword_thenSucceed() {
|
||||||
|
String successfulRequestBody = "{\"username\":\"oracle\", \"new_password\":\"Java_SE_9\"}";
|
||||||
|
Result result = changingPasswordHelper(successfulRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.SUCCESS, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenChangingToRecentlyUsedPassword_thenFail() {
|
||||||
|
String failedRequestBody = "{\"username\":\"oracle\", \"new_password\":\"Java_SE_7\"}";
|
||||||
|
Result result = changingPasswordHelper(failedRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.FAILURE, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenChangingToLongTimeAgoPassword_thenSucceed() {
|
||||||
|
String successfulRequestBody = "{\"username\":\"sun\", \"new_password\":\"J2SE_5.0\"}";
|
||||||
|
Result result = changingPasswordHelper(successfulRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.SUCCESS, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result changingPasswordHelper(String requestBody) {
|
||||||
|
DocumentContext requestContext = JsonPath.parse(requestBody);
|
||||||
|
String extractedUsername = requestContext.read("$['username']");
|
||||||
|
String extractedPassword = requestContext.read("$['new_password']");
|
||||||
|
|
||||||
|
DocumentContext jsonContext = JsonPath.parse(jsonDataSourceString);
|
||||||
|
List<String> dataSourceUsername = jsonContext.read("$[?(@.username == '" + extractedUsername + "')]");
|
||||||
|
if (dataSourceUsername.size() == 0)
|
||||||
|
return Result.FAILURE;
|
||||||
|
|
||||||
|
Configuration pathConfiguration = Configuration.builder().options(Option.AS_PATH_LIST).build();
|
||||||
|
List<String> pathToCurrentUser = JsonPath.using(pathConfiguration).parse(jsonDataSourceString).read("$[?(@.username == '" + extractedUsername + "')]");
|
||||||
|
List<Long> passwordCreatedTimeList = jsonContext.read(pathToCurrentUser.get(0) + "['password']['old'][?(@.value == '" + extractedPassword + "')]['created']");
|
||||||
|
if (passwordCreatedTimeList.size() == 0)
|
||||||
|
return Result.SUCCESS;
|
||||||
|
|
||||||
|
Long[] passwordCreatedTimeArray = (passwordCreatedTimeList.toArray(new Long[passwordCreatedTimeList.size()]));
|
||||||
|
Arrays.sort(passwordCreatedTimeArray);
|
||||||
|
DateTime oldDate = new DateTime(passwordCreatedTimeArray[passwordCreatedTimeArray.length - 1]);
|
||||||
|
if (Years.yearsBetween(oldDate, new DateTime()).getYears() <= 10)
|
||||||
|
return Result.FAILURE;
|
||||||
|
|
||||||
|
return Result.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package org.baeldung.jsonpath.introduction;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.DocumentContext;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
|
||||||
|
public class LoggingInTest {
|
||||||
|
|
||||||
|
enum Result {
|
||||||
|
SUCCESS, FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream jsonInputStream = this.getClass().getClassLoader().getResourceAsStream("intro_user.json");
|
||||||
|
String jsonDataSourceString = new Scanner(jsonInputStream, "UTF-8").useDelimiter("\\Z").next();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenLoggingInWithCorrectUserData_thenSucceed() {
|
||||||
|
String correctRequestBody = "{\"username\":\"sun\", \"password\":\"Java_SE_6\"}";
|
||||||
|
Result result = loggingInHelper(correctRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.SUCCESS, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenLoggingInWithIncorrectUserData_thenFail() {
|
||||||
|
String incorrectRequestBody = "{\"username\":\"oracle\", \"password\":\"Java_SE_9\"}";
|
||||||
|
Result result = loggingInHelper(incorrectRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.FAILURE, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result loggingInHelper(String requestBody) {
|
||||||
|
DocumentContext requestContext = JsonPath.parse(requestBody);
|
||||||
|
String extractedUsername = requestContext.read("$['username']");
|
||||||
|
String extractedPassword = requestContext.read("$['password']");
|
||||||
|
List<String> list = JsonPath.parse(jsonDataSourceString).read("$[?(@.username == '" + extractedUsername + "' && @.password.current.value == '" + extractedPassword + "')]");
|
||||||
|
|
||||||
|
if (list.size() == 0)
|
||||||
|
return Result.FAILURE;
|
||||||
|
return Result.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.baeldung.jsonpath.introduction;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.Criteria;
|
||||||
|
import com.jayway.jsonpath.DocumentContext;
|
||||||
|
import com.jayway.jsonpath.Filter;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
import com.jayway.jsonpath.Predicate;
|
||||||
|
|
||||||
|
public class OperationTest {
|
||||||
|
InputStream jsonInputStream = this.getClass().getClassLoader().getResourceAsStream("intro_api.json");
|
||||||
|
String jsonDataSourceString = new Scanner(jsonInputStream, "UTF-8").useDelimiter("\\Z").next();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenJsonPathWithoutPredicates_whenReading_thenCorrect() {
|
||||||
|
String jsonpathCreatorNamePath = "$['tool']['jsonpath']['creator']['name']";
|
||||||
|
String jsonpathCreatorLocationPath = "$['tool']['jsonpath']['creator']['location'][*]";
|
||||||
|
|
||||||
|
DocumentContext jsonContext = JsonPath.parse(jsonDataSourceString);
|
||||||
|
String jsonpathCreatorName = jsonContext.read(jsonpathCreatorNamePath);
|
||||||
|
List<String> jsonpathCreatorLocation = jsonContext.read(jsonpathCreatorLocationPath);
|
||||||
|
|
||||||
|
assertEquals("Jayway Inc.", jsonpathCreatorName);
|
||||||
|
assertThat(jsonpathCreatorLocation.toString(), containsString("Malmo"));
|
||||||
|
assertThat(jsonpathCreatorLocation.toString(), containsString("San Francisco"));
|
||||||
|
assertThat(jsonpathCreatorLocation.toString(), containsString("Helsingborg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenJsonPathWithFilterPredicate_whenReading_thenCorrect() {
|
||||||
|
Filter expensiveFilter = Filter.filter(Criteria.where("price").gt(20.00));
|
||||||
|
List<Map<String, Object>> expensive = JsonPath.parse(jsonDataSourceString).read("$['book'][?]", expensiveFilter);
|
||||||
|
predicateUsageAssertionHelper(expensive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenJsonPathWithCustomizedPredicate_whenReading_thenCorrect() {
|
||||||
|
Predicate expensivePredicate = new Predicate() {
|
||||||
|
public boolean apply(PredicateContext context) {
|
||||||
|
String value = context.item(Map.class).get("price").toString();
|
||||||
|
return Float.valueOf(value) > 20.00;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
List<Map<String, Object>> expensive = JsonPath.parse(jsonDataSourceString).read("$['book'][?]", expensivePredicate);
|
||||||
|
predicateUsageAssertionHelper(expensive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenJsonPathWithInlinePredicate_whenReading_thenCorrect() {
|
||||||
|
List<Map<String, Object>> expensive = JsonPath.parse(jsonDataSourceString).read("$['book'][?(@['price'] > $['price range']['medium'])]");
|
||||||
|
predicateUsageAssertionHelper(expensive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void predicateUsageAssertionHelper(List<?> predicate) {
|
||||||
|
assertThat(predicate.toString(), containsString("Beginning JSON"));
|
||||||
|
assertThat(predicate.toString(), containsString("JSON at Work"));
|
||||||
|
assertThat(predicate.toString(), not(containsString("Learn JSON in a DAY")));
|
||||||
|
assertThat(predicate.toString(), not(containsString("JSON: Questions and Answers")));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.baeldung.jsonpath.introduction;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
|
||||||
|
public class RegisteringAccountTest {
|
||||||
|
|
||||||
|
enum Result {
|
||||||
|
SUCCESS, FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream jsonInputStream = this.getClass().getClassLoader().getResourceAsStream("intro_user.json");
|
||||||
|
String jsonDataSourceString = new Scanner(jsonInputStream, "UTF-8").useDelimiter("\\Z").next();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenRegisteringUnusedAccount_thenSucceed() {
|
||||||
|
String unusedRequestBody = "{\"username\":\"jayway\", \"password\":\"JsonPath\"}";
|
||||||
|
Result result = registeringNewAccountHelper(unusedRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.SUCCESS, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenUseCase_whenRegisteringUsedAccount_thenFail() {
|
||||||
|
String usedRequestBody = "{\"username\":\"oracle\", \"password\":\"Java_SE_9\"}";
|
||||||
|
Result result = registeringNewAccountHelper(usedRequestBody);
|
||||||
|
|
||||||
|
assertEquals(Result.FAILURE, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result registeringNewAccountHelper(String requestBody) {
|
||||||
|
List<String> userDataSource = JsonPath.parse(jsonDataSourceString).read("$[*]['username']");
|
||||||
|
String extractedUsername = JsonPath.parse(requestBody).read("$['username']");
|
||||||
|
|
||||||
|
if (userDataSource.toString().contains(extractedUsername))
|
||||||
|
return Result.FAILURE;
|
||||||
|
return Result.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue