You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by dm...@apache.org on 2019/01/22 16:37:47 UTC

[camel] 01/04: [CAMEL-13026] Add 'CsvMarshallerFactory' and corresponding tests

This is an automated email from the ASF dual-hosted git repository.

dmvolod pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit ba75f65a66c10f05a860620c00531c7f1080f132
Author: Christian Ribeaud <ch...@novartis.com>
AuthorDate: Wed Jan 9 22:08:07 2019 +0100

    [CAMEL-13026] Add 'CsvMarshallerFactory' and corresponding tests
---
 .../apache/camel/dataformat/csv/CsvDataFormat.java |  25 +++-
 .../apache/camel/dataformat/csv/CsvMarshaller.java |  19 ++-
 .../camel/dataformat/csv/CsvMarshallerFactory.java |  41 ++++++
 .../camel/dataformat/csv/CsvMarshalHeaderTest.java | 109 +++++++++++++++
 ...MarshalHeaderWithCustomMarshallFactoryTest.java | 153 +++++++++++++++++++++
 5 files changed, 342 insertions(+), 5 deletions(-)

diff --git a/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvDataFormat.java b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvDataFormat.java
index e9bee40..db19351 100644
--- a/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvDataFormat.java
+++ b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvDataFormat.java
@@ -69,6 +69,8 @@ public class CsvDataFormat extends ServiceSupport implements DataFormat, DataFor
     private boolean useOrderedMaps;
     private CsvRecordConverter<?> recordConverter;
 
+    private CsvMarshallerFactory marshallerFactory = CsvMarshallerFactory.DEFAULT;
+
     private volatile CsvMarshaller marshaller;
     private volatile CsvUnmarshaller unmarshaller;
 
@@ -94,7 +96,7 @@ public class CsvDataFormat extends ServiceSupport implements DataFormat, DataFor
 
     @Override
     protected void doStart() throws Exception {
-        marshaller = CsvMarshaller.create(getActiveFormat(), this);
+        marshaller = marshallerFactory.create(getActiveFormat(), this);
         unmarshaller = CsvUnmarshaller.create(getActiveFormat(), this);
     }
 
@@ -208,6 +210,27 @@ public class CsvDataFormat extends ServiceSupport implements DataFormat, DataFor
     }
 
     /**
+     * Sets the {@link CsvMarshaller} factory.
+     * If {@code null}, then {@link CsvMarshallerFactory#DEFAULT} is used instead.
+     *
+     * @param marshallerFactory
+     * @return Current {@code CsvDataFormat}, fluent API
+     */
+    public CsvDataFormat setMarshallerFactory(CsvMarshallerFactory marshallerFactory) {
+        this.marshallerFactory = (marshallerFactory == null) ? CsvMarshallerFactory.DEFAULT : marshallerFactory;
+        return this;
+    }
+
+    /**
+     * Returns the used {@link CsvMarshallerFactory}.
+     *
+     * @return never {@code null}.
+     */
+    public CsvMarshallerFactory getMarshallerFactory() {
+        return marshallerFactory;
+    }
+
+    /**
      * Sets the CSV format by name before applying any changes.
      *
      * @param name CSV format name
diff --git a/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshaller.java b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshaller.java
index 5c5ad70..648cb4a 100644
--- a/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshaller.java
+++ b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshaller.java
@@ -28,18 +28,18 @@ import java.util.Map;
 import org.apache.camel.Exchange;
 import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.support.ExchangeHelper;
-import org.apache.camel.util.IOHelper;
 import org.apache.camel.support.ObjectHelper;
+import org.apache.camel.util.IOHelper;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVPrinter;
 
 /**
  * This class marshal data into a CSV format.
  */
