Skip to content

Commit 5281d6d

Browse files
fmbenhassinemminella
authored andcommitted
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 0dab67c commit 5281d6d

File tree

16 files changed

+1230
-0
lines changed

16 files changed

+1230
-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,118 @@
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.hamcrest.Matchers;
22+
import org.junit.Assert;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
26+
27+
import org.springframework.batch.item.ExecutionContext;
28+
import org.springframework.batch.item.ItemStreamException;
29+
import org.springframework.batch.item.ParseException;
30+
import org.springframework.batch.item.json.builder.JsonItemReaderBuilder;
31+
import org.springframework.batch.item.json.domain.Trade;
32+
import org.springframework.core.io.ByteArrayResource;
33+
import org.springframework.core.io.ClassPathResource;
34+
35+
import static org.hamcrest.Matchers.instanceOf;
36+
37+
/**
38+
* @author Mahmoud Ben Hassine
39+
*/
40+
public abstract class JsonItemReaderFunctionalTests {
41+
42+
@Rule
43+
public ExpectedException expectedException = ExpectedException.none();
44+
45+
protected abstract JsonObjectReader<Trade> getJsonObjectReader();
46+
47+
protected abstract Class<? extends Exception> getJsonParsingException();
48+
49+
@Test
50+
public void testJsonReading() throws Exception {
51+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
52+
.jsonObjectReader(getJsonObjectReader())
53+
.resource(new ClassPathResource("org/springframework/batch/item/json/trades.json"))
54+
.name("tradeJsonItemReader")
55+
.build();
56+
57+
itemReader.open(new ExecutionContext());
58+
59+
Trade trade = itemReader.read();
60+
Assert.assertNotNull(trade);
61+
Assert.assertEquals("123", trade.getIsin());
62+
Assert.assertEquals("foo", trade.getCustomer());
63+
Assert.assertEquals(new BigDecimal("1.2"), trade.getPrice());
64+
Assert.assertEquals(1, trade.getQuantity());
65+
66+
trade = itemReader.read();
67+
Assert.assertNotNull(trade);
68+
Assert.assertEquals("456", trade.getIsin());
69+
Assert.assertEquals("bar", trade.getCustomer());
70+
Assert.assertEquals(new BigDecimal("1.4"), trade.getPrice());
71+
Assert.assertEquals(2, trade.getQuantity());
72+
73+
trade = itemReader.read();
74+
Assert.assertNull(trade);
75+
}
76+
77+
@Test
78+
public void testEmptyResource() throws Exception {
79+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
80+
.jsonObjectReader(getJsonObjectReader())
81+
.resource(new ByteArrayResource("[]".getBytes()))
82+
.name("tradeJsonItemReader")
83+
.build();
84+
85+
itemReader.open(new ExecutionContext());
86+
87+
Trade trade = itemReader.read();
88+
Assert.assertNull(trade);
89+
}
90+
91+
@Test
92+
public void testInvalidResourceFormat() {
93+
this.expectedException.expect(ItemStreamException.class);
94+
this.expectedException.expectMessage("Failed to initialize the reader");
95+
this.expectedException.expectCause(instanceOf(IllegalStateException.class));
96+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
97+
.jsonObjectReader(getJsonObjectReader())
98+
.resource(new ByteArrayResource("{}, {}".getBytes()))
99+
.name("tradeJsonItemReader")
100+
.build();
101+
102+
itemReader.open(new ExecutionContext());
103+
}
104+
105+
@Test
106+
public void testInvalidResourceContent() throws Exception {
107+
this.expectedException.expect(ParseException.class);
108+
this.expectedException.expectCause(Matchers.instanceOf(getJsonParsingException()));
109+
JsonItemReader<Trade> itemReader = new JsonItemReaderBuilder<Trade>()
110+
.jsonObjectReader(getJsonObjectReader())
111+
.resource(new ByteArrayResource("[{]".getBytes()))
112+
.name("tradeJsonItemReader")
113+
.build();
114+
itemReader.open(new ExecutionContext());
115+
116+
itemReader.read();
117+
}
118+
}
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)