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.util.Iterator;
020import java.util.List;
021
022import org.springframework.batch.item.support.AbstractFileItemWriter;
023import org.springframework.core.io.Resource;
024import org.springframework.util.Assert;
025import org.springframework.util.ClassUtils;
026
027/**
028 * Item writer that writes data in json format to an output file. The location
029 * of the output file is defined by a {@link Resource} and must represent a
030 * writable file. Items are transformed to json format using a
031 * {@link JsonObjectMarshaller}. Items will be enclosed in a json array as follows:
032 *
033 * <p>
034 * <code>
035 * [
036 *  {json object},
037 *  {json object},
038 *  {json object}
039 * ]
040 * </code>
041 * </p>
042 *
043 * The implementation is <b>not</b> thread-safe.
044 *
045 * @see GsonJsonObjectMarshaller
046 * @see JacksonJsonObjectMarshaller
047 * @param <T> type of object to write as json representation
048 * @author Mahmoud Ben Hassine
049 * @since 4.1
050 */
051public class JsonFileItemWriter<T> extends AbstractFileItemWriter<T> {
052
053        private static final char JSON_OBJECT_SEPARATOR = ',';
054        private static final char JSON_ARRAY_START = '[';
055        private static final char JSON_ARRAY_STOP = ']';
056
057        private JsonObjectMarshaller<T> jsonObjectMarshaller;
058
059        /**
060         * Create a new {@link JsonFileItemWriter} instance.
061         * @param resource to write json data to
062         * @param jsonObjectMarshaller used to marshal object into json representation
063         */
064        public JsonFileItemWriter(Resource resource, JsonObjectMarshaller<T> jsonObjectMarshaller) {
065                Assert.notNull(resource, "resource must not be null");
066                Assert.notNull(jsonObjectMarshaller, "json object marshaller must not be null");
067                setResource(resource);
068                setJsonObjectMarshaller(jsonObjectMarshaller);
069                setHeaderCallback(writer -> writer.write(JSON_ARRAY_START));
070                setFooterCallback(writer -> writer.write(this.lineSeparator + JSON_ARRAY_STOP + this.lineSeparator));
071                setExecutionContextName(ClassUtils.getShortName(JsonFileItemWriter.class));
072        }
073
074        /**
075         * Assert that mandatory properties (jsonObjectMarshaller) are set.
076         *
077         * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
078         */
079        @Override
080        public void afterPropertiesSet() throws Exception {
081                if (this.append) {
082                        this.shouldDeleteIfExists = false;
083                }
084        }
085
086        /**
087         * Set the {@link JsonObjectMarshaller} to use to marshal object to json.
088         * @param jsonObjectMarshaller the marshaller to use
089         */
090        public void setJsonObjectMarshaller(JsonObjectMarshaller<T> jsonObjectMarshaller) {
091                this.jsonObjectMarshaller = jsonObjectMarshaller;
092        }
093
094        @Override
095        public String doWrite(List<? extends T> items) {
096                StringBuilder lines = new StringBuilder();
097                Iterator<? extends T> iterator = items.iterator();
098                if (!items.isEmpty() && state.getLinesWritten() > 0) {
099                        lines.append(JSON_OBJECT_SEPARATOR).append(this.lineSeparator);
100                }
101                while (iterator.hasNext()) {
102                        T item = iterator.next();
103                        lines.append(' ').append(this.jsonObjectMarshaller.marshal(item));
104                        if (iterator.hasNext()) {
105                                lines.append(JSON_OBJECT_SEPARATOR).append(this.lineSeparator);
106                        }
107                }
108                return lines.toString();
109        }
110
111}