-abstract class CsvMarshaller {
+public abstract class CsvMarshaller {
     private final CSVFormat format;
 
-    private CsvMarshaller(CSVFormat format) {
+    protected CsvMarshaller(CSVFormat format) {
         this.format = format;
     }
 
@@ -73,7 +73,7 @@ abstract class CsvMarshaller {
      * @throws IOException                        if we cannot write into the given stream
      */
     public void marshal(Exchange exchange, Object object, OutputStream outputStream) throws NoTypeConversionAvailableException, IOException {
-        CSVPrinter printer = new CSVPrinter(new OutputStreamWriter(outputStream, ExchangeHelper.getCharsetName(exchange)), format);
+        CSVPrinter printer = createPrinter(exchange, outputStream);
         try {
             Iterator it = ObjectHelper.createIterator(object);
             while (it.hasNext()) {
@@ -85,6 +85,17 @@ abstract class CsvMarshaller {
         }
     }
 
+    /**
+     * Creates and returns a {@link CSVPrinter}.
+     *
+     * @param exchange     Exchange (used for access to type conversion). Could NOT be <code>null</code>.
+     * @param outputStream Output stream of the CSV. Could NOT be <code>null</code>.
+     * @return a new {@link CSVPrinter}. Never <code>null</code>.
+     */
+    protected CSVPrinter createPrinter(Exchange exchange, OutputStream outputStream) throws IOException {
+        return new CSVPrinter(new OutputStreamWriter(outputStream, ExchangeHelper.getCharsetName(exchange)), format);
+    }
+
     private Iterable<?> getRecordValues(Exchange exchange, Object data) throws NoTypeConversionAvailableException {
         // each row must be a map or list based
         Map<?, ?> map = exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, data);
diff --git a/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshallerFactory.java b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshallerFactory.java
new file mode 100644
index 0000000..e88db04
--- /dev/null
+++ b/components/camel-csv/src/main/java/org/apache/camel/dataformat/csv/CsvMarshallerFactory.java
@@ -0,0 +1,41 @@
+/**
+ * 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 org.apache.commons.csv.CSVFormat;
+
+/**
+ * A {@link CsvMarshaller} factory.
+ */
+public interface CsvMarshallerFactory {
+
+    CsvMarshallerFactory DEFAULT = new CsvMarshallerFactory() {
+        @Override
+        public CsvMarshaller create(CSVFormat format, CsvDataFormat dataFormat) {
+            return CsvMarshaller.create(format, dataFormat);
+        }
+    };
+
+    /**
+     * Creates and returns a new {@link CsvMarshaller}.
+     *
+     * @param format     the <b>CSV</b> format. Can NOT be <code>null</code>.
+     * @param dataFormat the <b>CSV</b> data format. Can NOT be <code>null</code>.
+     * @return a new {@link CsvMarshaller}.
+     */
+    CsvMarshaller create(CSVFormat format, CsvDataFormat dataFormat);
+}
diff --git a/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderTest.java b/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderTest.java
new file mode 100644
index 0000000..af7c689
--- /dev/null
+++ b/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderTest.java
@@ -0,0 +1,109 @@
+/**
+ * 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.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * <b>Camel</b> based test cases for {@link org.apache.camel.dataformat.csv.CsvDataFormat}.
+ */
+public class CsvMarshalHeaderTest extends CamelTestSupport {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    @Produce(uri = "direct:start")
+    private ProducerTemplate producerTemplate;
+
+    private File outputFile;
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        outputFile = new File(folder.newFolder(), "output.csv");
+    }
+
+    @Test
+    public void testSendBody() throws IOException {
+        Map<String, String> body = new LinkedHashMap<>();
+        body.put("first_name", "John");
+        body.put("last_name", "Doe");
+        String fileName = outputFile.getName();
+        assertEquals("output.csv", fileName);
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        body = new LinkedHashMap<>();
+        body.put("first_name", "Max");
+        body.put("last_name", "Mustermann");
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        List<String> lines = Files.lines(Paths.get(outputFile.toURI()))
+                .filter(l -> l.trim().length() > 0).collect(Collectors.toList());
+        // We got twice the headers... :(
+        assertEquals(4, lines.size());
+    }
+
+    @Test
+    public void testSendBodyWithList() throws IOException {
+        List<List<String>> body = Collections.singletonList(Arrays.asList("John", "Doe"));
+        String fileName = outputFile.getName();
+        assertEquals("output.csv", fileName);
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        body = Collections.singletonList(Arrays.asList("Max", "Mustermann"));
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        List<String> lines = Files.lines(Paths.get(outputFile.toURI()))
+                .filter(l -> l.trim().length() > 0).collect(Collectors.toList());
+        // We got twice the headers... :(
+        assertEquals(4, lines.size());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                String uri = String.format("file:%s?charset=utf-8&fileExist=Append", outputFile.getParentFile().getAbsolutePath());
+                from("direct:start").marshal(createCsvDataFormat()).to(uri);
+            }
+        };
+    }
+
+    private static CsvDataFormat createCsvDataFormat() {
+        CsvDataFormat dataFormat = new CsvDataFormat();
+        dataFormat.setDelimiter('\t');
+        dataFormat.setTrim(true);
+        dataFormat.setIgnoreSurroundingSpaces(true);
+        dataFormat.setHeader((String[]) Arrays.asList("first_name", "last_name").toArray());
+        return dataFormat;
+    }
+}
\ No newline at end of file
diff --git a/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderWithCustomMarshallFactoryTest.java b/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderWithCustomMarshallFactoryTest.java
new file mode 100644
index 0000000..0916bc4
--- /dev/null
+++ b/components/camel-csv/src/test/java/org/apache/camel/dataformat/csv/CsvMarshalHeaderWithCustomMarshallFactoryTest.java
@@ -0,0 +1,153 @@
+/**
+ * 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.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.ObjectHelper;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * <b>Camel</b> based test cases for {@link CsvDataFormat}.
+ */
+public class CsvMarshalHeaderWithCustomMarshallFactoryTest extends CamelTestSupport {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    @Produce(uri = "direct:start")
+    private ProducerTemplate producerTemplate;
+
+    private File outputFile;
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        outputFile = new File(folder.newFolder(), "output.csv");
+    }
+
+    @Test
+    public void testSendBody() throws IOException {
+        Map<String, String> body = new LinkedHashMap<>();
+        body.put("first_name", "John");
+        body.put("last_name", "Doe");
+        String fileName = outputFile.getName();
+        assertEquals("output.csv", fileName);
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        body = new LinkedHashMap<>();
+        body.put("first_name", "Max");
+        body.put("last_name", "Mustermann");
+        producerTemplate.sendBodyAndHeader(body, Exchange.FILE_NAME, fileName);
+        List<String> lines = Files.lines(Paths.get(outputFile.toURI()))
+                .filter(l -> l.trim().length() > 0).collect(Collectors.toList());
+        assertEquals(3, lines.size());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                String uri = String.format("file:%s?charset=utf-8&fileExist=Append", outputFile.getParentFile().getAbsolutePath());
+                from("direct:start").marshal(createCsvDataFormat()).to(uri);
+            }
+        };
+    }
+
+    private static CsvDataFormat createCsvDataFormat() {
+        CsvDataFormat dataFormat = new CsvDataFormat();
+        dataFormat.setDelimiter('\t');
+        dataFormat.setTrim(true);
+        dataFormat.setIgnoreSurroundingSpaces(true);
+        dataFormat.setHeader((String[]) Arrays.asList("first_name", "last_name").toArray());
+        dataFormat.setMarshallerFactory(new CsvMarshallerFactory() {
+
+            @Override
+            public CsvMarshaller create(CSVFormat format, CsvDataFormat dataFormat) {
+                return new SinglePrinterCsvMarshaller(format);
+            }
+        });
+        return dataFormat;
+    }
+
+    //
+    // Helper classes
+    //
+
+    private static final class SinglePrinterCsvMarshaller extends CsvMarshaller {
+
+        private final CSVPrinter printer;
+
+        private SinglePrinterCsvMarshaller(CSVFormat format) {
+            super(format);
+            printer = createPrinter(format);
+        }
+
+        private static CSVPrinter createPrinter(CSVFormat format) {
+            try {
+                // Headers and header comments are written out in the constructor already.
+                return format.print(new StringBuilder());
+            } catch (IOException e) {
+                throw RuntimeCamelException.wrapRuntimeCamelException(e);
+            }
+        }
+
+        public void marshal(Exchange exchange, Object object, OutputStream outputStream) throws IOException {
+            Iterator<Map<String, String>> it = (Iterator<Map<String, String>>) ObjectHelper.createIterator(object);
+            synchronized (printer) {
+                while (it.hasNext()) {
+                    printer.printRecord(getMapRecordValues(it.next()));
+                }
+                // Access the 'Appendable'
+                StringBuilder stringBuilder = (StringBuilder) printer.getOut();
+                outputStream.write(stringBuilder.toString().getBytes());
+                // Reset the 'Appendable' for the next exchange.
+                stringBuilder.setLength(0);
+            }
+        }
+
+        @Override
+        protected Iterable<?> getMapRecordValues(Map<?, ?> map) {
+            List<String> result = new ArrayList<>(map.size());
+            for (Object key : map.keySet()) {
+                result.add((String) map.get(key));
+            }
+            return result;
+        }
+    }
+}
\ No newline at end of file