Skip to content

Commit d1b9998

Browse files
committed
Add a streaming Json item reader
This commit adds a new Json item reader with two implementations based on Jackson and Gson. Resolves BATCH-2691
1 parent 977741c commit d1b9998

File tree

16 files changed

+1197
-0
lines changed

16 files changed

+1197
-0
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ allprojects {
7474
hibernateValidatorVersion = '6.0.4.Final'
7575
hsqldbVersion = '2.4.0'
7676
jackson2Version = '2.9.2'
77+
gsonVersion = '2.8.5'
7778
javaMailVersion = '1.6.0'
7879
javaxBatchApiVersion = '1.0'
7980
javaxInjectVersion = '1'
@@ -326,6 +327,7 @@ project('spring-batch-infrastructure') {
326327

327328
optional "javax.jms:javax.jms-api:$jmsVersion"
328329
optional "com.fasterxml.jackson.core:jackson-databind:${jackson2Version}"
330+
optional "com.google.code.gson:gson:${gsonVersion}"
329331
compile("org.hibernate:hibernate-core:$hibernateVersion") { dep ->
330332
optional dep
331333
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.1_spec'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.json;
18+
19+
import com.google.gson.JsonSyntaxException;
20+
21+
import org.springframework.batch.item.json.domain.Trade;
22+
23+
/**
24+
* @author Mahmoud Ben Hassine
25+
*/
26+
public class GsonJsonItemReaderFunctionalTests extends JsonItemReaderFunctionalTests {
27+
28+
@Override
29+
protected JsonObjectReader<Trade> getJsonObjectReader() {
30+
return new GsonJsonObjectReader<>(Trade.class);
31+
}
32+
33+
@Override
34+
protected Class<? extends Exception> getJsonParsingException() {
35+
return JsonSyntaxException.class;
36+
}
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.json;
18+
19+
import com.fasterxml.jackson.core.JsonParseException;
20+
21+
import org.springframework.batch.item.json.domain.Trade;
22+
23+
/**
24+
* @author Mahmoud Ben Hassine
25+
*/
26+
public class JacksonJsonItemReaderFunctionalTests extends JsonItemReaderFunctionalTests {
27+
28+
@Override
29+
protected JsonObjectReader<Trade> getJsonObjectReader() {
30+
return new JacksonJsonObjectReader<>(Trade.class);
31+
}
32+
33+
@Override
34+
protected Class<? extends Exception> getJsonParsingException() {
35+
return JsonParseException.class;
36+
}
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.json;
18+
19+
import java.math.BigDecimal;
20+
21+
import org.junit.Assert;
22+
import org.junit.Rule;
23+
import org.junit.Test;
24+
import org.junit.rules.ExpectedException;
25+
26+
import org.springframework.batch.item.ExecutionContext;
27+
import org.springframework.batch.item.ItemStreamException;
28+
import org.springframework.batch.item.json.builder.JsonItemReaderBuilder;
29+
import org.springframework.batch.item.json.domain.Trade;
30+
import org.springframework.core.io.ByteArrayResource;
31+
import org.springframework.core.io.ClassPathResource;
32+
33+
import static org.hamcrest.Matchers.instanceOf;
34+
35+
/**
36+
* @author Mahmoud Ben Hassine
37+
*/
38+
public abstract class JsonItemReaderFunctionalTests {
39+
40+
@Rule
41+
public ExpectedException expectedException = ExpectedException.none();
42+
43+
protected abstract JsonObjectReader<Trade> getJsonObjectReader();
44+
45+
protected abstract Class<? extends Exception> getJsonParsingException();
46+
47+
@Test
48+
public void testJsonReading() throws Exception {
49+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
50+
.jsonObjectReader(getJsonObjectReader())
51+
.resource(new ClassPathResource("org/springframework/batch/item/json/trades.json"))
52+
.name("tradeJsonItemReader")
53+
.build();
54+
55+
itemReader.open(new ExecutionContext());
56+
57+
Trade trade = itemReader.read();
58+
Assert.assertNotNull(trade);
59+
Assert.assertEquals("123", trade.getIsin());
60+
Assert.assertEquals("foo", trade.getCustomer());
61+
Assert.assertEquals(new BigDecimal("1.2"), trade.getPrice());
62+
Assert.assertEquals(1, trade.getQuantity());
63+
64+
trade = itemReader.read();
65+
Assert.assertNotNull(trade);
66+
Assert.assertEquals("456", trade.getIsin());
67+
Assert.assertEquals("bar", trade.getCustomer());
68+
Assert.assertEquals(new BigDecimal("1.4"), trade.getPrice());
69+
Assert.assertEquals(2, trade.getQuantity());
70+
71+
trade = itemReader.read();
72+
Assert.assertNull(trade);
73+
}
74+
75+
@Test
76+
public void testEmptyResource() throws Exception {
77+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
78+
.jsonObjectReader(getJsonObjectReader())
79+
.resource(new ByteArrayResource("[]".getBytes()))
80+
.name("tradeJsonItemReader")
81+
.build();
82+
83+
itemReader.open(new ExecutionContext());
84+
85+
Trade trade = itemReader.read();
86+
Assert.assertNull(trade);
87+
}
88+
89+
@Test
90+
public void testInvalidResourceFormat() {
91+
this.expectedException.expect(ItemStreamException.class);
92+
this.expectedException.expectMessage("Failed to initialize the reader");
93+
this.expectedException.expectCause(instanceOf(IllegalStateException.class));
94+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
95+
.jsonObjectReader(getJsonObjectReader())
96+
.resource(new ByteArrayResource("{}, {}".getBytes()))
97+
.name("tradeJsonItemReader")
98+
.build();
99+
100+
itemReader.open(new ExecutionContext());
101+
}
102+
103+
@Test
104+
public void testInvalidResourceContent() throws Exception {
105+
this.expectedException.expect(getJsonParsingException());
106+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
107+
.jsonObjectReader(getJsonObjectReader())
108+
.resource(new ByteArrayResource("[{]".getBytes()))
109+
.name("tradeJsonItemReader")
110+
.build();
111+
itemReader.open(new ExecutionContext());
112+
113+
itemReader.read();
114+
}
115+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.item.json.domain;
17+
18+
import java.math.BigDecimal;
19+
20+
/**
21+
* @author Mahmoud Ben Hassine
22+
*/
23+
public class Trade {
24+
25+
private String isin = "";
26+
27+
private long quantity = 0;
28+
29+
private BigDecimal price = new BigDecimal(0);
30+
31+
private String customer = "";
32+
33+
public Trade() {
34+
}
35+
36+
public Trade(String isin, long quantity, BigDecimal price, String customer) {
37+
this.isin = isin;
38+
this.quantity = quantity;
39+
this.price = price;
40+
this.customer = customer;
41+
}
42+
43+
public void setCustomer(String customer) {
44+
this.customer = customer;
45+
}
46+
47+
public void setIsin(String isin) {
48+
this.isin = isin;
49+
}
50+
51+
public void setPrice(BigDecimal price) {
52+
this.price = price;
53+
}
54+
55+
public void setQuantity(long quantity) {
56+
this.quantity = quantity;
57+
}
58+
59+
public String getIsin() {
60+
return isin;
61+
}
62+
63+
public BigDecimal getPrice() {
64+
return price;
65+
}
66+
67+
public long getQuantity() {
68+
return quantity;
69+
}
70+
71+
public String getCustomer() {
72+
return customer;
73+
}
74+
75+
@Override
76+
public String toString() {
77+
return "Trade: [isin=" + this.isin + ",quantity=" + this.quantity + ",price=" + this.price + ",customer="
78+
+ this.customer + "]";
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
final int prime = 31;
84+
int result = 1;
85+
result = prime * result + ((customer == null) ? 0 : customer.hashCode());
86+
result = prime * result + ((isin == null) ? 0 : isin.hashCode());
87+
result = prime * result + ((price == null) ? 0 : price.hashCode());
88+
result = prime * result + (int) (quantity ^ (quantity >>> 32));
89+
return result;
90+
}
91+
92+
@Override
93+
public boolean equals(Object obj) {
94+
if (this == obj)
95+
return true;
96+
if (obj == null)
97+
return false;
98+
if (getClass() != obj.getClass())
99+
return false;
100+
Trade other = (Trade) obj;
101+
if (customer == null) {
102+
if (other.customer != null)
103+
return false;
104+
}
105+
else if (!customer.equals(other.customer))
106+
return false;
107+
if (isin == null) {
108+
if (other.isin != null)
109+
return false;
110+
}
111+
else if (!isin.equals(other.isin))
112+
return false;
113+
if (price == null) {
114+
if (other.price != null)
115+
return false;
116+
}
117+
else if (!price.equals(other.price))
118+
return false;
119+
if (quantity != other.quantity)
120+
return false;
121+
return true;
122+
}
123+
124+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"isin": "123",
4+
"quantity": 1,
5+
"price": 1.2,
6+
"customer": "foo"
7+
},
8+
{
9+
"isin": "456",
10+
"quantity": 2,
11+
"price": 1.4,
12+
"customer": "bar"
13+
}
14+
]

0 commit comments

Comments
 (0)