Updated Branches: refs/heads/camel-2.12.x faff7402f -> 95cfd96c5
CAMEL-6381: Added autospanLine option to bindy CSV format. Thanks to Asbjorn Aarrestad for patch. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/95cfd96c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/95cfd96c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/95cfd96c Branch: refs/heads/camel-2.12.x Commit: 95cfd96c5697256769014eb635ec032ae6209ccb Parents: faff740 Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Oct 17 11:13:02 2013 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Oct 17 11:13:18 2013 +0200 ---------------------------------------------------------------------- .../csv/BindySimpleCsvAutospanLineTest.java | 78 ++++++++++++++++++++ .../simple/spanLastRecord/SpanLastRecord.java | 45 +++++++++++ .../camel/dataformat/bindy/BindyCsvFactory.java | 32 +++++--- .../dataformat/bindy/annotation/CsvRecord.java | 5 ++ .../bindy/csv/BindyCsvDataFormat.java | 47 ++++++++++-- 5 files changed, 192 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/95cfd96c/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindySimpleCsvAutospanLineTest.java ---------------------------------------------------------------------- diff --git a/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindySimpleCsvAutospanLineTest.java b/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindySimpleCsvAutospanLineTest.java new file mode 100644 index 0000000..0df3f6d --- /dev/null +++ b/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindySimpleCsvAutospanLineTest.java @@ -0,0 +1,78 @@ +/** + * 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 java.util.List; +import java.util.Map; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.dataformat.bindy.model.simple.spanLastRecord.SpanLastRecord; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.CastUtils; +import org.junit.Test; + +public class BindySimpleCsvAutospanLineTest extends CamelTestSupport { + + @Test + public void testUnmarshalNoNeedToSpanLine() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:unmarshal"); + mock.expectedMessageCount(1); + + template.sendBody("direct:unmarshal", "1,hei,kommentar"); + + assertMockEndpointsSatisfied(); + + final List<Map<?, SpanLastRecord>> rows = CastUtils.cast(mock.getReceivedExchanges().get(0).getIn().getBody(List.class)); + final SpanLastRecord order = rows.get(0).get(SpanLastRecord.class.getName()); + + assertEquals(1, order.getRecordId()); + assertEquals("hei", order.getName()); + assertEquals("kommentar", order.getComment()); + } + + @Test + public void testUnmarshalSpanningLine() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:unmarshal"); + mock.expectedMessageCount(1); + + template.sendBody("direct:unmarshal", "1,hei,kommentar,test,noe,hei"); + + assertMockEndpointsSatisfied(); + + final List<Map<?, SpanLastRecord>> rows = CastUtils.cast(mock.getReceivedExchanges().get(0).getIn().getBody(List.class)); + final SpanLastRecord order = rows.get(0).get(SpanLastRecord.class.getName()); + + assertEquals(1, order.getRecordId()); + assertEquals("hei", order.getName()); + assertEquals("kommentar,test,noe,hei", order.getComment()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + final BindyCsvDataFormat bindy = new BindyCsvDataFormat("org.apache.camel.dataformat.bindy.model.simple.spanLastRecord"); + + from("direct:unmarshal") + .unmarshal(bindy) + .to("mock:unmarshal"); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/95cfd96c/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/model/simple/spanLastRecord/SpanLastRecord.java ---------------------------------------------------------------------- diff --git a/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/model/simple/spanLastRecord/SpanLastRecord.java b/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/model/simple/spanLastRecord/SpanLastRecord.java new file mode 100644 index 0000000..bcb01ee --- /dev/null +++ b/components/camel-bindy/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/model/simple/spanLastRecord/SpanLastRecord.java @@ -0,0 +1,45 @@ +package org.apache.camel.dataformat.bindy.model.simple.spanLastRecord; + +import org.apache.camel.dataformat.bindy.annotation.CsvRecord; +import org.apache.camel.dataformat.bindy.annotation.DataField; + +@CsvRecord(separator = ",", autospanLine = true) +public class SpanLastRecord { + + @DataField(pos = 1) + private int recordId; + @DataField(pos = 2) + private String name; + @DataField(pos = 3) + private String comment; + + public int getRecordId() { + return recordId; + } + + public void setRecordId(final int recordId) { + this.recordId = recordId; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getComment() { + return comment; + } + + public void setComment(final String comment) { + this.comment = comment; + } + + @Override + public String toString() { + return "SpanLastRecord [recordId=" + recordId + ", name=" + name + ", comment=" + comment + "]"; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/95cfd96c/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/BindyCsvFactory.java ---------------------------------------------------------------------- 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 75fd63a2..29b3bcf 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 @@ -16,7 +16,6 @@ */ package org.apache.camel.dataformat.bindy; - import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; @@ -40,7 +39,6 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * The BindyCsvFactory is the class who allows to : Generate a model associated * to a CSV record, bind data from a record to the POJOs, export data of POJOs @@ -60,6 +58,7 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor private int numberOptionalFields; private int numberMandatoryFields; private int totalFields; + private int maxpos; private String separator; private boolean skipFirstLine; @@ -67,6 +66,7 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor private boolean messageOrdered; private String quote; private boolean quoting; + private boolean autospanLine; public BindyCsvFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception { super(resolver, packageNames); @@ -87,7 +87,7 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor * bind the data. This process will scan for classes according to the * package name provided, check the annotated classes and fields and * retrieve the separator of the CSV record - * + * * @throws Exception */ public void initCsvModel() throws Exception { @@ -100,9 +100,10 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor initCsvRecordParameters(); } + @Override public void initAnnotatedFields() { - int maxpos = 0; + maxpos = 0; for (Class<?> cl : models) { List<Field> linkFields = new ArrayList<Field>(); @@ -176,7 +177,7 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor if (dataField.trim()) { data = data.trim(); } - + if (dataField.required()) { // Increment counter of mandatory fields ++counterMandatoryFields; @@ -377,7 +378,6 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor } /** - * * Generate a table containing the data formatted and sorted with their position/offset * If the model is Ordered than a key is created combining the annotation @Section and Position of the field * If a relation @OneToMany is defined, than we iterate recursively through this function @@ -482,10 +482,10 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor } } - + /** * Generate for the first line the headers of the columns - * + * * @return the headers columns */ public String generateHeader() { @@ -564,6 +564,9 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor quoting = record.quoting(); LOG.debug("CSV will be quoted: {}", messageOrdered); + + autospanLine = record.autospanLine(); + LOG.debug("Autospan line in last record: {}", autospanLine); } if (section != null) { @@ -620,8 +623,15 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor } /** + * If last record is to span the rest of the line + */ + public boolean getAutospanLine() { + return autospanLine; + } + + /** * Flag indicating if the message must be ordered - * + * * @return boolean */ public boolean isMessageOrdered() { @@ -631,4 +641,8 @@ public class BindyCsvFactory extends BindyAbstractFactory implements BindyFactor public String getQuote() { return quote; } + + public int getMaxpos() { + return maxpos; + } } http://git-wip-us.apache.org/repos/asf/camel/blob/95cfd96c/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/CsvRecord.java ---------------------------------------------------------------------- diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/CsvRecord.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/CsvRecord.java index 238ec58..d6573e7 100755 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/CsvRecord.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/CsvRecord.java @@ -80,4 +80,9 @@ public @interface CsvRecord { */ boolean quoting() default false; + /** + * Last record spans rest of line (optional) + */ + boolean autospanLine() default false; + } http://git-wip-us.apache.org/repos/asf/camel/blob/95cfd96c/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/csv/BindyCsvDataFormat.java ---------------------------------------------------------------------- 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 5e1cac9..312ddd0 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 @@ -150,7 +150,7 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { // Create POJO where CSV data will be stored model = factory.factory(); - + // Split the CSV record according to the separator defined in // annotated class @CSVRecord String[] tokens = line.split(separator, -1); @@ -160,13 +160,15 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { if (result.size() == 0 || result.isEmpty()) { throw new java.lang.IllegalArgumentException("No records have been defined in the CSV"); - } - - if (result.size() > 0) { + } else { if (LOG.isDebugEnabled()) { LOG.debug("Size of the record splitted : {}", result.size()); } + if (factory.getAutospanLine()) { + result = autospanLine(result, factory.getMaxpos(), separator); + } + // Bind data from CSV record with model classes factory.bind(result, model, count); @@ -196,6 +198,38 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { } /** + * Concatenate "the rest of the line" as the last record. Works similar as if quoted + * + * @param result input result set + * @param maxpos position of maximum record + * @param separator csv separator char + * @return List<String> with concatenated last record + */ + private static List<String> autospanLine(final List<String> result, final int maxpos, final String separator) { + if (result.size() <= maxpos) { + return result; + } + + final List<String> answer = new ArrayList<String>(); + final StringBuilder lastRecord = new StringBuilder(); + + final Iterator<String> it = result.iterator(); + for (int counter = 0; counter < maxpos - 1; counter++) { + answer.add(it.next()); + } + + while (it.hasNext()) { + lastRecord.append(it.next()); + if (it.hasNext()) { + lastRecord.append(separator); + } + } + answer.add(lastRecord.toString()); + + return answer; + } + + /** * Unquote the tokens, by removing leading and trailing quote chars, * as will handling fixing broken tokens which may have been split * by a separator inside a quote. @@ -224,7 +258,7 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { // are we in progress of rebuilding a broken token boolean currentInProgress = current.length() > 0; - // situation when field ending with a separator symbol. + // situation when field ending with a separator symbol. if (currentInProgress && startQuote && s.isEmpty()) { // Add separator, append current and reset it current.append(separator); @@ -232,7 +266,7 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { current.setLength(0); continue; } - + // if we hit a start token then rebuild a broken token if (currentInProgress || startQuote) { // append to current if we are in the middle of a start quote @@ -266,6 +300,7 @@ public class BindyCsvDataFormat extends BindyAbstractDataFormat { return answer; } + @Override protected BindyAbstractFactory createModelFactory(PackageScanClassResolver resolver) throws Exception { if (getClassType() != null) { return new BindyCsvFactory(resolver, getClassType());