blob: da0ed548522fc727b5019c6f908db1fbb0e47e0f [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.dataformat.csv;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.camel.Exchange;
import org.apache.camel.util.IOHelper;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
/**
* This class unmarshal CSV into lists or maps depending on the configuration.
*/
abstract class CsvUnmarshaller {
protected final CSVFormat format;
protected final CsvRecordConverter<?> converter;
private CsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) {
this.format = format;
this.converter = extractConverter(dataFormat);
}
public static CsvUnmarshaller create(CSVFormat format, CsvDataFormat dataFormat) {
// If we want to use maps, thus the header must be either fixed or automatic
if (dataFormat.isUseMaps() && format.getHeader() == null) {
format = format.withHeader();
}
// If we want to skip the header record it must automatic otherwise it's not working
if (format.getSkipHeaderRecord() && format.getHeader() == null) {
format = format.withHeader();
}
if (dataFormat.isLazyLoad()) {
return new StreamCsvUnmarshaller(format, dataFormat);
}
return new BulkCsvUnmarshaller(format, dataFormat);
}
/**
* Unmarshal the CSV
*
* @param exchange Exchange (used for accessing type converter)
* @param inputStream Input CSV stream
* @return Unmarshalled CSV
* @throws IOException if the stream cannot be read properly
*/
public abstract Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException;
private static CsvRecordConverter<?> extractConverter(CsvDataFormat dataFormat) {
if (dataFormat.getRecordConverter() != null) {
return dataFormat.getRecordConverter();
} else if (dataFormat.isUseMaps()) {
return CsvRecordConverters.mapConverter();
} else {
return CsvRecordConverters.listConverter();
}
}
//region Implementations
/**
* This class reads all the CSV into one big list.
*/
private static final class BulkCsvUnmarshaller extends CsvUnmarshaller {
private BulkCsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) {
super(format, dataFormat);
}
public Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException {
CSVParser parser = new CSVParser(new InputStreamReader(inputStream, IOHelper.getCharsetName(exchange)), format);
try {
return asList(parser.iterator(), converter);
} finally {
IOHelper.close(parser);
}
}
private <T> List<T> asList(Iterator<CSVRecord> iterator, CsvRecordConverter<T> converter) {
List<T> answer = new ArrayList<T>();
while (iterator.hasNext()) {
answer.add(converter.convertRecord(iterator.next()));
}
return answer;
}
}
/**
* This class streams the content of the CSV
*/
@SuppressWarnings("unchecked")
private static final class StreamCsvUnmarshaller extends CsvUnmarshaller {
private StreamCsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) {
super(format, dataFormat);
}
@Override
public Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException {
Reader reader = null;
try {
reader = new InputStreamReader(inputStream, IOHelper.getCharsetName(exchange));
CSVParser parser = new CSVParser(reader, format);
CsvIterator answer = new CsvIterator(parser, converter);
// add to UoW so we can close the iterator so it can release any resources
exchange.addOnCompletion(new CsvUnmarshalOnCompletion(answer));
return answer;
} catch (Exception e) {
IOHelper.close(reader);
throw e;
}
}
}
/**
* This class converts the CSV iterator into the proper result type.
*
* @param <T> Converted type
*/
private static final class CsvIterator<T> implements Iterator<T>, Closeable {
private final CSVParser parser;
private final Iterator<CSVRecord> iterator;
private final CsvRecordConverter<T> converter;
private CsvIterator(CSVParser parser, CsvRecordConverter<T> converter) {
this.parser = parser;
this.iterator = parser.iterator();
this.converter = converter;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return converter.convertRecord(iterator.next());
}
@Override
public void remove() {
iterator.remove();
}
@Override
public void close() throws IOException {
if (!parser.isClosed()) {
parser.close();
}
}
}
//endregion
}