001/* 002 * Copyright 2017-2019 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 */ 016package org.springframework.batch.item.xml.builder; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.List; 021 022import javax.xml.stream.XMLInputFactory; 023 024import org.springframework.batch.item.xml.StaxEventItemReader; 025import org.springframework.batch.item.xml.StaxUtils; 026import org.springframework.core.io.Resource; 027import org.springframework.oxm.Unmarshaller; 028import org.springframework.util.Assert; 029import org.springframework.util.StringUtils; 030 031/** 032 * A fluent builder for the {@link StaxEventItemReader} 033 * 034 * @author Michael Minella 035 * @author Glenn Renfro 036 * @since 4.0 037 */ 038public class StaxEventItemReaderBuilder<T> { 039 040 private boolean strict = true; 041 042 private Resource resource; 043 044 private Unmarshaller unmarshaller; 045 046 private List<String> fragmentRootElements = new ArrayList<>(); 047 048 private boolean saveState = true; 049 050 private String name; 051 052 private int maxItemCount = Integer.MAX_VALUE; 053 054 private int currentItemCount; 055 056 private XMLInputFactory xmlInputFactory = StaxUtils.createXmlInputFactory(); 057 058 /** 059 * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport} 060 * should be persisted within the {@link org.springframework.batch.item.ExecutionContext} 061 * for restart purposes. 062 * 063 * @param saveState defaults to true 064 * @return The current instance of the builder. 065 */ 066 public StaxEventItemReaderBuilder<T> saveState(boolean saveState) { 067 this.saveState = saveState; 068 069 return this; 070 } 071 072 /** 073 * The name used to calculate the key within the 074 * {@link org.springframework.batch.item.ExecutionContext}. Required if 075 * {@link #saveState(boolean)} is set to true. 076 * 077 * @param name name of the reader instance 078 * @return The current instance of the builder. 079 * @see org.springframework.batch.item.ItemStreamSupport#setName(String) 080 */ 081 public StaxEventItemReaderBuilder<T> name(String name) { 082 this.name = name; 083 084 return this; 085 } 086 087 /** 088 * Configure the max number of items to be read. 089 * 090 * @param maxItemCount the max items to be read 091 * @return The current instance of the builder. 092 * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) 093 */ 094 public StaxEventItemReaderBuilder<T> maxItemCount(int maxItemCount) { 095 this.maxItemCount = maxItemCount; 096 097 return this; 098 } 099 100 /** 101 * Index for the current item. Used on restarts to indicate where to start from. 102 * 103 * @param currentItemCount current index 104 * @return this instance for method chaining 105 * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) 106 */ 107 public StaxEventItemReaderBuilder<T> currentItemCount(int currentItemCount) { 108 this.currentItemCount = currentItemCount; 109 110 return this; 111 } 112 113 /** 114 * The {@link Resource} to be used as input. 115 * 116 * @param resource the input to the reader. 117 * @return The current instance of the builder. 118 * @see StaxEventItemReader#setResource(Resource) 119 */ 120 public StaxEventItemReaderBuilder<T> resource(Resource resource) { 121 this.resource = resource; 122 123 return this; 124 } 125 126 /** 127 * An implementation of the {@link Unmarshaller} from Spring's OXM module. 128 * 129 * @param unmarshaller component responsible for unmarshalling XML chunks 130 * @return The current instance of the builder. 131 * @see StaxEventItemReader#setUnmarshaller 132 */ 133 public StaxEventItemReaderBuilder<T> unmarshaller(Unmarshaller unmarshaller) { 134 this.unmarshaller = unmarshaller; 135 136 return this; 137 } 138 139 /** 140 * Adds the list of fragments to be used as the root of each chunk to the 141 * configuration. 142 * 143 * @param fragmentRootElements the XML root elements to be used to identify XML 144 * chunks. 145 * @return The current instance of the builder. 146 * @see StaxEventItemReader#setFragmentRootElementNames(String[]) 147 */ 148 public StaxEventItemReaderBuilder<T> addFragmentRootElements(String... fragmentRootElements) { 149 this.fragmentRootElements.addAll(Arrays.asList(fragmentRootElements)); 150 151 return this; 152 } 153 154 /** 155 * Adds the list of fragments to be used as the root of each chunk to the 156 * configuration. 157 * 158 * @param fragmentRootElements the XML root elements to be used to identify XML 159 * chunks. 160 * @return The current instance of the builder. 161 * @see StaxEventItemReader#setFragmentRootElementNames(String[]) 162 */ 163 public StaxEventItemReaderBuilder<T> addFragmentRootElements(List<String> fragmentRootElements) { 164 this.fragmentRootElements.addAll(fragmentRootElements); 165 166 return this; 167 } 168 169 /** 170 * Setting this value to true indicates that it is an error if the input does not 171 * exist and an exception will be thrown. Defaults to true. 172 * 173 * @param strict indicates the input file must exist 174 * @return The current instance of the builder 175 * @see StaxEventItemReader#setStrict(boolean) 176 */ 177 public StaxEventItemReaderBuilder<T> strict(boolean strict) { 178 this.strict = strict; 179 180 return this; 181 } 182 183 /** 184 * Set the {@link XMLInputFactory}. 185 * 186 * @param xmlInputFactory to use 187 * @return The current instance of the builder 188 * @see StaxEventItemReader#setXmlInputFactory(XMLInputFactory) 189 */ 190 public StaxEventItemReaderBuilder<T> xmlInputFactory(XMLInputFactory xmlInputFactory) { 191 this.xmlInputFactory = xmlInputFactory; 192 193 return this; 194 } 195 196 /** 197 * Validates the configuration and builds a new {@link StaxEventItemReader} 198 * 199 * @return a new instance of the {@link StaxEventItemReader} 200 */ 201 public StaxEventItemReader<T> build() { 202 Assert.notNull(this.resource, "A resource is required."); 203 204 StaxEventItemReader<T> reader = new StaxEventItemReader<>(); 205 206 if (this.saveState) { 207 Assert.state(StringUtils.hasText(this.name), "A name is required when saveState is set to true."); 208 } 209 else { 210 reader.setName(this.name); 211 } 212 213 Assert.notEmpty(this.fragmentRootElements, "At least one fragment root element is required"); 214 215 reader.setSaveState(this.saveState); 216 reader.setResource(this.resource); 217 reader.setFragmentRootElementNames( 218 this.fragmentRootElements.toArray(new String[this.fragmentRootElements.size()])); 219 220 reader.setStrict(this.strict); 221 reader.setUnmarshaller(this.unmarshaller); 222 reader.setCurrentItemCount(this.currentItemCount); 223 reader.setMaxItemCount(this.maxItemCount); 224 reader.setXmlInputFactory(this.xmlInputFactory); 225 226 return reader; 227 } 228}