/** | |
* 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 | |
} |