Skip to content

Add a streaming Json item reader #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from

Conversation

fmbenhassine
Copy link
Contributor

@fmbenhassine fmbenhassine commented May 22, 2018

This PR adds a new Json item reader with two implementations based on Jackson and gson.

Resolves BATCH-2691.

This commit adds a new Json item reader with two implementations based
on Jackson and Gson.

Resolves BATCH-2691

@Override
protected Class<? extends Exception> getJsonParsingException() {
return JsonSyntaxException.class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These exceptions being exposed outside the impl is a leaky abstraction. We should be catching the implementation's exception and wrapping it in a standard exception. Using the Marshaller as an example, it throws an XmlMappingException if there is an issue and that would wrap the underlying implementation's exception.

Copy link
Contributor Author

@fmbenhassine fmbenhassine May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree! Good catch. The best exception I see as a wrapper is org.springframework.batch.item.ParseException.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 53262e6. Now the implementation detail exception is wrapped in a ParseException.

public void afterPropertiesSet() throws Exception {
Assert.notNull(this.jsonObjectReader, "The json object reader must not be null.");
Assert.notNull(this.resource, "The resource must not be null.");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really is an old school way of doing things. We should require these in the constructor if possible.

Copy link
Contributor Author

@fmbenhassine fmbenhassine May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the StaxEventItemReader, both the resource and unmarshaller are mandatory resources but they are not required at construction time (they are checked in afterPropertiesSet). I've seen this in many places in the code base like for example the ValidatingItemProcessor where I can create an instance with the default constructor while the validator is mandatory. Another example is the JmsItemReader: the jmsTemplate is mandatory but it is not required in the constructor.

Copy link
Member

@mminella mminella May 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When XML was the preferred way to configure things, setter injection and using InitializingBean made sense (since java doesn't have named parameters). However, with java config the norm now, we should take advantage of that and utilize constructor injection where we can. Also, given the builders will be used in most cases going forward, there's even less reason to take this approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more about class design than configuration style: I should not be able to create a class without specifying its required dependencies (fail fast, at construction time) rather than letting me create the class and then throw me an exception when the container calls afterPropertiesSet. Here, I used the InitializingBean way for consistency with the current code base, but for me, we should always use constructor injection for required dependencies and setter injection for optional ones regardless of the configuration style. I couldn't agree more with you, so I fixed that in 493196c.

* @since 4.1
*/
public class JsonItemReader<T> extends AbstractItemCountingItemStreamItemReader<T> implements
ResourceAwareItemReaderItemStream<T>, InitializingBean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're already bringing in the ItemStream and the ItemReader interfaces via the AbstractItemCountingItemStreamItemReader, you can probably just implement ResourceAware (no need to use the composite).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the same as StaxEventItemWriter.

* @param resource the input resource
* @throws Exception if unable to open the resource
*/
default void open(Resource resource) throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try not to throw Exception unless there is a real reason to do so in an API. We'd rather throw a documented RuntimeException if possible which this feels like it should be able to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I think there is a real reason to do so. If the implementation cannot open the resource, we gave it a chance to throw a checked exception and signal the problem to the framework. We do the same in org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#doOpen. The javadoc makes sense to me: @throws Exception Allows subclasses to throw checked exceptions for interpretation by the framework.

* @return the next object or null if the resource is exhausted
* @throws Exception if unable to read the next object
*/
T read() throws Exception;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above about throwing Exception

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment on doOpen. We throw an exception in org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#doRead and org.springframework.batch.item.ItemReader#read.

* Close the input resource.
* @throws Exception if unable to close the input resource
*/
default void close() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above about throwing Exception

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#doClose.

new JsonItemReaderBuilder<String>()
.jsonObjectReader(this.jsonObjectReader)
.resource(this.resource)
.saveState(true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is redundant isn't it (save state defaults to true)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought being explicit is better for this specific test case. But I will remove it, I agree it is not really necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 5b6e7b9

@mminella
Copy link
Member

LGTM. Rebased, squashed, and merged as 5281d6d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants