001/*
002 * Copyright 2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.item.json;
018
019import java.io.IOException;
020import java.io.InputStream;
021
022import com.fasterxml.jackson.core.JsonParser;
023import com.fasterxml.jackson.core.JsonToken;
024import com.fasterxml.jackson.databind.ObjectMapper;
025
026import org.springframework.batch.item.ParseException;
027import org.springframework.core.io.Resource;
028import org.springframework.util.Assert;
029
030/**
031 * Implementation of {@link JsonObjectReader} based on
032 * <a href="https://github.com/FasterXML/jackson">Jackson</a>.
033 *
034 * @param <T> type of the target object
035 *
036 * @author Mahmoud Ben Hassine
037 * @since 4.1
038 */
039public class JacksonJsonObjectReader<T> implements JsonObjectReader<T> {
040
041        private Class<? extends T> itemType;
042
043        private JsonParser jsonParser;
044
045        private ObjectMapper mapper = new ObjectMapper();
046
047        private InputStream inputStream;
048
049        /**
050         * Create a new {@link JacksonJsonObjectReader} instance.
051         * @param itemType the target item type
052         */
053        public JacksonJsonObjectReader(Class<? extends T> itemType) {
054                this.itemType = itemType;
055        }
056
057        /**
058         * Set the object mapper to use to map Json objects to domain objects.
059         * @param mapper the object mapper to use
060         */
061        public void setMapper(ObjectMapper mapper) {
062                Assert.notNull(mapper, "The mapper must not be null");
063                this.mapper = mapper;
064        }
065
066        @Override
067        public void open(Resource resource) throws Exception {
068                Assert.notNull(resource, "The resource must not be null");
069                this.inputStream = resource.getInputStream();
070                this.jsonParser = this.mapper.getFactory().createParser(this.inputStream);
071                Assert.state(this.jsonParser.nextToken() == JsonToken.START_ARRAY,
072                                "The Json input stream must start with an array of Json objects");
073        }
074
075        @Override
076        public T read() throws Exception {
077                try {
078                        if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
079                                return this.mapper.readValue(this.jsonParser, this.itemType);
080                        }
081                } catch (IOException e) {
082                        throw new ParseException("Unable to read next JSON object", e);
083                }
084                return null;
085        }
086
087        @Override
088        public void close() throws Exception {
089                this.inputStream.close();
090                this.jsonParser.close();
091        }
092
093}