/** | |
* 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.univocity; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.io.OutputStream; | |
import java.io.OutputStreamWriter; | |
import java.io.Reader; | |
import java.io.Writer; | |
import com.univocity.parsers.common.AbstractParser; | |
import com.univocity.parsers.common.AbstractWriter; | |
import com.univocity.parsers.common.CommonParserSettings; | |
import com.univocity.parsers.common.CommonSettings; | |
import com.univocity.parsers.common.CommonWriterSettings; | |
import com.univocity.parsers.common.Format; | |
import org.apache.camel.Exchange; | |
import org.apache.camel.spi.DataFormat; | |
import org.apache.camel.spi.DataFormatName; | |
import org.apache.camel.support.ServiceSupport; | |
import static org.apache.camel.util.IOHelper.getCharsetName; | |
/** | |
* This abstract class contains all the common parts for all the uniVocity parsers. | |
* <p/> | |
* | |
* @param <F> uniVocity format class | |
* @param <CWS> uniVocity writer settings class | |
* @param <W> uniVocity writer class | |
* @param <CPS> uniVocity parser settings class | |
* @param <P> uniVocity parser class | |
* @param <DF> the data format class (for providing a fluent API) | |
*/ | |
public abstract class AbstractUniVocityDataFormat<F extends Format, CWS extends CommonWriterSettings<F>, | |
W extends AbstractWriter<CWS>, CPS extends CommonParserSettings<F>, P extends AbstractParser<CPS>, DF extends AbstractUniVocityDataFormat<F, CWS, W, CPS, P, DF>> | |
extends ServiceSupport implements DataFormat, DataFormatName { | |
protected String nullValue; | |
protected Boolean skipEmptyLines; | |
protected Boolean ignoreTrailingWhitespaces; | |
protected Boolean ignoreLeadingWhitespaces; | |
protected boolean headersDisabled; | |
protected String[] headers; | |
protected Boolean headerExtractionEnabled; | |
protected Integer numberOfRecordsToRead; | |
protected String emptyValue; | |
protected String lineSeparator; | |
protected Character normalizedLineSeparator; | |
protected Character comment; | |
protected boolean lazyLoad; | |
protected boolean asMap; | |
private volatile CWS writerSettings; | |
private final Object writerSettingsToken = new Object(); | |
private volatile Marshaller<W> marshaller; | |
// We're using a ThreadLocal for the parser settings because in order to retrieve the headers we need to change the | |
// settings each time we're parsing... | |
private volatile ThreadLocal<CPS> parserSettings; | |
private final Object parserSettingsToken = new Object(); | |
private volatile Unmarshaller<P> unmarshaller; | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void marshal(Exchange exchange, Object body, OutputStream stream) throws Exception { | |
if (writerSettings == null) { | |
synchronized (writerSettingsToken) { | |
if (writerSettings == null) { | |
writerSettings = createAndConfigureWriterSettings(); | |
marshaller = new Marshaller<W>(headers, headers == null); | |
} | |
} | |
} | |
Writer writer = new OutputStreamWriter(stream, getCharsetName(exchange)); | |
try { | |
marshaller.marshal(exchange, body, createWriter(writer, writerSettings)); | |
} finally { | |
writer.close(); | |
} | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception { | |
if (parserSettings == null) { | |
synchronized (parserSettingsToken) { | |
if (parserSettings == null) { | |
parserSettings = new ThreadLocal<CPS>() { | |
@Override | |
protected CPS initialValue() { | |
return createAndConfigureParserSettings(); | |
} | |
}; | |
unmarshaller = new Unmarshaller<P>(lazyLoad, asMap); | |
} | |
} | |
} | |
Reader reader = new InputStreamReader(stream, getCharsetName(exchange)); | |
try { | |
HeaderRowProcessor headerRowProcessor = new HeaderRowProcessor(); | |
CPS settings = parserSettings.get(); | |
settings.setRowProcessor(headerRowProcessor); | |
return unmarshaller.unmarshal(reader, createParser(settings), headerRowProcessor); | |
} finally { | |
reader.close(); | |
} | |
} | |
/** | |
* Gets the String representation of a null value. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return the String representation of a null value | |
* @see com.univocity.parsers.common.CommonSettings#getNullValue() | |
*/ | |
public String getNullValue() { | |
return nullValue; | |
} | |
/** | |
* Sets the String representation of a null value. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param nullValue the String representation of a null value | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setNullValue(String) | |
*/ | |
public DF setNullValue(String nullValue) { | |
this.nullValue = nullValue; | |
return self(); | |
} | |
/** | |
* Gets whether or not empty lines should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return whether or not empty lines should be ignored | |
* @see com.univocity.parsers.common.CommonSettings#getSkipEmptyLines() | |
*/ | |
public Boolean getSkipEmptyLines() { | |
return skipEmptyLines; | |
} | |
/** | |
* Sets whether or not empty lines should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param skipEmptyLines whether or not empty lines should be ignored | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setSkipEmptyLines(boolean) | |
*/ | |
public DF setSkipEmptyLines(Boolean skipEmptyLines) { | |
this.skipEmptyLines = skipEmptyLines; | |
return self(); | |
} | |
/** | |
* Gets whether or not trailing whitespaces should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return whether or not trailing whitespaces should be ignored | |
* @see com.univocity.parsers.common.CommonSettings#getIgnoreTrailingWhitespaces() | |
*/ | |
public Boolean getIgnoreTrailingWhitespaces() { | |
return ignoreTrailingWhitespaces; | |
} | |
/** | |
* Sets whether or not trailing whitespaces should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param ignoreTrailingWhitespaces whether or not trailing whitespaces should be ignored | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setIgnoreTrailingWhitespaces(boolean) | |
*/ | |
public DF setIgnoreTrailingWhitespaces(Boolean ignoreTrailingWhitespaces) { | |
this.ignoreTrailingWhitespaces = ignoreTrailingWhitespaces; | |
return self(); | |
} | |
/** | |
* Gets whether or not leading whitespaces should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return whether or not leading whitespaces should be ignored | |
* @see com.univocity.parsers.common.CommonSettings#getIgnoreLeadingWhitespaces() | |
*/ | |
public Boolean getIgnoreLeadingWhitespaces() { | |
return ignoreLeadingWhitespaces; | |
} | |
/** | |
* Sets whether or not leading whitespaces should be ignored. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param ignoreLeadingWhitespaces whether or not leading whitespaces should be ignored | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setIgnoreLeadingWhitespaces(boolean) | |
*/ | |
public DF setIgnoreLeadingWhitespaces(Boolean ignoreLeadingWhitespaces) { | |
this.ignoreLeadingWhitespaces = ignoreLeadingWhitespaces; | |
return self(); | |
} | |
/** | |
* Gets whether or not headers are disabled. | |
* If {@code true} then it passes {@code null} to | |
* {@link com.univocity.parsers.common.CommonSettings#setHeaders(String...)} in order to disabled them. | |
* | |
* @return whether or not headers are disabled | |
* @see com.univocity.parsers.common.CommonSettings#getHeaders() | |
*/ | |
public boolean isHeadersDisabled() { | |
return headersDisabled; | |
} | |
/** | |
* Sets whether or not headers are disabled. | |
* If {@code true} then it passes {@code null} to | |
* {@link com.univocity.parsers.common.CommonSettings#setHeaders(String...)} in order to disabled them. | |
* | |
* @param headersDisabled whether or not headers are disabled | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setHeaders(String...) | |
*/ | |
public DF setHeadersDisabled(boolean headersDisabled) { | |
this.headersDisabled = headersDisabled; | |
return self(); | |
} | |
/** | |
* Gets the headers. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return the headers | |
* @see com.univocity.parsers.common.CommonSettings#getHeaders() | |
*/ | |
public String[] getHeaders() { | |
return headers; | |
} | |
/** | |
* Sets the headers. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param headers the headers | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonSettings#setHeaders(String...) | |
*/ | |
public DF setHeaders(String[] headers) { | |
this.headers = headers; | |
return self(); | |
} | |
/** | |
* Gets whether or not the header extraction is enabled. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return whether or not the header extraction is enabled | |
* @see com.univocity.parsers.common.CommonParserSettings#isHeaderExtractionEnabled() | |
*/ | |
public Boolean getHeaderExtractionEnabled() { | |
return headerExtractionEnabled; | |
} | |
/** | |
* Sets whether or not the header extraction is enabled. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param headerExtractionEnabled whether or not the header extraction is enabled | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonParserSettings#setHeaderExtractionEnabled(boolean) | |
*/ | |
public DF setHeaderExtractionEnabled(Boolean headerExtractionEnabled) { | |
this.headerExtractionEnabled = headerExtractionEnabled; | |
return self(); | |
} | |
/** | |
* Gets the number of records to read. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return the number of records to read | |
* @see com.univocity.parsers.common.CommonParserSettings#getNumberOfRecordsToRead() | |
*/ | |
public Integer getNumberOfRecordsToRead() { | |
return numberOfRecordsToRead; | |
} | |
/** | |
* Sets the number of records to read. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param numberOfRecordsToRead the number of records to read | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonParserSettings#setNumberOfRecordsToRead(int) | |
*/ | |
public DF setNumberOfRecordsToRead(Integer numberOfRecordsToRead) { | |
this.numberOfRecordsToRead = numberOfRecordsToRead; | |
return self(); | |
} | |
/** | |
* Gets the String representation of an empty value. | |
* If {@code null} then the default settings value is used. | |
* | |
* @return the String representation of an empty value | |
* @see com.univocity.parsers.common.CommonWriterSettings#getEmptyValue() | |
*/ | |
public String getEmptyValue() { | |
return emptyValue; | |
} | |
/** | |
* Sets the String representation of an empty value. | |
* If {@code null} then the default settings value is used. | |
* | |
* @param emptyValue the String representation of an empty value | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.CommonWriterSettings#setEmptyValue(String) | |
*/ | |
public DF setEmptyValue(String emptyValue) { | |
this.emptyValue = emptyValue; | |
return self(); | |
} | |
/** | |
* Gets the line separator. | |
* If {@code null} then the default format value is used. | |
* | |
* @return the line separator | |
* @see com.univocity.parsers.common.Format#getLineSeparatorString() | |
*/ | |
public String getLineSeparator() { | |
return lineSeparator; | |
} | |
/** | |
* Sets the line separator. | |
* If {@code null} then the default format value is used. | |
* | |
* @param lineSeparator the line separator | |
* @return current data format instance, fluent API | |
* @see Format#setLineSeparator(String) | |
*/ | |
public DF setLineSeparator(String lineSeparator) { | |
this.lineSeparator = lineSeparator; | |
return self(); | |
} | |
/** | |
* Gets the normalized line separator. | |
* If {@code null} then the default format value is used. | |
* | |
* @return the normalized line separator | |
* @see com.univocity.parsers.common.Format#getNormalizedNewline() | |
*/ | |
public Character getNormalizedLineSeparator() { | |
return normalizedLineSeparator; | |
} | |
/** | |
* Sets the normalized line separator. | |
* If {@code null} then the default format value is used. | |
* | |
* @param normalizedLineSeparator the normalized line separator | |
* @return current data format instance, fluent API | |
* @see Format#setNormalizedNewline(char) | |
*/ | |
public DF setNormalizedLineSeparator(Character normalizedLineSeparator) { | |
this.normalizedLineSeparator = normalizedLineSeparator; | |
return self(); | |
} | |
/** | |
* Gets the comment symbol. | |
* If {@code null} then the default format value is used. | |
* | |
* @return the comment symbol | |
* @see com.univocity.parsers.common.Format#getComment() | |
*/ | |
public Character getComment() { | |
return comment; | |
} | |
/** | |
* Gets the comment symbol. | |
* If {@code null} then the default format value is used. | |
* | |
* @param comment the comment symbol | |
* @return current data format instance, fluent API | |
* @see com.univocity.parsers.common.Format#setComment(char) | |
*/ | |
public DF setComment(Character comment) { | |
this.comment = comment; | |
return self(); | |
} | |
/** | |
* Gets whether or not the unmarshalling should read lines lazily. | |
* | |
* @return whether or not the unmarshalling should read lines lazily | |
*/ | |
public boolean isLazyLoad() { | |
return lazyLoad; | |
} | |
/** | |
* Sets whether or not the unmarshalling should read lines lazily. | |
* | |
* @param lazyLoad whether or not the unmarshalling should read lines lazily | |
* @return current data format instance, fluent API | |
*/ | |
public DF setLazyLoad(boolean lazyLoad) { | |
this.lazyLoad = lazyLoad; | |
return self(); | |
} | |
/** | |
* Gets whether or not the unmarshalling should produces maps instead of lists. | |
* | |
* @return whether or not the unmarshalling should produces maps instead of lists | |
*/ | |
public boolean isAsMap() { | |
return asMap; | |
} | |
/** | |
* Sets whether or not the unmarshalling should produces maps instead of lists. | |
* | |
* @param asMap whether or not the unmarshalling should produces maps instead of lists | |
* @return current data format instance, fluent API | |
*/ | |
public DF setAsMap(boolean asMap) { | |
this.asMap = asMap; | |
return self(); | |
} | |
/** | |
* Creates a new instance of the writer settings. | |
* | |
* @return New instance of the writer settings | |
*/ | |
protected abstract CWS createWriterSettings(); | |
/** | |
* Configures the writer settings. | |
* | |
* @param settings Writer settings to configure | |
*/ | |
protected void configureWriterSettings(CWS settings) { | |
configureCommonSettings(settings); | |
if (emptyValue != null) { | |
settings.setEmptyValue(emptyValue); | |
} | |
} | |
/** | |
* Creates a new instance of the uniVocity writer. | |
* | |
* @param writer Output writer to use | |
* @param settings Writer settings to use | |
* @return New uinstance of the uniVocity writer | |
*/ | |
protected abstract W createWriter(Writer writer, CWS settings); | |
/** | |
* Creates a new instance of the parser settings. | |
* | |
* @return New instance of the parser settings | |
*/ | |
protected abstract CPS createParserSettings(); | |
/** | |
* Configure the parser settings. | |
* | |
* @param settings Parser settings to configure | |
*/ | |
protected void configureParserSettings(CPS settings) { | |
configureCommonSettings(settings); | |
if (headerExtractionEnabled != null) { | |
settings.setHeaderExtractionEnabled(headerExtractionEnabled); | |
} | |
if (numberOfRecordsToRead != null) { | |
settings.setNumberOfRecordsToRead(numberOfRecordsToRead); | |
} | |
} | |
/** | |
* Creates a new instance of the uniVocity parser. | |
* | |
* @param settings Parser settings to use | |
* @return New instance of the uniVocity parser | |
*/ | |
protected abstract P createParser(CPS settings); | |
/** | |
* Configures the format. | |
* | |
* @param format format to configure | |
*/ | |
protected void configureFormat(F format) { | |
if (lineSeparator != null) { | |
format.setLineSeparator(lineSeparator); | |
} | |
if (normalizedLineSeparator != null) { | |
format.setNormalizedNewline(normalizedLineSeparator); | |
} | |
if (comment != null) { | |
format.setComment(comment); | |
} | |
} | |
/** | |
* Creates and configures the writer settings. | |
* | |
* @return new configured instance of the writer settings | |
*/ | |
final CWS createAndConfigureWriterSettings() { | |
CWS settings = createWriterSettings(); | |
configureWriterSettings(settings); | |
configureFormat(settings.getFormat()); | |
return settings; | |
} | |
/** | |
* Creates and configures the parser settings. | |
* | |
* @return new configured instance of the parser settings | |
*/ | |
final CPS createAndConfigureParserSettings() { | |
CPS settings = createParserSettings(); | |
configureParserSettings(settings); | |
configureFormat(settings.getFormat()); | |
return settings; | |
} | |
/** | |
* Configures the common settings shared by parser and writer. | |
* | |
* @param settings settings to configure | |
*/ | |
private void configureCommonSettings(CommonSettings<F> settings) { | |
if (nullValue != null) { | |
settings.setNullValue(nullValue); | |
} | |
if (skipEmptyLines != null) { | |
settings.setSkipEmptyLines(skipEmptyLines); | |
} | |
if (ignoreTrailingWhitespaces != null) { | |
settings.setIgnoreTrailingWhitespaces(ignoreTrailingWhitespaces); | |
} | |
if (ignoreLeadingWhitespaces != null) { | |
settings.setIgnoreLeadingWhitespaces(ignoreLeadingWhitespaces); | |
} | |
if (headersDisabled) { | |
settings.setHeaders((String[]) null); | |
} else if (headers != null) { | |
settings.setHeaders(headers); | |
} | |
} | |
/** | |
* Returns {@code this} as the proper data format type. It helps the fluent API with inheritance. | |
* | |
* @return {@code this} as the proper data format type | |
*/ | |
@SuppressWarnings("unchecked") | |
private DF self() { | |
return (DF) this; | |
} | |
@Override | |
protected void doStart() throws Exception { | |
writerSettings = null; | |
marshaller = null; | |
parserSettings = null; | |
unmarshaller = null; | |
} | |
@Override | |
protected void doStop() throws Exception { | |
// noop | |
} | |
} |