/**
* 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.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.support.ServiceSupport;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;
/**
* CSV Data format.
* <p/>
* By default, columns are autogenerated in the resulting CSV. Subsequent
* messages use the previously created columns with new fields being added at
* the end of the line. Thus, field order is the same from message to message.
* Autogeneration can be disabled. In this case, only the fields defined in
* csvConfig are written on the output.
*/
public class CsvDataFormat extends ServiceSupport implements DataFormat, DataFormatName {
// CSV format options
private CSVFormat format = CSVFormat.DEFAULT;
private boolean commentMarkerDisabled;
private Character commentMarker;
private Character delimiter;
private boolean escapeDisabled;
private Character escape;
private boolean headerDisabled;
private String[] header;
private Boolean allowMissingColumnNames;
private Boolean ignoreEmptyLines;
private Boolean ignoreSurroundingSpaces;
private boolean nullStringDisabled;
private String nullString;
private boolean quoteDisabled;
private Character quote;
private QuoteMode quoteMode;
private boolean recordSeparatorDisabled;
private String recordSeparator;
private Boolean skipHeaderRecord;
private Boolean trim;
private Boolean ignoreHeaderCase;
private Boolean trailingDelimiter;
// Unmarshal options
private boolean lazyLoad;
private boolean useMaps;
private CsvRecordConverter<?> recordConverter;
private volatile CsvMarshaller marshaller;
private volatile CsvUnmarshaller unmarshaller;
public CsvDataFormat() {
}
public CsvDataFormat(CSVFormat format) {
setFormat(format);
}
@Override
public String getDataFormatName() {
return "csv";
}
public void marshal(Exchange exchange, Object object, OutputStream outputStream) throws Exception {
marshaller.marshal(exchange, object, outputStream);
}
public Object unmarshal(Exchange exchange, InputStream inputStream) throws Exception {
return unmarshaller.unmarshal(exchange, inputStream);
}
@Override
protected void doStart() throws Exception {
marshaller = CsvMarshaller.create(getActiveFormat(), this);
unmarshaller = CsvUnmarshaller.create(getActiveFormat(), this);
}
@Override
protected void doStop() throws Exception {
// noop
}
CSVFormat getActiveFormat() {
CSVFormat answer = format;
if (commentMarkerDisabled) {
answer = answer.withCommentMarker(null); // null disables the comment marker
} else if (commentMarker != null) {
answer = answer.withCommentMarker(commentMarker);
}
if (delimiter != null) {
answer = answer.withDelimiter(delimiter);
}
if (escapeDisabled) {
answer = answer.withEscape(null); // null disables the escape
} else if (escape != null) {
answer = answer.withEscape(escape);
}
if (headerDisabled) {
answer = answer.withHeader((String[]) null); // null disables the header
} else if (header != null) {
answer = answer.withHeader(header);
}
if (allowMissingColumnNames != null) {
answer = answer.withAllowMissingColumnNames(allowMissingColumnNames);
}
if (ignoreEmptyLines != null) {
answer = answer.withIgnoreEmptyLines(ignoreEmptyLines);
}
if (ignoreSurroundingSpaces != null) {
answer = answer.withIgnoreSurroundingSpaces(ignoreSurroundingSpaces);
}
if (nullStringDisabled) {
answer = answer.withNullString(null); // null disables the null string replacement
} else if (nullString != null) {
answer = answer.withNullString(nullString);
}
if (quoteDisabled) {
answer = answer.withQuote(null); // null disables quotes
} else if (quote != null) {
answer = answer.withQuote(quote);
}
if (quoteMode != null) {
answer = answer.withQuoteMode(quoteMode);
}
if (recordSeparatorDisabled) {
answer = answer.withRecordSeparator(null); // null disables the record separator
} else if (recordSeparator != null) {
answer = answer.withRecordSeparator(recordSeparator);
}
if (skipHeaderRecord != null) {
answer = answer.withSkipHeaderRecord(skipHeaderRecord);
}
if (trim != null) {
answer = answer.withTrim(trim);
}
if (ignoreHeaderCase != null) {
answer = answer.withIgnoreHeaderCase(ignoreHeaderCase);
}
if (trailingDelimiter != null) {
answer = answer.withTrailingDelimiter(trailingDelimiter);
}
return answer;
}
//region Getters/Setters
/**
* Gets the CSV format before applying any changes.
* It cannot be {@code null}, the default one is {@link org.apache.commons.csv.CSVFormat#DEFAULT}.
*
* @return CSV format
*/
public CSVFormat getFormat() {
return format;
}
/**
* Sets the CSV format before applying any changes.
* If {@code null}, then {@link org.apache.commons.csv.CSVFormat#DEFAULT} is used instead.
*
* @param format CSV format
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat
* @see org.apache.commons.csv.CSVFormat#DEFAULT
*/
public CsvDataFormat setFormat(CSVFormat format) {
this.format = (format == null) ? CSVFormat.DEFAULT : format;
return this;
}
/**
* Sets the CSV format by name before applying any changes.
*
* @param name CSV format name
* @return Current {@code CsvDataFormat}, fluent API
* @see #setFormat(org.apache.commons.csv.CSVFormat)
* @see org.apache.commons.csv.CSVFormat
*/
public CsvDataFormat setFormatName(String name) {
if (name == null) {
setFormat(null);
} else if ("DEFAULT".equals(name)) {
setFormat(CSVFormat.DEFAULT);
} else if ("RFC4180".equals(name)) {
setFormat(CSVFormat.RFC4180);
} else if ("EXCEL".equals(name)) {
setFormat(CSVFormat.EXCEL);
} else if ("TDF".equals(name)) {
setFormat(CSVFormat.TDF);
} else if ("MYSQL".equals(name)) {
setFormat(CSVFormat.MYSQL);
} else {
throw new IllegalArgumentException("Unsupported format");
}
return this;
}
/**
* Indicates whether or not the comment markers are disabled.
*
* @return {@code true} if the comment markers are disabled, {@code false} otherwise
*/
public boolean isCommentMarkerDisabled() {
return commentMarkerDisabled;
}
/**
* Sets whether or not the comment markers are disabled.
*
* @param commentMarkerDisabled {@code true} if the comment markers are disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withCommentMarker(java.lang.Character)
*/
public CsvDataFormat setCommentMarkerDisabled(boolean commentMarkerDisabled) {
this.commentMarkerDisabled = commentMarkerDisabled;
return this;
}
/**
* Gets the comment marker.
* If {@code null} then the default one of the format used.
*
* @return Comment marker
*/
public Character getCommentMarker() {
return commentMarker;
}
/**
* Sets the comment marker to use.
* If {@code null} then the default one of the format used.
*
* @param commentMarker Comment marker
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withCommentMarker(Character)
*/
public CsvDataFormat setCommentMarker(Character commentMarker) {
this.commentMarker = commentMarker;
return this;
}
/**
* Gets the delimiter.
* If {@code null} then the default one of the format used.
*
* @return Delimiter
*/
public Character getDelimiter() {
return delimiter;
}
/**
* Sets the delimiter.
* If {@code null} then the default one of the format used.
*
* @param delimiter Delimiter
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withDelimiter(char)
*/
public CsvDataFormat setDelimiter(Character delimiter) {
this.delimiter = delimiter;
return this;
}
/**
* Indicates whether or not the escaping is disabled.
*
* @return {@code true} if the escaping is disabled, {@code false} otherwise
*/
public boolean isEscapeDisabled() {
return escapeDisabled;
}
/**
* Sets whether or not the escaping is disabled.
*
* @param escapeDisabled {@code true} if the escaping is disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withEscape(Character)
*/
public CsvDataFormat setEscapeDisabled(boolean escapeDisabled) {
this.escapeDisabled = escapeDisabled;
return this;
}
/**
* Gets the escape character.
* If {@code null} then the default one of the format used.
*
* @return Escape character
*/
public Character getEscape() {
return escape;
}
/**
* Sets the escape character.
* If {@code null} then the default one of the format used.
*
* @param escape Escape character
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withEscape(Character)
*/
public CsvDataFormat setEscape(Character escape) {
this.escape = escape;
return this;
}
/**
* Indicates whether or not the headers are disabled.
*
* @return {@code true} if the headers are disabled, {@code false} otherwise
*/
public boolean isHeaderDisabled() {
return headerDisabled;
}
/**
* Sets whether or not the headers are disabled.
*
* @param headerDisabled {@code true} if the headers are disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withHeader(String...)
*/
public CsvDataFormat setHeaderDisabled(boolean headerDisabled) {
this.headerDisabled = headerDisabled;
return this;
}
/**
* Gets the header.
* If {@code null} then the default one of the format used. If empty then it will be automatically handled.
*
* @return Header
*/
public String[] getHeader() {
return header;
}
/**
* Gets the header.
* If {@code null} then the default one of the format used. If empty then it will be automatically handled.
*
* @param header Header
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withHeader(String...)
*/
public CsvDataFormat setHeader(String[] header) {
this.header = Arrays.copyOf(header, header.length);
return this;
}
/**
* Indicates whether or not missing column names are allowed.
* If {@code null} then the default value of the format used.
*
* @return Whether or not missing column names are allowed
*/
public Boolean getAllowMissingColumnNames() {
return allowMissingColumnNames;
}
/**
* Sets whether or not missing column names are allowed.
* If {@code null} then the default value of the format used.
*
* @param allowMissingColumnNames Whether or not missing column names are allowed
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withAllowMissingColumnNames(boolean)
*/
public CsvDataFormat setAllowMissingColumnNames(Boolean allowMissingColumnNames) {
this.allowMissingColumnNames = allowMissingColumnNames;
return this;
}
/**
* Indicates whether or not empty lines must be ignored.
* If {@code null} then the default value of the format used.
*
* @return Whether or not empty lines must be ignored
*/
public Boolean getIgnoreEmptyLines() {
return ignoreEmptyLines;
}
/**
* Sets whether or not empty lines must be ignored.
* If {@code null} then the default value of the format used.
*
* @param ignoreEmptyLines Whether or not empty lines must be ignored
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withIgnoreEmptyLines(boolean)
*/
public CsvDataFormat setIgnoreEmptyLines(Boolean ignoreEmptyLines) {
this.ignoreEmptyLines = ignoreEmptyLines;
return this;
}
/**
* Indicates whether or not surrounding spaces must be ignored.
* If {@code null} then the default value of the format used.
*
* @return Whether or not surrounding spaces must be ignored
*/
public Boolean getIgnoreSurroundingSpaces() {
return ignoreSurroundingSpaces;
}
/**
* Sets whether or not surrounding spaces must be ignored.
* If {@code null} then the default value of the format used.
*
* @param ignoreSurroundingSpaces Whether or not surrounding spaces must be ignored
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withIgnoreSurroundingSpaces(boolean)
*/
public CsvDataFormat setIgnoreSurroundingSpaces(Boolean ignoreSurroundingSpaces) {
this.ignoreSurroundingSpaces = ignoreSurroundingSpaces;
return this;
}
/**
* Indicates whether or not the null string replacement is disabled.
*
* @return {@code true} if the null string replacement is disabled, {@code false} otherwise
*/
public boolean isNullStringDisabled() {
return nullStringDisabled;
}
/**
* Sets whether or not the null string replacement is disabled.
*
* @param nullStringDisabled {@code true} if the null string replacement is disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withNullString(String)
*/
public CsvDataFormat setNullStringDisabled(boolean nullStringDisabled) {
this.nullStringDisabled = nullStringDisabled;
return this;
}
/**
* Gets the null string replacement.
* If {@code null} then the default one of the format used.
*
* @return Null string replacement
*/
public String getNullString() {
return nullString;
}
/**
* Sets the null string replacement.
* If {@code null} then the default one of the format used.
*
* @param nullString Null string replacement
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withNullString(String)
*/
public CsvDataFormat setNullString(String nullString) {
this.nullString = nullString;
return this;
}
/**
* Indicates whether or not quotes are disabled.
*
* @return {@code true} if quotes are disabled, {@code false} otherwise
*/
public boolean isQuoteDisabled() {
return quoteDisabled;
}
/**
* Sets whether or not quotes are disabled
*
* @param quoteDisabled {@code true} if quotes are disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withQuote(Character)
*/
public CsvDataFormat setQuoteDisabled(boolean quoteDisabled) {
this.quoteDisabled = quoteDisabled;
return this;
}
/**
* Gets the quote character.
* If {@code null} then the default one of the format used.
*
* @return Quote character
*/
public Character getQuote() {
return quote;
}
/**
* Sets the quote character.
* If {@code null} then the default one of the format used.
*
* @param quote Quote character
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withQuote(Character)
*/
public CsvDataFormat setQuote(Character quote) {
this.quote = quote;
return this;
}
/**
* Gets the quote mode.
* If {@code null} then the default one of the format used.
*
* @return Quote mode
*/
public QuoteMode getQuoteMode() {
return quoteMode;
}
/**
* Sets the quote mode.
* If {@code null} then the default one of the format used.
*
* @param quoteMode Quote mode
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withQuoteMode(org.apache.commons.csv.QuoteMode)
*/
public CsvDataFormat setQuoteMode(QuoteMode quoteMode) {
this.quoteMode = quoteMode;
return this;
}
/**
* Indicates whether or not the record separator is disabled.
*
* @return {@code true} if the record separator disabled, {@code false} otherwise
*/
public boolean isRecordSeparatorDisabled() {
return recordSeparatorDisabled;
}
/**
* Sets whether or not the record separator is disabled.
*
* @param recordSeparatorDisabled {@code true} if the record separator disabled, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withRecordSeparator(String)
*/
public CsvDataFormat setRecordSeparatorDisabled(boolean recordSeparatorDisabled) {
this.recordSeparatorDisabled = recordSeparatorDisabled;
return this;
}
/**
* Gets the record separator.
* If {@code null} then the default one of the format used.
*
* @return Record separator
*/
public String getRecordSeparator() {
return recordSeparator;
}
/**
* Sets the record separator.
* If {@code null} then the default one of the format used.
*
* @param recordSeparator Record separator
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withRecordSeparator(String)
*/
public CsvDataFormat setRecordSeparator(String recordSeparator) {
this.recordSeparator = recordSeparator;
return this;
}
/**
* Indicates whether or not header record must be skipped.
* If {@code null} then the default value of the format used.
*
* @return Whether or not header record must be skipped
*/
public Boolean getSkipHeaderRecord() {
return skipHeaderRecord;
}
/**
* Sets whether or not header record must be skipped.
* If {@code null} then the default value of the format used.
*
* @param skipHeaderRecord Whether or not header record must be skipped
* @return Current {@code CsvDataFormat}, fluent API
* @see org.apache.commons.csv.CSVFormat#withSkipHeaderRecord(boolean)
*/
public CsvDataFormat setSkipHeaderRecord(Boolean skipHeaderRecord) {
this.skipHeaderRecord = skipHeaderRecord;
return this;
}
/**
* Indicates whether or not the unmarshalling should lazily load the records.
*
* @return {@code true} for lazy loading, {@code false} otherwise
*/
public boolean isLazyLoad() {
return lazyLoad;
}
/**
* Indicates whether or not the unmarshalling should lazily load the records.
*
* @param lazyLoad {@code true} for lazy loading, {@code false} otherwise
* @return Current {@code CsvDataFormat}, fluent API
*/
public CsvDataFormat setLazyLoad(boolean lazyLoad) {
this.lazyLoad = lazyLoad;
return this;
}
/**
* Indicates whether or not the unmarshalling should produce maps instead of lists.
*
* @return {@code true} for maps, {@code false} for lists
*/
public boolean isUseMaps() {
return useMaps;
}
/**
* Sets whether or not the unmarshalling should produce maps instead of lists.
*
* @param useMaps {@code true} for maps, {@code false} for lists
* @return Current {@code CsvDataFormat}, fluent API
*/
public CsvDataFormat setUseMaps(boolean useMaps) {
this.useMaps = useMaps;
return this;
}
/**
* Gets the record converter to use. If {@code null} then it will use {@link CsvDataFormat#isUseMaps()} for finding
* the proper converter.
*
* @return Record converter to use
*/
public CsvRecordConverter<?> getRecordConverter() {
return recordConverter;
}
/**
* Sets the record converter to use. If {@code null} then it will use {@link CsvDataFormat#isUseMaps()} for finding
* the proper converter.
*
* @param recordConverter Record converter to use
* @return Current {@code CsvDataFormat}, fluent API
*/
public CsvDataFormat setRecordConverter(CsvRecordConverter<?> recordConverter) {
this.recordConverter = recordConverter;
return this;
}
//endregion
/**
* Sets whether or not to trim leading and trailing blanks.
* <p>
* If {@code null} then the default value of the format used.
* </p>
*
* @param trim whether or not to trim leading and trailing blanks.
* <code>null</code> value allowed.
* @return Current {@code CsvDataFormat}, fluent API.
*/
public CsvDataFormat setTrim(Boolean trim) {
this.trim = trim;
return this;
}
/**
* Indicates whether or not to trim leading and trailing blanks.
*
* @return {@link Boolean#TRUE} if leading and trailing blanks should be
* trimmed. {@link Boolean#FALSE} otherwise. Could return
* <code>null</code> if value has NOT been set.
*/
public Boolean getTrim() {
return trim;
}
/**
* Sets whether or not to ignore case when accessing header names.
* <p>
* If {@code null} then the default value of the format used.
* </p>
*
* @param ignoreHeaderCase whether or not to ignore case when accessing header names.
* <code>null</code> value allowed.
* @return Current {@code CsvDataFormat}, fluent API.
*/
public CsvDataFormat setIgnoreHeaderCase(Boolean ignoreHeaderCase) {
this.ignoreHeaderCase = ignoreHeaderCase;
return this;
}
/**
* Indicates whether or not to ignore case when accessing header names.
*
* @return {@link Boolean#TRUE} if case should be ignored when accessing
* header name. {@link Boolean#FALSE} otherwise. Could return
* <code>null</code> if value has NOT been set.
*/
public Boolean getIgnoreHeaderCase() {
return ignoreHeaderCase;
}
/**
* Sets whether or not to add a trailing delimiter.
* <p>
* If {@code null} then the default value of the format used.
* </p>
*
* @param trailingDelimiter whether or not to add a trailing delimiter.
* @return Current {@code CsvDataFormat}, fluent API.
*/
public CsvDataFormat setTrailingDelimiter(Boolean trailingDelimiter) {
this.trailingDelimiter = trailingDelimiter;
return this;
}
/**
* Indicates whether or not to add a trailing delimiter.
*
* @return {@link Boolean#TRUE} if a trailing delimiter should be added.
* {@link Boolean#FALSE} otherwise. Could return <code>null</code>
* if value has NOT been set.
*/
public Boolean getTrailingDelimiter() {
return trailingDelimiter;
}
}