This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 024b059663be07eea6c4379c9114e7ca6563acd9 Author: longxu <neoxu...@gmail.com> AuthorDate: Tue Jan 30 22:16:43 2018 +1100 CAMEL-12192 support csv bindy skip fields --- .../camel/dataformat/bindy/BindyCsvFactory.java | 188 ++++++++++++--------- .../dataformat/bindy/csv/BindyCsvDataFormat.java | 16 +- .../bindy/csv/BindyCsvSkipFieldTest.java | 170 +++++++++++++++++++ .../bindy/csv/BindyCsvSkipFieldTest-context.xml | 34 ++++ 4 files changed, 328 insertions(+), 80 deletions(-) diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/BindyCsvFactory.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/BindyCsvFactory.java index 9906402..0015947 100755 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/BindyCsvFactory.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/BindyCsvFactory.java @@ -75,12 +75,20 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor private boolean allowEmptyStream; private boolean quotingEscaped; private boolean endWithLineBreak; + + private boolean isSkipField; public BindyCsvFactory(Class<?> type) throws Exception { + this(type, false); + } + + public BindyCsvFactory(Class<?> type, boolean isSkipField) throws Exception { super(type); // initialize specific parameters of the csv model initCsvModel(); + + this.isSkipField = isSkipField; } /** @@ -174,105 +182,118 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor // Get DataField from model DataField dataField = dataFields.get(pos); - ObjectHelper.notNull(dataField, "No position " + pos + " defined for the field: " + data + ", line: " + line); - - if (dataField.trim()) { - data = data.trim(); + + // If a DataField can be skipped, it needs to check whether it is in dataFields keyset + if (isSkipField()) { + if (dataFields.keySet().contains(pos)) { + counterMandatoryFields = setDataFieldValue(camelContext, model, line, pos, counterMandatoryFields, data, dataField); + } + } else { + counterMandatoryFields = setDataFieldValue(camelContext, model, line, pos, counterMandatoryFields, data, dataField); } + + ++pos; - if (dataField.required()) { - // Increment counter of mandatory fields - ++counterMandatoryFields; + } - // Check if content of the field is empty - // This is not possible for mandatory fields - if (data.equals("")) { - throw new IllegalArgumentException("The mandatory field defined at the position " + pos + " is empty for the line: " + line); - } - } + LOG.debug("Counter mandatory fields: {}", counterMandatoryFields); - // Get Field to be setted - Field field = annotatedFields.get(pos); - field.setAccessible(true); + if (counterMandatoryFields < numberMandatoryFields) { + throw new IllegalArgumentException("Some mandatory fields are missing, line: " + line); + } - if (LOG.isDebugEnabled()) { - LOG.debug("Pos: {}, Data: {}, Field type: {}", new Object[]{pos, data, field.getType()}); + if (pos < totalFields) { + setDefaultValuesForFields(model); + } + + } + + private int setDataFieldValue(CamelContext camelContext, Map<String, Object> model, int line, int pos, int counterMandatoryFields, String data, DataField dataField) throws Exception { + ObjectHelper.notNull(dataField, "No position " + pos + " defined for the field: " + data + ", line: " + line); + + if (dataField.trim()) { + data = data.trim(); + } + + if (dataField.required()) { + // Increment counter of mandatory fields + ++counterMandatoryFields; + + // Check if content of the field is empty + // This is not possible for mandatory fields + if (data.equals("")) { + throw new IllegalArgumentException("The mandatory field defined at the position " + pos + " is empty for the line: " + line); } + } - // Create format object to format the field - FormattingOptions formattingOptions = ConverterUtils.convert(dataField, - field.getType(), - field.getAnnotation(BindyConverter.class), - getLocale()); - Format<?> format = formatFactory.getFormat(formattingOptions); + // Get Field to be setted + Field field = annotatedFields.get(pos); + field.setAccessible(true); - // field object to be set - Object modelField = model.get(field.getDeclaringClass().getName()); + if (LOG.isDebugEnabled()) { + LOG.debug("Pos: {}, Data: {}, Field type: {}", new Object[]{pos, data, field.getType()}); + } - // format the data received - Object value = null; + // Create format object to format the field + FormattingOptions formattingOptions = ConverterUtils.convert(dataField, + field.getType(), + field.getAnnotation(BindyConverter.class), + getLocale()); + Format<?> format = formatFactory.getFormat(formattingOptions); - if (!data.equals("")) { - try { - if (quoting && quote != null && (data.contains("\\" + quote) || data.contains(quote)) && quotingEscaped) { - value = format.parse(data.replaceAll("\\\\" + quote, "\\" + quote)); - } else { - value = format.parse(data); - } - } catch (FormatException ie) { - throw new IllegalArgumentException(ie.getMessage() + ", position: " + pos + ", line: " + line, ie); - } catch (Exception e) { - throw new IllegalArgumentException("Parsing error detected for field defined at the position: " + pos + ", line: " + line, e); - } - } else { - if (!dataField.defaultValue().isEmpty()) { - value = format.parse(dataField.defaultValue()); + // field object to be set + Object modelField = model.get(field.getDeclaringClass().getName()); + + // format the data received + Object value = null; + + if (!data.equals("")) { + try { + if (quoting && quote != null && (data.contains("\\" + quote) || data.contains(quote)) && quotingEscaped) { + value = format.parse(data.replaceAll("\\\\" + quote, "\\" + quote)); } else { - value = getDefaultValueForPrimitive(field.getType()); + value = format.parse(data); } + } catch (FormatException ie) { + throw new IllegalArgumentException(ie.getMessage() + ", position: " + pos + ", line: " + line, ie); + } catch (Exception e) { + throw new IllegalArgumentException("Parsing error detected for field defined at the position: " + pos + ", line: " + line, e); } - - if (value != null && !dataField.method().isEmpty()) { - Class<?> clazz; - if (dataField.method().contains(".")) { - clazz = camelContext.getClassResolver().resolveMandatoryClass(dataField.method().substring(0, dataField.method().lastIndexOf("."))); - } else { - clazz = field.getType(); - } - - String methodName = dataField.method().substring(dataField.method().lastIndexOf(".") + 1, - dataField.method().length()); - - Method m = ReflectionHelper.findMethod(clazz, methodName, field.getType()); - if (m != null) { - // this method must be static and return type - // must be the same as the datafield and - // must receive only the datafield value - // as the method argument - value = ObjectHelper.invokeMethod(m, null, value); - } else { - // fallback to method without parameter, that is on the value itself - m = ReflectionHelper.findMethod(clazz, methodName); - value = ObjectHelper.invokeMethod(m, value); - } + } else { + if (!dataField.defaultValue().isEmpty()) { + value = format.parse(dataField.defaultValue()); + } else { + value = getDefaultValueForPrimitive(field.getType()); } - - field.set(modelField, value); - - ++pos; - } - LOG.debug("Counter mandatory fields: {}", counterMandatoryFields); + if (value != null && !dataField.method().isEmpty()) { + Class<?> clazz; + if (dataField.method().contains(".")) { + clazz = camelContext.getClassResolver().resolveMandatoryClass(dataField.method().substring(0, dataField.method().lastIndexOf("."))); + } else { + clazz = field.getType(); + } - if (counterMandatoryFields < numberMandatoryFields) { - throw new IllegalArgumentException("Some mandatory fields are missing, line: " + line); - } + String methodName = dataField.method().substring(dataField.method().lastIndexOf(".") + 1, + dataField.method().length()); - if (pos < totalFields) { - setDefaultValuesForFields(model); + Method m = ReflectionHelper.findMethod(clazz, methodName, field.getType()); + if (m != null) { + // this method must be static and return type + // must be the same as the datafield and + // must receive only the datafield value + // as the method argument + value = ObjectHelper.invokeMethod(m, null, value); + } else { + // fallback to method without parameter, that is on the value itself + m = ReflectionHelper.findMethod(clazz, methodName); + value = ObjectHelper.invokeMethod(m, value); + } } + field.set(modelField, value); + return counterMandatoryFields; } @Override @@ -719,4 +740,13 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor public boolean isEndWithLineBreak() { return endWithLineBreak; } + + /** + * Indicate if DataField can be ignored + * + * @return boolean + */ + public boolean isSkipField() { + return this.isSkipField; + } } diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/csv/BindyCsvDataFormat.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/csv/BindyCsvDataFormat.java index 00ea809..d076198 100755 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/csv/BindyCsvDataFormat.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/csv/BindyCsvDataFormat.java @@ -47,11 +47,21 @@ import org.slf4j.LoggerFactory; public class BindyCsvDataFormat extends BindyAbstractDataFormat { private static final Logger LOG = LoggerFactory.getLogger(BindyCsvDataFormat.class); + /** + * If isSkipField = true, a CSV file doesn't need to declare all the fields, otherwise, all the fields are mandatory. + */ + private boolean isSkipField; + public BindyCsvDataFormat() { } public BindyCsvDataFormat(Class<?> type) { + this(type, false); + } + + public BindyCsvDataFormat(Class<?> type, boolean isSkipField) { super(type); + this.isSkipField = isSkipField; } @Override @@ -310,8 +320,12 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { @Override protected BindyAbstractFactory createModelFactory(FormatFactory formatFactory) throws Exception { - BindyCsvFactory bindyCsvFactory = new BindyCsvFactory(getClassType()); + BindyCsvFactory bindyCsvFactory = new BindyCsvFactory(getClassType(), isSkipField()); bindyCsvFactory.setFormatFactory(formatFactory); return bindyCsvFactory; } + + private boolean isSkipField() { + return this.isSkipField; + } } diff --git a/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest.java b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest.java new file mode 100644 index 0000000..f9907b5 --- /dev/null +++ b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest.java @@ -0,0 +1,170 @@ +/** + * 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.bindy.csv; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.dataformat.bindy.annotation.CsvRecord; +import org.apache.camel.dataformat.bindy.annotation.DataField; +import org.junit.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; + + +@ContextConfiguration +public class BindyCsvSkipFieldTest extends AbstractJUnit4SpringContextTests { + + private static final String URI_MOCK_RESULT = "mock:result"; + private static final String URI_DIRECT_START = "direct:start"; + + private static String input = "VOA,12 abc street,Skip Street,Melbourne,VIC,3000,Australia,Skip dummy1,end of record"; + + @Produce(uri = URI_DIRECT_START) + private ProducerTemplate template; + + @EndpointInject(uri = URI_MOCK_RESULT) + private MockEndpoint result; + + private String expected; + + @Test + @DirtiesContext + public void testUnMarshalAndMarshal() throws Exception { + + template.sendBody(input); + result.expectedMessageCount(1); + result.assertIsSatisfied(); + } + + public static class ContextConfig extends RouteBuilder { + BindyCsvDataFormat camelDataFormat = new BindyCsvDataFormat(CsvSkipField.class, true); + + public void configure() { + from(URI_DIRECT_START).unmarshal(camelDataFormat) + .process(new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + CsvSkipField csvSkipField = (CsvSkipField) exchange.getIn().getBody(); + assert csvSkipField.getAttention().equals("VOA"); + assert csvSkipField.getAddressLine1().equals("12 abc street"); + assert csvSkipField.getCity().equals("Melbourne"); + assert csvSkipField.getState().equals("VIC"); + assert csvSkipField.getZip().equals("3000"); + assert csvSkipField.getCountry() .equals("Australia"); + assert csvSkipField.getDummy2().equals("end of record"); + } + }) + + .marshal(camelDataFormat) + .convertBodyTo(String.class) + .to(URI_MOCK_RESULT); + } + + } + + @CsvRecord(separator = ",") + public static class CsvSkipField { + @DataField(pos = 1) + private String attention; + + @DataField(pos = 2) + private String addressLine1; + + @DataField(pos = 4) + private String city; + + @DataField(pos = 5) + private String state; + + @DataField(pos = 6) + private String zip; + + @DataField(pos = 7) + private String country; + + @DataField(pos = 9) + private String dummy2; + + public String getAttention() { + return attention; + } + + public void setAttention(String attention) { + this.attention = attention; + } + + public String getAddressLine1() { + return addressLine1; + } + + public void setAddressLine1(String addressLine1) { + this.addressLine1 = addressLine1; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getDummy2() { + return dummy2; + } + + public void setDummy2(String dummy2) { + this.dummy2 = dummy2; + } + + @Override + public String toString() { + return "Record [attention=" + getAttention() + ", addressLine1=" + getAddressLine1() + ", " + "city=" + getCity() + ", state=" + getState() + ", zip=" + getZip() + ", country=" + + getCountry() + ", dummy2=" + getDummy2() + "]"; + } + } +} diff --git a/components/camel-bindy/src/test/resources/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest-context.xml b/components/camel-bindy/src/test/resources/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest-context.xml new file mode 100644 index 0000000..c1c6ebd --- /dev/null +++ b/components/camel-bindy/src/test/resources/org/apache/camel/dataformat/bindy/csv/BindyCsvSkipFieldTest-context.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring + http://camel.apache.org/schema/spring/camel-spring.xsd"> + + <camelContext xmlns="http://camel.apache.org/schema/spring"> + <routeBuilder ref="myBuilder" /> + </camelContext> + + <bean id="myBuilder" class="org.apache.camel.dataformat.bindy.csv.BindyCsvSkipFieldTest$ContextConfig"/> + +</beans> \ No newline at end of file -- To stop receiving notification emails like this one, please contact davscl...@apache.org.