From 8dcc4b95ba5fa37f6f08a3d6bf4229ef540472a1 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 19:42:47 +0900 Subject: [PATCH 01/23] Add hdfs config debug log --- .../org/embulk/input/hdfs/HdfsFileInputPlugin.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 30c89ee..d718e8e 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -81,8 +82,8 @@ public ConfigDiff transaction(ConfigSource config, FileInputPlugin.Control contr throw new PathNotFoundException(pathString); } + logger.debug("embulk-input-hdfs: Loading target files: {}", originalFileList); task.setFiles(allocateHdfsFilesToTasks(task, getFs(task), originalFileList)); - logger.info("embulk-input-hdfs: Loading target files: {}", originalFileList); } catch (IOException e) { logger.error(e.getMessage()); @@ -181,6 +182,14 @@ private static FileSystem getFs(final PluginTask task) configuration.set(entry.getKey(), entry.getValue()); } + // For debug + Iterator> entryIterator = configuration.iterator(); + while (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + logger.debug("{}: {}", entry.getKey(), entry.getValue()); + } + logger.debug("Resource Files: {}", configuration); + return FileSystem.get(configuration); } From 6cfb5c2ab483110f31f9b720eb4d1eb27f2c4522 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 19:54:18 +0900 Subject: [PATCH 02/23] Add tests dependencies --- build.gradle | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6f47812..7a14044 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,8 @@ plugins { id "com.jfrog.bintray" version "1.1" id "com.github.jruby-gradle.base" version "0.1.5" + id "com.github.kt3k.coveralls" version "2.4.0" + id "jacoco" id "java" } import com.github.jrubygradle.JRubyExec @@ -22,8 +24,15 @@ dependencies { provided "org.embulk:embulk-core:0.7.0" // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION" compile 'org.apache.hadoop:hadoop-client:2.6.0' - compile 'com.google.guava:guava:15.0' testCompile "junit:junit:4.+" + testCompile "org.embulk:embulk-core:0.7.+:tests" +} + +jacocoTestReport { + reports { + xml.enabled = true // coveralls plugin depends on xml format report + html.enabled = true + } } task classpath(type: Copy, dependsOn: ["jar"]) { From efab2521c50f0a5fb2eaa71e2d94bd87e4d18bae Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 19:54:50 +0900 Subject: [PATCH 03/23] Bump up embulk dependency version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7a14044..fb14168 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ sourceCompatibility = 1.7 targetCompatibility = 1.7 dependencies { - compile "org.embulk:embulk-core:0.7.0" - provided "org.embulk:embulk-core:0.7.0" + compile "org.embulk:embulk-core:0.7.+" + provided "org.embulk:embulk-core:0.7.+" // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION" compile 'org.apache.hadoop:hadoop-client:2.6.0' testCompile "junit:junit:4.+" From b879d571492f078ba4e60201eec9084b1756bcb5 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 20:08:07 +0900 Subject: [PATCH 04/23] Update timestamp --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 06b273e..21e9013 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Tue Aug 11 00:26:20 PDT 2015 +#Wed Jan 06 20:05:55 JST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 5eae01c0e05fdc4d3e22a5766d41d8b2b28431af Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 20:52:41 +0900 Subject: [PATCH 05/23] Add dependency to use csv parser plugin for tests --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index fb14168..6f0ecd0 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ dependencies { compile 'org.apache.hadoop:hadoop-client:2.6.0' testCompile "junit:junit:4.+" testCompile "org.embulk:embulk-core:0.7.+:tests" + testCompile "org.embulk:embulk-standards:0.7.+" } jacocoTestReport { From b1dec8ff35e47b986d2ea8eb1df8c87d4b5a3f16 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 20:57:27 +0900 Subject: [PATCH 06/23] Add sample resources (from embulk-input-gcs) --- src/test/resources/sample_01.csv | 5 +++++ src/test/resources/sample_02.csv | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/test/resources/sample_01.csv create mode 100644 src/test/resources/sample_02.csv diff --git a/src/test/resources/sample_01.csv b/src/test/resources/sample_01.csv new file mode 100644 index 0000000..be0e659 --- /dev/null +++ b/src/test/resources/sample_01.csv @@ -0,0 +1,5 @@ +id,account,time,purchase,comment +1,32864,2015-01-27 19:23:49,20150127,embulk +2,14824,2015-01-27 19:01:23,20150127,embulk jruby +3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin" +4,11270,2015-01-29 11:54:36,20150129,NULL diff --git a/src/test/resources/sample_02.csv b/src/test/resources/sample_02.csv new file mode 100644 index 0000000..be0e659 --- /dev/null +++ b/src/test/resources/sample_02.csv @@ -0,0 +1,5 @@ +id,account,time,purchase,comment +1,32864,2015-01-27 19:23:49,20150127,embulk +2,14824,2015-01-27 19:01:23,20150127,embulk jruby +3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin" +4,11270,2015-01-29 11:54:36,20150129,NULL From b291df247ecf347e36839c8b2a7a48dd498a7fd7 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 22:56:32 +0900 Subject: [PATCH 07/23] Add basic tests --- .../input/hdfs/TestHdfsFileInputPlugin.java | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java index b8eb7c7..0089beb 100644 --- a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java +++ b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java @@ -1,5 +1,238 @@ package org.embulk.input.hdfs; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.hadoop.fs.Path; +import org.embulk.EmbulkTestRuntime; +import org.embulk.config.ConfigException; +import org.embulk.config.ConfigSource; +import org.embulk.config.TaskReport; +import org.embulk.config.TaskSource; +import org.embulk.input.hdfs.HdfsFileInputPlugin.PluginTask; +import org.embulk.spi.Exec; +import org.embulk.spi.FileInputPlugin; +import org.embulk.spi.FileInputRunner; +import org.embulk.spi.InputPlugin; +import org.embulk.spi.Schema; +import org.embulk.spi.TestPageBuilderReader.MockPageOutput; +import org.embulk.spi.util.Pages; +import org.embulk.standards.CsvParserPlugin; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNotNull; + public class TestHdfsFileInputPlugin { + + @Rule + public EmbulkTestRuntime runtime = new EmbulkTestRuntime(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + private Logger logger = runtime.getExec().getLogger(TestHdfsFileInputPlugin.class); + private HdfsFileInputPlugin plugin; + private FileInputRunner runner; + private MockPageOutput output; + private Path path; + + @Before + public void createResources() + { + plugin = new HdfsFileInputPlugin(); + runner = new FileInputRunner(runtime.getInstance(HdfsFileInputPlugin.class)); + output = new MockPageOutput(); + path = new Path(new File(getClass().getResource("/sample_01.csv").getPath()).getParent()); + } + + @Test + public void testDefaultValues() + { + ConfigSource config = Exec.newConfigSource() + .set("path", path.toString()); + PluginTask task = config.loadConfig(PluginTask.class); + assertEquals(path.toString(), task.getPath()); + assertEquals(Lists.newArrayList(), task.getConfigFiles()); + assertEquals(Maps.newHashMap(), task.getConfig()); + assertEquals(true, task.getPartition()); + assertEquals(0, task.getRewindSeconds()); + assertEquals(-1, task.getApproximateNumPartitions()); + } + + @Test(expected = ConfigException.class) + public void testRequiredValues() + { + ConfigSource config = Exec.newConfigSource(); + PluginTask task = config.loadConfig(PluginTask.class); + } + + @Test + public void testFileList() + { + ConfigSource config = getConfigWithDefaultValues(); + config.set("num_partitions", 1); + plugin.transaction(config, new FileInputPlugin.Control() + { + @Override + public List run(TaskSource taskSource, int taskCount) + { + PluginTask task = taskSource.loadTask(PluginTask.class); + List fileList = Lists.transform(Lists.newArrayList(new File(path.toString()).list()), new Function() + { + @Nullable + @Override + public String apply(@Nullable String input) + { + return new File(path.toString() + "/" + input).toURI().toString(); + } + }); + + List resultFList = Lists.transform(task.getFiles(), new Function() + { + @Nullable + @Override + public String apply(@Nullable HdfsPartialFile input) + { + assert input != null; + return input.getPath(); + } + }); + assertEquals(fileList, resultFList); + return emptyTaskReports(taskCount); + } + }); + } + + @Test + public void testHdfsFileInputByOpen() + { + ConfigSource config = getConfigWithDefaultValues(); + config.set("num_partitions", 10); + runner.transaction(config, new Control()); + assertRecords(config, output); + } + + @Test + public void testHdfsFileInputByOpenWithoutPartition() + { + ConfigSource config = getConfigWithDefaultValues(); + config.set("partition", false); + runner.transaction(config, new Control()); + assertRecords(config, output); + } + + private class Control + implements InputPlugin.Control + { + @Override + public List run(TaskSource taskSource, Schema schema, int taskCount) + { + List reports = new ArrayList<>(); + for (int i = 0; i < taskCount; i++) { + reports.add(runner.run(taskSource, schema, i, output)); + } + return reports; + } + } + + private ConfigSource getConfigWithDefaultValues() + { + return Exec.newConfigSource() + .set("path", path.toString()) + .set("config", hdfsLocalFSConfig()) + .set("parser", parserConfig(schemaConfig())); + } + + static List emptyTaskReports(int taskCount) + { + ImmutableList.Builder reports = new ImmutableList.Builder<>(); + for (int i = 0; i < taskCount; i++) { + reports.add(Exec.newTaskReport()); + } + return reports.build(); + } + + private ImmutableMap hdfsLocalFSConfig() + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put("fs.hdfs.impl", "org.apache.hadoop.fs.LocalFileSystem"); + builder.put("fs.file.impl", "org.apache.hadoop.fs.LocalFileSystem"); + builder.put("fs.defaultFS", "file:///"); + return builder.build(); + } + + private ImmutableMap parserConfig(ImmutableList schemaConfig) + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + builder.put("type", "csv"); + builder.put("newline", "CRLF"); + builder.put("delimiter", ","); + builder.put("quote", "\""); + builder.put("escape", "\""); + builder.put("trim_if_not_quoted", false); + builder.put("skip_header_lines", 1); + builder.put("allow_extra_columns", false); + builder.put("allow_optional_columns", false); + builder.put("columns", schemaConfig); + return builder.build(); + } + + private ImmutableList schemaConfig() + { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + builder.add(ImmutableMap.of("name", "id", "type", "long")); + builder.add(ImmutableMap.of("name", "account", "type", "long")); + builder.add(ImmutableMap.of("name", "time", "type", "timestamp", "format", "%Y-%m-%d %H:%M:%S")); + builder.add(ImmutableMap.of("name", "purchase", "type", "timestamp", "format", "%Y%m%d")); + builder.add(ImmutableMap.of("name", "comment", "type", "string")); + return builder.build(); + } + + private void assertRecords(ConfigSource config, MockPageOutput output) + { + List records = getRecords(config, output); + assertEquals(8, records.size()); + { + Object[] record = records.get(0); + assertEquals(1L, record[0]); + assertEquals(32864L, record[1]); + assertEquals("2015-01-27 19:23:49 UTC", record[2].toString()); + assertEquals("2015-01-27 00:00:00 UTC", record[3].toString()); + assertEquals("embulk", record[4]); + } + + { + Object[] record = records.get(1); + assertEquals(2L, record[0]); + assertEquals(14824L, record[1]); + assertEquals("2015-01-27 19:01:23 UTC", record[2].toString()); + assertEquals("2015-01-27 00:00:00 UTC", record[3].toString()); + assertEquals("embulk jruby", record[4]); + } + } + + private List getRecords(ConfigSource config, MockPageOutput output) + { + Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema(); + return Pages.toObjects(schema, output.pages); + } } From f250ef88ce1a55dc0026c872aedff489519db99c Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 23:00:40 +0900 Subject: [PATCH 08/23] Add batches --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2a35c5f..ae9ecab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Hdfs file input plugin for Embulk +[![Build Status](https://travis-ci.org/civitaspo/embulk-input-hdfs.svg)](https://travis-ci.org/civitaspo/embulk-input-hdfs) +[![Coverage Status](https://coveralls.io/repos/civitaspo/embulk-input-hdfs/badge.svg?branch=master&service=github)](https://coveralls.io/github/civitaspo/embulk-input-hdfs?branch=master) Read files on Hdfs. From 6929d5d2490786852edc41dc64954d473c689e9d Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 23:09:54 +0900 Subject: [PATCH 09/23] Add examples --- example/config.yml | 35 +++++++++++++++++++++++++++++++++++ example/data.csv | 5 +++++ 2 files changed, 40 insertions(+) create mode 100644 example/config.yml create mode 100644 example/data.csv diff --git a/example/config.yml b/example/config.yml new file mode 100644 index 0000000..982d42c --- /dev/null +++ b/example/config.yml @@ -0,0 +1,35 @@ +hdfs_example: &hdfs_example + config_files: + - /etc/hadoop/conf/core-site.xml + - /etc/hadoop/conf/hdfs-site.xml + config: + fs.defaultFS: 'hdfs://hadoop-nn1:8020' + fs.hdfs.impl: 'org.apache.hadoop.hdfs.DistributedFileSystem' + fs.file.impl: 'org.apache.hadoop.fs.LocalFileSystem' + +local_fs_example: &local_fs_example + config: + fs.defaultFS: 'file:///' + fs.hdfs.impl: 'org.apache.hadoop.fs.LocalFileSystem' + fs.file.impl: 'org.apache.hadoop.fs.LocalFileSystem' + +in: + type: hdfs + <<: *local_fs_example + path: example/data.csv + parser: + charset: UTF-8 + newline: CRLF + type: csv + delimiter: ',' + quote: '"' + header_line: true + columns: + - {name: id, type: long} + - {name: account, type: long} + - {name: time, type: timestamp, format: '%Y-%m-%d %H:%M:%S'} + - {name: purchase, type: timestamp, format: '%Y%m%d'} + - {name: comment, type: string} + +out: + type: stdout diff --git a/example/data.csv b/example/data.csv new file mode 100644 index 0000000..be0e659 --- /dev/null +++ b/example/data.csv @@ -0,0 +1,5 @@ +id,account,time,purchase,comment +1,32864,2015-01-27 19:23:49,20150127,embulk +2,14824,2015-01-27 19:01:23,20150127,embulk jruby +3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin" +4,11270,2015-01-29 11:54:36,20150129,NULL From 508316a4184ce82b260495e756cded54057e901b Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 23:10:22 +0900 Subject: [PATCH 10/23] Change log level: hadoop configuration --- src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index d718e8e..896f7ac 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -186,7 +186,7 @@ private static FileSystem getFs(final PluginTask task) Iterator> entryIterator = configuration.iterator(); while (entryIterator.hasNext()) { Map.Entry entry = entryIterator.next(); - logger.debug("{}: {}", entry.getKey(), entry.getValue()); + logger.trace("{}: {}", entry.getKey(), entry.getValue()); } logger.debug("Resource Files: {}", configuration); From 7f10aedd88fd0e24828cb2ce6262a482a4363b9f Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 23:11:52 +0900 Subject: [PATCH 11/23] Change hadoop config path to common path --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae9ecab..b34cba8 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Read files on Hdfs. in: type: hdfs config_files: - - /opt/analytics/etc/hadoop/conf/core-site.xml - - /opt/analytics/etc/hadoop/conf/hdfs-site.xml + - /etc/hadoop/conf/core-site.xml + - /etc/hadoop/conf/hdfs-site.xml config: fs.defaultFS: 'hdfs://hadoop-nn1:8020' dfs.replication: 1 @@ -108,4 +108,4 @@ $ ./gradlew gem ``` $ ./gradlew classpath $ bundle exec embulk run -I lib example.yml -``` \ No newline at end of file +``` From deeea2a532d81a9d0ce4f1fccd60061396bbd87b Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Wed, 6 Jan 2016 23:50:23 +0900 Subject: [PATCH 12/23] Support too small file partition --- src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 896f7ac..9901584 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -245,6 +245,7 @@ private List lsr(final FileSystem fs, FileStatus status) private List allocateHdfsFilesToTasks(final PluginTask task, final FileSystem fs, final List fileList) throws IOException { + List pathList = Lists.transform(fileList, new Function() { @Nullable @@ -264,6 +265,9 @@ public Path apply(@Nullable String input) long approximateNumPartitions = (task.getApproximateNumPartitions() <= 0) ? Runtime.getRuntime().availableProcessors() : task.getApproximateNumPartitions(); long partitionSizeByOneTask = totalFileLength / approximateNumPartitions; + if (partitionSizeByOneTask <= 0) { + partitionSizeByOneTask = 1; + } List hdfsPartialFiles = new ArrayList<>(); for (Path path : pathList) { From 086ae6b8c84232d706da44dc172d5373ef6bfa33 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Thu, 7 Jan 2016 02:36:25 +0900 Subject: [PATCH 13/23] Support headerline --- .../input/hdfs/HdfsFileInputPlugin.java | 50 ++++++++++++++++++- .../input/hdfs/TestHdfsFileInputPlugin.java | 1 + 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 9901584..9d7acec 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -24,9 +24,14 @@ import org.slf4j.Logger; import javax.annotation.Nullable; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.SequenceInputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -61,6 +66,10 @@ public interface PluginTask extends Task @ConfigDefault("-1") // Default: Runtime.getRuntime().availableProcessors() public long getApproximateNumPartitions(); + @Config("skip_header_lines") + @ConfigDefault("0") + public int getSkipHeaderLines(); + public List getFiles(); public void setFiles(List hdfsFiles); @@ -139,8 +148,12 @@ public TransactionalFileInput open(TaskSource taskSource, int taskIndex) final PluginTask task = taskSource.loadTask(PluginTask.class); InputStream input; + final HdfsPartialFile file = task.getFiles().get(taskIndex); try { - input = openInputStream(task, task.getFiles().get(taskIndex)); + input = openInputStream(task, file); + if (file.getStart() > 0 && task.getSkipHeaderLines() > 0) { + input = new SequenceInputStream(openWithHeaders(task, file), input); + } } catch (IOException e) { logger.error(e.getMessage()); @@ -160,6 +173,41 @@ public TaskReport commit() }; } + private InputStream openWithHeaders(PluginTask task, HdfsPartialFile partialFile) throws IOException + { + FileSystem fs = getFs(task); + ByteArrayOutputStream header = new ByteArrayOutputStream(); + int skippedHeaders = 0; + + try (BufferedInputStream in = new BufferedInputStream(fs.open(new Path(partialFile.getPath())))) { + while (true) { + int c = in.read(); + if (c < 0) { + break; + } + + header.write(c); + + if (c == '\n') { + skippedHeaders++; + } + else if (c == '\r') { + int c2 = in.read(); + if (c2 == '\n') { + header.write(c2); + } + skippedHeaders++; + } + + if (skippedHeaders >= task.getSkipHeaderLines()) { + break; + } + } + } + header.close(); + return new ByteArrayInputStream(header.toByteArray()); + } + private static HdfsPartialFileInputStream openInputStream(PluginTask task, HdfsPartialFile partialFile) throws IOException { diff --git a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java index 0089beb..6126f0d 100644 --- a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java +++ b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java @@ -159,6 +159,7 @@ private ConfigSource getConfigWithDefaultValues() return Exec.newConfigSource() .set("path", path.toString()) .set("config", hdfsLocalFSConfig()) + .set("skip_header_lines", 1) .set("parser", parserConfig(schemaConfig())); } From 15ede88978c164e58ed8660da2092dcd1ca20d41 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Thu, 7 Jan 2016 02:45:34 +0900 Subject: [PATCH 14/23] Comment & Update README --- README.md | 1 + .../java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b34cba8..73a234f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Read files on Hdfs. - **rewind_seconds** When you use Date format in input_path property, the format is executed by using the time which is Now minus this property. - **partition** when this is true, partition input files and increase task count. (default: `true`) - **num_partitions** number of partitions. (default: `Runtime.getRuntime().availableProcessors()`) +- **skip_header_lines** Skip this number of lines first. Set 1 if the file has header line. (default: `0`) ## Example diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 9d7acec..cf19416 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -66,8 +66,8 @@ public interface PluginTask extends Task @ConfigDefault("-1") // Default: Runtime.getRuntime().availableProcessors() public long getApproximateNumPartitions(); - @Config("skip_header_lines") - @ConfigDefault("0") + @Config("skip_header_lines") // Skip this number of lines first. Set 1 if the file has header line. + @ConfigDefault("0") // The reason why the parameter is configured is that this plugin splits files. public int getSkipHeaderLines(); public List getFiles(); @@ -152,7 +152,7 @@ public TransactionalFileInput open(TaskSource taskSource, int taskIndex) try { input = openInputStream(task, file); if (file.getStart() > 0 && task.getSkipHeaderLines() > 0) { - input = new SequenceInputStream(openWithHeaders(task, file), input); + input = new SequenceInputStream(openHeaders(task, file), input); } } catch (IOException e) { @@ -173,7 +173,7 @@ public TaskReport commit() }; } - private InputStream openWithHeaders(PluginTask task, HdfsPartialFile partialFile) throws IOException + private InputStream openHeaders(PluginTask task, HdfsPartialFile partialFile) throws IOException { FileSystem fs = getFs(task); ByteArrayOutputStream header = new ByteArrayOutputStream(); From 5a3dc9fbde6518d6a5b0d9d9ef588632d972ad5e Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Fri, 8 Jan 2016 23:18:18 +0900 Subject: [PATCH 15/23] Rename method --- .../java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index cf19416..12fa96a 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -150,9 +150,11 @@ public TransactionalFileInput open(TaskSource taskSource, int taskIndex) InputStream input; final HdfsPartialFile file = task.getFiles().get(taskIndex); try { - input = openInputStream(task, file); if (file.getStart() > 0 && task.getSkipHeaderLines() > 0) { - input = new SequenceInputStream(openHeaders(task, file), input); + input = new SequenceInputStream(getHeadersInputStream(task, file), openInputStream(task, file)); + } + else { + input = openInputStream(task, file); } } catch (IOException e) { @@ -173,7 +175,7 @@ public TaskReport commit() }; } - private InputStream openHeaders(PluginTask task, HdfsPartialFile partialFile) throws IOException + private InputStream getHeadersInputStream(PluginTask task, HdfsPartialFile partialFile) throws IOException { FileSystem fs = getFs(task); ByteArrayOutputStream header = new ByteArrayOutputStream(); From 98f8d2defac97356b2f96241d9852b1726ffb890 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Fri, 8 Jan 2016 23:23:52 +0900 Subject: [PATCH 16/23] Add .travis.yml --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..78a7c0b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: java +jdk: + - openjdk7 + - oraclejdk7 + - oraclejdk8 +script: + - ./gradlew test +after_success: + - ./gradlew jacocoTestReport coveralls \ No newline at end of file From 837814b8ab652eaaddc8cea995234ad0effadc08 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Fri, 8 Jan 2016 23:50:35 +0900 Subject: [PATCH 17/23] Add checkstyle --- build.gradle | 5 ++ config/checkstyle/checkstyle.xml | 127 +++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 config/checkstyle/checkstyle.xml diff --git a/build.gradle b/build.gradle index 6f0ecd0..c3985ed 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id "com.jfrog.bintray" version "1.1" id "com.github.jruby-gradle.base" version "0.1.5" id "com.github.kt3k.coveralls" version "2.4.0" + id "checkstyle" id "jacoco" id "java" } @@ -36,6 +37,10 @@ jacocoTestReport { } } +checkstyle { + toolVersion = "6.7" +} + task classpath(type: Copy, dependsOn: ["jar"]) { doFirst { file("classpath").deleteDir() } from (configurations.runtime - configurations.provided + files(jar.archivePath)) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..7d77578 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 26396653db90609596b3ec9a5f20139cb661172b Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Fri, 8 Jan 2016 23:50:49 +0900 Subject: [PATCH 18/23] dress up! --- .../input/hdfs/HdfsFileInputPlugin.java | 25 +++++++++------- .../input/hdfs/HdfsFilePartitioner.java | 3 +- .../embulk/input/hdfs/HdfsPartialFile.java | 3 -- .../hdfs/HdfsPartialFileInputStream.java | 29 ++++++++++++------- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 12fa96a..b99771e 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -24,11 +24,11 @@ import org.slf4j.Logger; import javax.annotation.Nullable; + import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; @@ -37,11 +37,13 @@ import java.util.List; import java.util.Map; -public class HdfsFileInputPlugin implements FileInputPlugin +public class HdfsFileInputPlugin + implements FileInputPlugin { private static final Logger logger = Exec.getLogger(HdfsFileInputPlugin.class); - public interface PluginTask extends Task + public interface PluginTask + extends Task { @Config("config_files") @ConfigDefault("[]") @@ -71,6 +73,7 @@ public interface PluginTask extends Task public int getSkipHeaderLines(); public List getFiles(); + public void setFiles(List hdfsFiles); @ConfigInject @@ -114,8 +117,8 @@ public ConfigDiff transaction(ConfigSource config, FileInputPlugin.Control contr @Override public ConfigDiff resume(TaskSource taskSource, - int taskCount, - FileInputPlugin.Control control) + int taskCount, + FileInputPlugin.Control control) { control.run(taskSource, taskCount); @@ -137,8 +140,8 @@ public ConfigDiff resume(TaskSource taskSource, @Override public void cleanup(TaskSource taskSource, - int taskCount, - List successTaskReports) + int taskCount, + List successTaskReports) { } @@ -162,7 +165,8 @@ public TransactionalFileInput open(TaskSource taskSource, int taskIndex) throw new RuntimeException(e); } - return new InputStreamTransactionalFileInput(task.getBufferAllocator(), input) { + return new InputStreamTransactionalFileInput(task.getBufferAllocator(), input) + { @Override public void abort() { } @@ -175,7 +179,8 @@ public TaskReport commit() }; } - private InputStream getHeadersInputStream(PluginTask task, HdfsPartialFile partialFile) throws IOException + private InputStream getHeadersInputStream(PluginTask task, HdfsPartialFile partialFile) + throws IOException { FileSystem fs = getFs(task); ByteArrayOutputStream header = new ByteArrayOutputStream(); @@ -228,7 +233,7 @@ private static FileSystem getFs(final PluginTask task) configuration.addResource(file.toURI().toURL()); } - for (Map.Entry entry: task.getConfig().entrySet()) { + for (Map.Entry entry : task.getConfig().entrySet()) { configuration.set(entry.getKey(), entry.getValue()); } diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFilePartitioner.java b/src/main/java/org/embulk/input/hdfs/HdfsFilePartitioner.java index 3d18ab1..521ceb4 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFilePartitioner.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFilePartitioner.java @@ -23,7 +23,8 @@ public HdfsFilePartitioner(FileSystem fs, Path path, long numPartitions) this.numPartitions = numPartitions; } - public List getHdfsPartialFiles() throws IOException + public List getHdfsPartialFiles() + throws IOException { List hdfsPartialFiles = new ArrayList<>(); long size = fs.getFileStatus(path).getLen(); diff --git a/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java b/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java index f71ef8c..31ade6e 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java @@ -1,7 +1,5 @@ package org.embulk.input.hdfs; -import org.apache.hadoop.fs.Path; - /** * Created by takahiro.nakayama on 8/20/15. */ @@ -36,5 +34,4 @@ public long getEnd() { return end; } - } diff --git a/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java b/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java index 1bf8e5b..85127e5 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java @@ -6,7 +6,8 @@ import java.io.PushbackInputStream; // ref. https://github.com/hito4t/embulk-input-filesplit/blob/master/src/main/java/org/embulk/input/filesplit/PartialFileInputStream.java -public class HdfsPartialFileInputStream extends InputStream +public class HdfsPartialFileInputStream + extends InputStream { private final PushbackInputStream original; private long start; @@ -23,13 +24,15 @@ public HdfsPartialFileInputStream(InputStream original, long start, long end) } @Override - public int read(byte[] b) throws IOException + public int read(byte[] b) + throws IOException { return read(b, 0, b.length); } @Override - public int read(byte[] b, int off, int len) throws IOException + public int read(byte[] b, int off, int len) + throws IOException { initializeIfNeeded(); @@ -45,7 +48,7 @@ public int read(byte[] b, int off, int len) throws IOException current += read; if (current >= end) { - for (int i = Math.max((int)(end - 1 - current + read), 0); i < read; i++) { + for (int i = Math.max((int) (end - 1 - current + read), 0); i < read; i++) { if (b[off + i] == '\n') { eof = true; return i + 1; @@ -65,7 +68,8 @@ public int read(byte[] b, int off, int len) throws IOException } @Override - public int read() throws IOException + public int read() + throws IOException { initializeIfNeeded(); @@ -91,7 +95,8 @@ public int read() throws IOException } @Override - public long skip(long n) throws IOException + public long skip(long n) + throws IOException { throw new IOException("Skip not supported."); /* @@ -102,18 +107,21 @@ public long skip(long n) throws IOException } @Override - public int available() throws IOException + public int available() + throws IOException { return 0; } @Override - public void close() throws IOException + public void close() + throws IOException { original.close(); } - private void initializeIfNeeded() throws IOException + private void initializeIfNeeded() + throws IOException { if (current >= start) { return; @@ -144,7 +152,8 @@ private void initializeIfNeeded() throws IOException } } - private int prefetch() throws IOException + private int prefetch() + throws IOException { int c = original.read(); if (c >= 0) { From 2742c93bd20e49492aade23fdbaca16da34cbbc5 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Mon, 18 Jan 2016 23:33:51 +0900 Subject: [PATCH 19/23] Migrate v0.8.1 --- .gitignore | 5 +- build.gradle | 44 +++++---- config/checkstyle/checkstyle.xml | 3 +- config/checkstyle/default.xml | 108 +++++++++++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- 6 files changed, 144 insertions(+), 20 deletions(-) create mode 100644 config/checkstyle/default.xml diff --git a/.gitignore b/.gitignore index eab43f9..4bbfc99 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /classpath/ build/ .idea +/.settings/ +/.metadata/ +.classpath +.project *.iml .ruby-version - diff --git a/build.gradle b/build.gradle index c3985ed..34924a1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ plugins { id "com.jfrog.bintray" version "1.1" id "com.github.jruby-gradle.base" version "0.1.5" - id "com.github.kt3k.coveralls" version "2.4.0" + id "java" id "checkstyle" + id "com.github.kt3k.coveralls" version "2.4.0" id "jacoco" - id "java" } import com.github.jrubygradle.JRubyExec repositories { @@ -21,14 +21,21 @@ sourceCompatibility = 1.7 targetCompatibility = 1.7 dependencies { - compile "org.embulk:embulk-core:0.7.+" - provided "org.embulk:embulk-core:0.7.+" + compile "org.embulk:embulk-core:0.8.+" + provided "org.embulk:embulk-core:0.8.+" // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION" - compile 'org.apache.hadoop:hadoop-client:2.6.0' + compile 'org.apache.hadoop:hadoop-client:2.6.+' testCompile "junit:junit:4.+" - testCompile "org.embulk:embulk-core:0.7.+:tests" - testCompile "org.embulk:embulk-standards:0.7.+" + testCompile "org.embulk:embulk-core:0.8.+:tests" + testCompile "org.embulk:embulk-standards:0.8.+" +} + +task classpath(type: Copy, dependsOn: ["jar"]) { + doFirst { file("classpath").deleteDir() } + from (configurations.runtime - configurations.provided + files(jar.archivePath)) + into "classpath" } +clean { delete "classpath" } jacocoTestReport { reports { @@ -36,17 +43,22 @@ jacocoTestReport { html.enabled = true } } - checkstyle { - toolVersion = "6.7" + configFile = file("${project.rootDir}/config/checkstyle/checkstyle.xml") + toolVersion = '6.14.1' } - -task classpath(type: Copy, dependsOn: ["jar"]) { - doFirst { file("classpath").deleteDir() } - from (configurations.runtime - configurations.provided + files(jar.archivePath)) - into "classpath" +checkstyleMain { + configFile = file("${project.rootDir}/config/checkstyle/default.xml") + ignoreFailures = true +} +checkstyleTest { + configFile = file("${project.rootDir}/config/checkstyle/default.xml") + ignoreFailures = true +} +task checkstyle(type: Checkstyle) { + classpath = sourceSets.main.output + sourceSets.test.output + source = sourceSets.main.allJava + sourceSets.test.allJava } -clean { delete "classpath" } task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) { jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "build" @@ -72,7 +84,7 @@ task gemspec { Gem::Specification.new do |spec| spec.name = "${project.name}" spec.version = "${project.version}" - spec.authors = ["takahiro.nakayama"] + spec.authors = ["Civitaspo"] spec.summary = %[Hdfs file input plugin for Embulk] spec.description = %[Reads files stored on Hdfs.] spec.email = ["civitaspo@gmail.com"] diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 7d77578..24515d7 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -3,6 +3,7 @@ "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> + @@ -124,4 +125,4 @@ STAR, STAR_ASSIGN, TYPE_EXTENSION_AND"/> - \ No newline at end of file + diff --git a/config/checkstyle/default.xml b/config/checkstyle/default.xml new file mode 100644 index 0000000..d6ba96f --- /dev/null +++ b/config/checkstyle/default.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c97a8bdb9088d370da7e88784a7a093b971aa23a..13372aef5e24af05341d49695ee84e5f9b594659 100644 GIT binary patch delta 25652 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 I`TN)Z1GY; Date: Mon, 18 Jan 2016 23:57:24 +0900 Subject: [PATCH 20/23] Pass checkstyle --- .../java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 5 ++--- src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java | 4 +++- .../embulk/input/hdfs/HdfsPartialFileInputStream.java | 2 +- .../org/embulk/input/hdfs/TestHdfsFileInputPlugin.java | 9 +-------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index b99771e..d7719f0 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -248,11 +248,11 @@ private static FileSystem getFs(final PluginTask task) return FileSystem.get(configuration); } - private String strftime(final String raw, final int rewind_seconds) + private String strftime(final String raw, final int rewindSeconds) { ScriptingContainer jruby = new ScriptingContainer(); Object resolved = jruby.runScriptlet( - String.format("(Time.now - %s).strftime('%s')", String.valueOf(rewind_seconds), raw)); + String.format("(Time.now - %s).strftime('%s')", String.valueOf(rewindSeconds), raw)); return resolved.toString(); } @@ -300,7 +300,6 @@ private List lsr(final FileSystem fs, FileStatus status) private List allocateHdfsFilesToTasks(final PluginTask task, final FileSystem fs, final List fileList) throws IOException { - List pathList = Lists.transform(fileList, new Function() { @Nullable diff --git a/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java b/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java index 31ade6e..4dee38a 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsPartialFile.java @@ -18,7 +18,9 @@ public HdfsPartialFile(String path, long start, long end) } // see: http://stackoverflow.com/questions/7625783/jsonmappingexception-no-suitable-constructor-found-for-type-simple-type-class - public HdfsPartialFile() { } + public HdfsPartialFile() + { + } public String getPath() { diff --git a/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java b/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java index 85127e5..4a8e3d1 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsPartialFileInputStream.java @@ -161,4 +161,4 @@ private int prefetch() } return c; } -} \ No newline at end of file +} diff --git a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java index 6126f0d..9a609b0 100644 --- a/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java +++ b/src/test/java/org/embulk/input/hdfs/TestHdfsFileInputPlugin.java @@ -1,8 +1,6 @@ package org.embulk.input.hdfs; - import com.google.common.base.Function; -import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -26,23 +24,18 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; import org.slf4j.Logger; import javax.annotation.Nullable; + import java.io.File; -import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assume.assumeNotNull; public class TestHdfsFileInputPlugin { - @Rule public EmbulkTestRuntime runtime = new EmbulkTestRuntime(); From fb613256685bd30fb6dc19b65f33c023466c0f63 Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Tue, 2 Feb 2016 11:16:29 +0900 Subject: [PATCH 21/23] Singleton Fs --- .../embulk/input/hdfs/HdfsFileInputPlugin.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index d7719f0..0fcfc79 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -41,6 +41,7 @@ public class HdfsFileInputPlugin implements FileInputPlugin { private static final Logger logger = Exec.getLogger(HdfsFileInputPlugin.class); + private static FileSystem fs; public interface PluginTask extends Task @@ -224,6 +225,18 @@ private static HdfsPartialFileInputStream openInputStream(PluginTask task, HdfsP } private static FileSystem getFs(final PluginTask task) + throws IOException + { + if (fs == null) { + setFs(task); + return fs; + } + else { + return fs; + } + } + + private static FileSystem setFs(final PluginTask task) throws IOException { Configuration configuration = new Configuration(); @@ -238,9 +251,7 @@ private static FileSystem getFs(final PluginTask task) } // For debug - Iterator> entryIterator = configuration.iterator(); - while (entryIterator.hasNext()) { - Map.Entry entry = entryIterator.next(); + for (Map.Entry entry : configuration) { logger.trace("{}: {}", entry.getKey(), entry.getValue()); } logger.debug("Resource Files: {}", configuration); From e9db52f90b15b179d42e16fcabb94157b79893fe Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Mon, 8 Feb 2016 14:54:30 +0900 Subject: [PATCH 22/23] Optimize imports --- src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index 0fcfc79..fdd2c60 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -33,7 +33,6 @@ import java.io.InputStream; import java.io.SequenceInputStream; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; From f14c622c2cf99f551501536ede3fa2ecdab96c0d Mon Sep 17 00:00:00 2001 From: Civitaspo Date: Mon, 8 Feb 2016 14:55:57 +0900 Subject: [PATCH 23/23] Reuse fs --- src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java index fdd2c60..ae06614 100644 --- a/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java +++ b/src/main/java/org/embulk/input/hdfs/HdfsFileInputPlugin.java @@ -255,7 +255,8 @@ private static FileSystem setFs(final PluginTask task) } logger.debug("Resource Files: {}", configuration); - return FileSystem.get(configuration); + fs = FileSystem.get(configuration); + return fs; } private String strftime(final String raw, final int rewindSeconds)