embulk-output-ftp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +56 -2
- data/build.gradle +3 -1
- data/classpath/embulk-output-ftp-0.1.1.jar +0 -0
- data/config/checkstyle/checkstyle.xml +3 -1
- data/config/checkstyle/default.xml +2 -3
- data/src/main/java/org/embulk/output/ftp/FtpFileOutputPlugin.java +17 -12
- data/src/main/java/org/embulk/output/ftp/SSLPlugins.java +2 -2
- data/src/test/java/org/embulk/output/ftp/TestFtpFileOutputPlugin.java +370 -0
- data/src/test/resources/sample_01.csv +6 -0
- data/src/test/resources/sample_02.csv +6 -0
- metadata +4 -4
- data/classpath/embulk-output-ftp-0.1.0.jar +0 -0
- data/src/main/java/org/embulk/output/ftp/BlockingTransfer.java +0 -277
- data/src/test/java/org/embulk/output/ftp/TestBlockingTransfer.java +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db87448f5b289f68ebb6c9d03b36f295a0139ac0
|
4
|
+
data.tar.gz: ab5abab07a3bec49eed5ec362c8fdec549564d88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7281539145640359c6c084c031b7a212efd3044755e57ea5b823f5a4b80635facf0c0f300e59fdf1ced7db7425104a0fdd0f737de7274674a0fba93987b4d8b5
|
7
|
+
data.tar.gz: a2d49e39e67f7c5c680e62651db9ccf6e845bc42e317d81a834d52e9c83a3630003b10667f304fc4351e637f74bb9304b985510d688d5b39db86d837b89eb4de
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -37,7 +37,7 @@ out:
|
|
37
37
|
path_prefix: /ftp/file/path/prefix
|
38
38
|
ext: csv
|
39
39
|
formatter:
|
40
|
-
type: csv
|
40
|
+
type: csv.gz
|
41
41
|
header_line: false
|
42
42
|
encoders:
|
43
43
|
- {type: gzip}
|
@@ -87,7 +87,7 @@ out:
|
|
87
87
|
-----END CERTIFICATE-----
|
88
88
|
|
89
89
|
path_prefix: /ftp/file/path/prefix
|
90
|
-
|
90
|
+
ext: csv
|
91
91
|
```
|
92
92
|
|
93
93
|
|
@@ -96,3 +96,57 @@ out:
|
|
96
96
|
```
|
97
97
|
$ ./gradlew gem # -t to watch change of files and rebuild continuously
|
98
98
|
```
|
99
|
+
|
100
|
+
## Test
|
101
|
+
|
102
|
+
```
|
103
|
+
$ ./gradlew test # -t to watch change of files and rebuild continuously
|
104
|
+
```
|
105
|
+
|
106
|
+
To run unit tests, we need to configure the following environment variables.
|
107
|
+
|
108
|
+
When environment variables are not set, skip some test cases.
|
109
|
+
|
110
|
+
```
|
111
|
+
FTP_TEST_HOST
|
112
|
+
FTP_TEST_USER
|
113
|
+
FTP_TEST_PASSWORD
|
114
|
+
FTP_TEST_SSL_TRUSTED_CA_CERT_FILE
|
115
|
+
FTP_TEST_SSL_TRUSTED_CA_CERT_DATA
|
116
|
+
```
|
117
|
+
|
118
|
+
If you're using Mac OS X El Capitan and GUI Applications(IDE), like as follows.
|
119
|
+
```xml
|
120
|
+
$ vi ~/Library/LaunchAgents/environment.plist
|
121
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
122
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
123
|
+
<plist version="1.0">
|
124
|
+
<dict>
|
125
|
+
<key>Label</key>
|
126
|
+
<string>my.startup</string>
|
127
|
+
<key>ProgramArguments</key>
|
128
|
+
<array>
|
129
|
+
<string>sh</string>
|
130
|
+
<string>-c</string>
|
131
|
+
<string>
|
132
|
+
launchctl setenv FTP_TEST_HOST ftp.example.com
|
133
|
+
launchctl setenv FTP_TEST_USER username
|
134
|
+
launchctl setenv FTP_TEST_PASSWORD password
|
135
|
+
launchctl setenv FTP_TEST_SSL_TRUSTED_CA_CERT_FILE /path/to/cert.pem
|
136
|
+
launchctl setenv FTP_TEST_SSL_TRUSTED_CA_CERT_DATA /path/to/cert.pem
|
137
|
+
</string>
|
138
|
+
</array>
|
139
|
+
<key>RunAtLoad</key>
|
140
|
+
<true/>
|
141
|
+
</dict>
|
142
|
+
</plist>
|
143
|
+
|
144
|
+
$ launchctl load ~/Library/LaunchAgents/environment.plist
|
145
|
+
$ launchctl getenv FTP_TEST_HOST //try to get value.
|
146
|
+
|
147
|
+
Then start your applications.
|
148
|
+
```
|
149
|
+
|
150
|
+
## Acknowledgement
|
151
|
+
|
152
|
+
This program is forked from [embulk-input-ftp](https://github.com/embulk/embulk-input-ftp) and originally written by @frsyuki, modified by @sakama.
|
data/build.gradle
CHANGED
@@ -14,7 +14,7 @@ configurations {
|
|
14
14
|
provided
|
15
15
|
}
|
16
16
|
|
17
|
-
version = "0.1.
|
17
|
+
version = "0.1.1"
|
18
18
|
|
19
19
|
sourceCompatibility = 1.7
|
20
20
|
targetCompatibility = 1.7
|
@@ -25,6 +25,8 @@ dependencies {
|
|
25
25
|
compile files("libs/ftp4j-1.7.2.jar")
|
26
26
|
compile "org.bouncycastle:bcpkix-jdk15on:1.52"
|
27
27
|
testCompile "junit:junit:4.+"
|
28
|
+
testCompile "org.embulk:embulk-core:0.8.9:tests"
|
29
|
+
testCompile "org.embulk:embulk-standards:0.8.9"
|
28
30
|
}
|
29
31
|
|
30
32
|
task classpath(type: Copy, dependsOn: ["jar"]) {
|
Binary file
|
@@ -92,7 +92,9 @@
|
|
92
92
|
<module name="TypeName"/>
|
93
93
|
<module name="PackageName"/>
|
94
94
|
<module name="ParameterName"/>
|
95
|
-
<module name="StaticVariableName"
|
95
|
+
<module name="StaticVariableName">
|
96
|
+
<property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
|
97
|
+
</module>
|
96
98
|
<module name="ClassTypeParameterName">
|
97
99
|
<property name="format" value="^[A-Z][0-9]?$"/>
|
98
100
|
</module>
|
@@ -82,9 +82,8 @@
|
|
82
82
|
<module name="TypeName"/>
|
83
83
|
<module name="PackageName"/>
|
84
84
|
<module name="ParameterName"/>
|
85
|
-
<module name="StaticVariableName"
|
86
|
-
|
87
|
-
<property name="format" value="^[A-Z][0-9]?$"/>
|
85
|
+
<module name="StaticVariableName">
|
86
|
+
<property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
|
88
87
|
</module>
|
89
88
|
<module name="MethodTypeParameterName">
|
90
89
|
<property name="format" value="^[A-Z][0-9]?$"/>
|
@@ -103,7 +103,7 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
103
103
|
client = newFTPClient(log, task);
|
104
104
|
}
|
105
105
|
catch (Exception ex) {
|
106
|
-
throw new ConfigException(ex);
|
106
|
+
throw new ConfigException("Faild to connect to FTP server", ex);
|
107
107
|
}
|
108
108
|
finally {
|
109
109
|
disconnectClient(client);
|
@@ -168,8 +168,8 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
168
168
|
suffix = "." + suffix;
|
169
169
|
}
|
170
170
|
filePath = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + suffix;
|
171
|
-
file =
|
172
|
-
log.info("Writing local file {}", file.getAbsolutePath());
|
171
|
+
file = Exec.getTempFileSpace().createTempFile(filePath, ".tmp");
|
172
|
+
log.info("Writing local temporary file \"{}\"", file.getAbsolutePath());
|
173
173
|
output = new BufferedOutputStream(new FileOutputStream(file));
|
174
174
|
}
|
175
175
|
catch (IOException ex) {
|
@@ -225,13 +225,14 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
225
225
|
public Void call() throws FTPIllegalReplyException, FTPException, FTPDataTransferException,
|
226
226
|
FTPAbortedException, IOException, RetryGiveupException
|
227
227
|
{
|
228
|
-
|
229
|
-
|
230
|
-
|
228
|
+
client.upload(filePath,
|
229
|
+
new BufferedInputStream(new FileInputStream(file)), 0L, 0L,
|
230
|
+
new LoggingTransferListener(file.getAbsolutePath(), filePath, log, TRANSFER_NOTICE_BYTES)
|
231
|
+
);
|
231
232
|
if (!file.delete()) {
|
232
233
|
throw new ConfigException("Couldn't delete local file " + file.getAbsolutePath());
|
233
234
|
}
|
234
|
-
log.info("
|
235
|
+
log.info("Deleted local temporary file \"{}\"", file.getAbsolutePath());
|
235
236
|
return null;
|
236
237
|
}
|
237
238
|
|
@@ -396,7 +397,7 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
396
397
|
|
397
398
|
public void received(String statement)
|
398
399
|
{
|
399
|
-
log.
|
400
|
+
log.debug("< " + statement);
|
400
401
|
}
|
401
402
|
|
402
403
|
public void sent(String statement)
|
@@ -405,20 +406,24 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
405
406
|
// don't show password
|
406
407
|
return;
|
407
408
|
}
|
408
|
-
log.
|
409
|
+
log.debug("> {}", statement);
|
409
410
|
}
|
410
411
|
}
|
411
412
|
|
412
413
|
private static class LoggingTransferListener implements FTPDataTransferListener
|
413
414
|
{
|
415
|
+
private final String localPath;
|
416
|
+
private final String remotePath;
|
414
417
|
private final Logger log;
|
415
418
|
private final long transferNoticeBytes;
|
416
419
|
|
417
420
|
private long totalTransfer;
|
418
421
|
private long nextTransferNotice;
|
419
422
|
|
420
|
-
public LoggingTransferListener(Logger log, long transferNoticeBytes)
|
423
|
+
public LoggingTransferListener(String localPath, String remotePath, Logger log, long transferNoticeBytes)
|
421
424
|
{
|
425
|
+
this.localPath = localPath;
|
426
|
+
this.remotePath = remotePath;
|
422
427
|
this.log = log;
|
423
428
|
this.transferNoticeBytes = transferNoticeBytes;
|
424
429
|
this.nextTransferNotice = transferNoticeBytes;
|
@@ -426,7 +431,7 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
426
431
|
|
427
432
|
public void started()
|
428
433
|
{
|
429
|
-
log.info("Transfer started");
|
434
|
+
log.info("Transfer started. local path:\"{}\" remote path:\"{}\"", localPath, remotePath);
|
430
435
|
}
|
431
436
|
|
432
437
|
public void transferred(int length)
|
@@ -440,7 +445,7 @@ public class FtpFileOutputPlugin implements FileOutputPlugin
|
|
440
445
|
|
441
446
|
public void completed()
|
442
447
|
{
|
443
|
-
log.info("Transfer completed {} bytes", totalTransfer);
|
448
|
+
log.info("Transfer completed. remote path:\"{}\", size:{} bytes", remotePath, totalTransfer);
|
444
449
|
}
|
445
450
|
|
446
451
|
public void aborted()
|
@@ -63,7 +63,7 @@ public class SSLPlugins
|
|
63
63
|
|
64
64
|
public static class SSLPluginConfig
|
65
65
|
{
|
66
|
-
static SSLPluginConfig
|
66
|
+
static SSLPluginConfig NO_VERIFY = new SSLPluginConfig(VerifyMode.NO_VERIFY, false, ImmutableList.<byte[]>of());
|
67
67
|
|
68
68
|
private final VerifyMode verifyMode;
|
69
69
|
private final boolean verifyHostname;
|
@@ -176,7 +176,7 @@ public class SSLPlugins
|
|
176
176
|
}
|
177
177
|
}
|
178
178
|
else {
|
179
|
-
return SSLPluginConfig.
|
179
|
+
return SSLPluginConfig.NO_VERIFY;
|
180
180
|
}
|
181
181
|
}
|
182
182
|
|
@@ -1,5 +1,375 @@
|
|
1
1
|
package org.embulk.output.ftp;
|
2
2
|
|
3
|
+
import com.google.common.collect.ImmutableList;
|
4
|
+
import com.google.common.collect.ImmutableMap;
|
5
|
+
import com.google.common.collect.Lists;
|
6
|
+
import com.google.common.io.Resources;
|
7
|
+
import it.sauronsoftware.ftp4j.FTPClient;
|
8
|
+
import org.embulk.EmbulkTestRuntime;
|
9
|
+
import org.embulk.config.ConfigDiff;
|
10
|
+
import org.embulk.config.ConfigException;
|
11
|
+
import org.embulk.config.ConfigSource;
|
12
|
+
import org.embulk.config.TaskReport;
|
13
|
+
import org.embulk.config.TaskSource;
|
14
|
+
import org.embulk.output.ftp.FtpFileOutputPlugin.PluginTask;
|
15
|
+
import org.embulk.spi.Buffer;
|
16
|
+
import org.embulk.spi.Exec;
|
17
|
+
import org.embulk.spi.FileOutputPlugin;
|
18
|
+
import org.embulk.spi.FileOutputRunner;
|
19
|
+
import org.embulk.spi.OutputPlugin;
|
20
|
+
import org.embulk.spi.Schema;
|
21
|
+
import org.embulk.spi.TransactionalFileOutput;
|
22
|
+
import org.embulk.standards.CsvParserPlugin;
|
23
|
+
|
24
|
+
import org.junit.Before;
|
25
|
+
import org.junit.BeforeClass;
|
26
|
+
import org.junit.Rule;
|
27
|
+
import org.junit.Test;
|
28
|
+
import org.slf4j.Logger;
|
29
|
+
|
30
|
+
import static org.junit.Assert.assertEquals;
|
31
|
+
import static org.junit.Assume.assumeNotNull;
|
32
|
+
|
33
|
+
import java.io.BufferedInputStream;
|
34
|
+
import java.io.BufferedReader;
|
35
|
+
import java.io.ByteArrayOutputStream;
|
36
|
+
import java.io.File;
|
37
|
+
import java.io.FileInputStream;
|
38
|
+
import java.io.IOException;
|
39
|
+
import java.io.InputStream;
|
40
|
+
import java.io.InputStreamReader;
|
41
|
+
import java.lang.reflect.Method;
|
42
|
+
import java.security.GeneralSecurityException;
|
43
|
+
import java.util.Arrays;
|
44
|
+
import java.util.List;
|
45
|
+
|
3
46
|
public class TestFtpFileOutputPlugin
|
4
47
|
{
|
48
|
+
private static String FTP_TEST_HOST;
|
49
|
+
private static String FTP_TEST_USER;
|
50
|
+
private static String FTP_TEST_PASSWORD;
|
51
|
+
private static String FTP_TEST_SSL_TRUSTED_CA_CERT_FILE;
|
52
|
+
private static String FTP_TEST_DIRECTORY;
|
53
|
+
private static String FTP_TEST_PATH_PREFIX;
|
54
|
+
private static String LOCAL_PATH_PREFIX;
|
55
|
+
private FileOutputRunner runner;
|
56
|
+
|
57
|
+
/*
|
58
|
+
* This test case requires environment variables
|
59
|
+
* FTP_TEST_HOST
|
60
|
+
* FTP_TEST_USER
|
61
|
+
* FTP_TEST_PASSWORD
|
62
|
+
* FTP_TEST_SSL_TRUSTED_CA_CERT_FILE
|
63
|
+
*/
|
64
|
+
@BeforeClass
|
65
|
+
public static void initializeConstant()
|
66
|
+
{
|
67
|
+
FTP_TEST_HOST = System.getenv("FTP_TEST_HOST");
|
68
|
+
FTP_TEST_USER = System.getenv("FTP_TEST_USER");
|
69
|
+
FTP_TEST_PASSWORD = System.getenv("FTP_TEST_PASSWORD");
|
70
|
+
FTP_TEST_SSL_TRUSTED_CA_CERT_FILE = System.getenv("FTP_TEST_SSL_TRUSTED_CA_CERT_FILE");
|
71
|
+
// skip test cases, if environment variables are not set.
|
72
|
+
assumeNotNull(FTP_TEST_HOST, FTP_TEST_USER, FTP_TEST_PASSWORD, FTP_TEST_SSL_TRUSTED_CA_CERT_FILE);
|
73
|
+
|
74
|
+
FTP_TEST_DIRECTORY = System.getenv("FTP_TEST_DIRECTORY") != null ? getDirectory(System.getenv("FTP_TEST_DIRECTORY")) : getDirectory("/unittest/");
|
75
|
+
FTP_TEST_PATH_PREFIX = FTP_TEST_DIRECTORY + "sample_";
|
76
|
+
LOCAL_PATH_PREFIX = Resources.getResource("sample_01.csv").getPath();
|
77
|
+
}
|
78
|
+
|
79
|
+
@Rule
|
80
|
+
public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
|
81
|
+
private FtpFileOutputPlugin plugin;
|
82
|
+
|
83
|
+
@Before
|
84
|
+
public void createResources() throws GeneralSecurityException, NoSuchMethodException, IOException
|
85
|
+
{
|
86
|
+
plugin = new FtpFileOutputPlugin();
|
87
|
+
runner = new FileOutputRunner(runtime.getInstance(FtpFileOutputPlugin.class));
|
88
|
+
}
|
89
|
+
|
90
|
+
@Test
|
91
|
+
public void checkDefaultValues()
|
92
|
+
{
|
93
|
+
ConfigSource config = Exec.newConfigSource()
|
94
|
+
.set("in", inputConfig())
|
95
|
+
.set("parser", parserConfig(schemaConfig()))
|
96
|
+
.set("type", "ftp")
|
97
|
+
.set("host", FTP_TEST_HOST)
|
98
|
+
.set("user", FTP_TEST_USER)
|
99
|
+
.set("password", FTP_TEST_PASSWORD)
|
100
|
+
.set("path_prefix", "my-prefix")
|
101
|
+
.set("file_ext", ".csv")
|
102
|
+
.set("formatter", formatterConfig());
|
103
|
+
|
104
|
+
PluginTask task = config.loadConfig(PluginTask.class);
|
105
|
+
|
106
|
+
assertEquals(FTP_TEST_HOST, task.getHost());
|
107
|
+
assertEquals(FTP_TEST_USER, task.getUser().get());
|
108
|
+
assertEquals(FTP_TEST_PASSWORD, task.getPassword().get());
|
109
|
+
assertEquals(10, task.getMaxConnectionRetry());
|
110
|
+
}
|
111
|
+
|
112
|
+
@Test
|
113
|
+
public void testTransaction()
|
114
|
+
{
|
115
|
+
ConfigSource config = Exec.newConfigSource()
|
116
|
+
.set("in", inputConfig())
|
117
|
+
.set("parser", parserConfig(schemaConfig()))
|
118
|
+
.set("type", "ftp")
|
119
|
+
.set("host", FTP_TEST_HOST)
|
120
|
+
.set("user", FTP_TEST_USER)
|
121
|
+
.set("password", FTP_TEST_PASSWORD)
|
122
|
+
.set("path_prefix", "my-prefix")
|
123
|
+
.set("file_ext", ".csv")
|
124
|
+
.set("formatter", formatterConfig());
|
125
|
+
|
126
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
127
|
+
|
128
|
+
runner.transaction(config, schema, 0, new Control());
|
129
|
+
}
|
130
|
+
|
131
|
+
@Test(expected = ConfigException.class)
|
132
|
+
public void testTransactionWithInvalidHost()
|
133
|
+
{
|
134
|
+
ConfigSource config = Exec.newConfigSource()
|
135
|
+
.set("in", inputConfig())
|
136
|
+
.set("parser", parserConfig(schemaConfig()))
|
137
|
+
.set("type", "ftp")
|
138
|
+
.set("host", "non-exists.example.com")
|
139
|
+
.set("user", FTP_TEST_USER)
|
140
|
+
.set("password", FTP_TEST_PASSWORD)
|
141
|
+
.set("path_prefix", "my-prefix")
|
142
|
+
.set("file_ext", ".csv")
|
143
|
+
.set("formatter", formatterConfig());
|
144
|
+
|
145
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
146
|
+
|
147
|
+
runner.transaction(config, schema, 0, new Control());
|
148
|
+
}
|
149
|
+
|
150
|
+
// @Test
|
151
|
+
// public void testTransactionWithSsl()
|
152
|
+
// {
|
153
|
+
// ConfigSource config = Exec.newConfigSource()
|
154
|
+
// .set("in", inputConfig())
|
155
|
+
// .set("parser", parserConfig(schemaConfig()))
|
156
|
+
// .set("type", "ftp")
|
157
|
+
// .set("host", FTP_TEST_HOST)
|
158
|
+
// .set("port", 990)
|
159
|
+
// .set("user", FTP_TEST_USER)
|
160
|
+
// .set("password", FTP_TEST_PASSWORD)
|
161
|
+
// .set("ssl", true)
|
162
|
+
// .set("ssl_verify", false)
|
163
|
+
// .set("ssl_verify_hostname", false)
|
164
|
+
// .set("ssl_trusted_ca_cert_file", FTP_TEST_SSL_TRUSTED_CA_CERT_FILE)
|
165
|
+
// .set("path_prefix", "my-prefix")
|
166
|
+
// .set("file_ext", ".csv")
|
167
|
+
// .set("formatter", formatterConfig());
|
168
|
+
//
|
169
|
+
// Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
170
|
+
//
|
171
|
+
// runner.transaction(config, schema, 0, new Control());
|
172
|
+
// }
|
173
|
+
|
174
|
+
@Test
|
175
|
+
public void testResume()
|
176
|
+
{
|
177
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
178
|
+
ConfigDiff configDiff = plugin.resume(task.dump(), 0, new FileOutputPlugin.Control()
|
179
|
+
{
|
180
|
+
@Override
|
181
|
+
public List<TaskReport> run(TaskSource taskSource)
|
182
|
+
{
|
183
|
+
return Lists.newArrayList(Exec.newTaskReport());
|
184
|
+
}
|
185
|
+
});
|
186
|
+
//assertEquals("in/aa/a", configDiff.get(String.class, "last_path"));
|
187
|
+
}
|
188
|
+
|
189
|
+
@Test
|
190
|
+
public void testCleanup()
|
191
|
+
{
|
192
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
193
|
+
plugin.cleanup(task.dump(), 0, Lists.<TaskReport>newArrayList()); // no errors happens
|
194
|
+
}
|
195
|
+
|
196
|
+
@Test
|
197
|
+
public void testFtpFileOutputByOpen() throws Exception
|
198
|
+
{
|
199
|
+
ConfigSource configSource = config();
|
200
|
+
PluginTask task = configSource.loadConfig(PluginTask.class);
|
201
|
+
task.setSSLConfig(SSLPlugins.configure(task));
|
202
|
+
Schema schema = configSource.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
203
|
+
runner.transaction(configSource, schema, 0, new Control());
|
204
|
+
|
205
|
+
TransactionalFileOutput output = plugin.open(task.dump(), 0);
|
206
|
+
|
207
|
+
output.nextFile();
|
208
|
+
|
209
|
+
FileInputStream is = new FileInputStream(LOCAL_PATH_PREFIX);
|
210
|
+
byte[] bytes = convertInputStreamToByte(is);
|
211
|
+
Buffer buffer = Buffer.wrap(bytes);
|
212
|
+
output.add(buffer);
|
213
|
+
|
214
|
+
output.finish();
|
215
|
+
output.commit();
|
216
|
+
|
217
|
+
String remotePath = FTP_TEST_PATH_PREFIX + String.format(task.getSequenceFormat(), 0, 0) + task.getFileNameExtension();
|
218
|
+
assertRecords(remotePath, task);
|
219
|
+
}
|
220
|
+
|
221
|
+
public ConfigSource config()
|
222
|
+
{
|
223
|
+
return Exec.newConfigSource()
|
224
|
+
.set("in", inputConfig())
|
225
|
+
.set("parser", parserConfig(schemaConfig()))
|
226
|
+
.set("type", "ftp")
|
227
|
+
.set("host", FTP_TEST_HOST)
|
228
|
+
.set("user", FTP_TEST_USER)
|
229
|
+
.set("password", FTP_TEST_PASSWORD)
|
230
|
+
.set("path_prefix", FTP_TEST_PATH_PREFIX)
|
231
|
+
.set("last_path", "")
|
232
|
+
.set("file_ext", ".csv")
|
233
|
+
.set("formatter", formatterConfig());
|
234
|
+
}
|
235
|
+
|
236
|
+
private class Control implements OutputPlugin.Control
|
237
|
+
{
|
238
|
+
@Override
|
239
|
+
public List<TaskReport> run(TaskSource taskSource)
|
240
|
+
{
|
241
|
+
return Lists.newArrayList(Exec.newTaskReport());
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
private ImmutableMap<String, Object> inputConfig()
|
246
|
+
{
|
247
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
248
|
+
builder.put("type", "file");
|
249
|
+
builder.put("path_prefix", LOCAL_PATH_PREFIX);
|
250
|
+
builder.put("last_path", "");
|
251
|
+
return builder.build();
|
252
|
+
}
|
253
|
+
|
254
|
+
private ImmutableMap<String, Object> parserConfig(ImmutableList<Object> schemaConfig)
|
255
|
+
{
|
256
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
257
|
+
builder.put("type", "csv");
|
258
|
+
builder.put("newline", "CRLF");
|
259
|
+
builder.put("delimiter", ",");
|
260
|
+
builder.put("quote", "\"");
|
261
|
+
builder.put("escape", "\"");
|
262
|
+
builder.put("trim_if_not_quoted", false);
|
263
|
+
builder.put("skip_header_lines", 1);
|
264
|
+
builder.put("allow_extra_columns", false);
|
265
|
+
builder.put("allow_optional_columns", false);
|
266
|
+
builder.put("columns", schemaConfig);
|
267
|
+
return builder.build();
|
268
|
+
}
|
269
|
+
|
270
|
+
private ImmutableList<Object> schemaConfig()
|
271
|
+
{
|
272
|
+
ImmutableList.Builder<Object> builder = new ImmutableList.Builder<>();
|
273
|
+
builder.add(ImmutableMap.of("name", "id", "type", "long"));
|
274
|
+
builder.add(ImmutableMap.of("name", "account", "type", "long"));
|
275
|
+
builder.add(ImmutableMap.of("name", "time", "type", "timestamp", "format", "%Y-%m-%d %H:%M:%S"));
|
276
|
+
builder.add(ImmutableMap.of("name", "purchase", "type", "timestamp", "format", "%Y%m%d"));
|
277
|
+
builder.add(ImmutableMap.of("name", "comment", "type", "string"));
|
278
|
+
builder.add(ImmutableMap.of("name", "json_column", "type", "json"));
|
279
|
+
return builder.build();
|
280
|
+
}
|
281
|
+
|
282
|
+
private ImmutableMap<String, Object> formatterConfig()
|
283
|
+
{
|
284
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
285
|
+
builder.put("type", "csv");
|
286
|
+
builder.put("header_line", "false");
|
287
|
+
builder.put("timezone", "Asia/Tokyo");
|
288
|
+
return builder.build();
|
289
|
+
}
|
290
|
+
|
291
|
+
private void assertRecords(String remotePath, PluginTask task) throws Exception
|
292
|
+
{
|
293
|
+
ImmutableList<List<String>> records = getFileContentsFromFtp(remotePath, task);
|
294
|
+
assertEquals(6, records.size());
|
295
|
+
{
|
296
|
+
List<String> record = records.get(1);
|
297
|
+
assertEquals("1", record.get(0));
|
298
|
+
assertEquals("32864", record.get(1));
|
299
|
+
assertEquals("2015-01-27 19:23:49", record.get(2));
|
300
|
+
assertEquals("20150127", record.get(3));
|
301
|
+
assertEquals("embulk", record.get(4));
|
302
|
+
assertEquals("{\"k\":true}", record.get(5));
|
303
|
+
}
|
304
|
+
|
305
|
+
{
|
306
|
+
List<String> record = records.get(2);
|
307
|
+
assertEquals("2", record.get(0));
|
308
|
+
assertEquals("14824", record.get(1));
|
309
|
+
assertEquals("2015-01-27 19:01:23", record.get(2));
|
310
|
+
assertEquals("20150127", record.get(3));
|
311
|
+
assertEquals("embulk jruby", record.get(4));
|
312
|
+
assertEquals("{\"k\":1}", record.get(5));
|
313
|
+
}
|
314
|
+
|
315
|
+
{
|
316
|
+
List<String> record = records.get(3);
|
317
|
+
assertEquals("{\"k\":1.23}", record.get(5));
|
318
|
+
}
|
319
|
+
|
320
|
+
{
|
321
|
+
List<String> record = records.get(4);
|
322
|
+
assertEquals("{\"k\":\"v\"}", record.get(5));
|
323
|
+
}
|
324
|
+
|
325
|
+
{
|
326
|
+
List<String> record = records.get(5);
|
327
|
+
assertEquals("{\"k\":\"2015-02-03 08:13:45\"}", record.get(5));
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
331
|
+
private ImmutableList<List<String>> getFileContentsFromFtp(String path, PluginTask task) throws Exception
|
332
|
+
{
|
333
|
+
Method method = FtpFileOutputPlugin.class.getDeclaredMethod("newFTPClient", Logger.class, PluginTask.class);
|
334
|
+
method.setAccessible(true);
|
335
|
+
FTPClient client = (FTPClient) method.invoke(plugin, Exec.getLogger(FtpFileOutputPlugin.class), task);
|
336
|
+
|
337
|
+
ImmutableList.Builder<List<String>> builder = new ImmutableList.Builder<>();
|
338
|
+
File localFile = Exec.getTempFileSpace().createTempFile();
|
339
|
+
client.download(path, localFile);
|
340
|
+
InputStream is = new BufferedInputStream(new FileInputStream(localFile));
|
341
|
+
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
342
|
+
String line;
|
343
|
+
while ((line = reader.readLine()) != null) {
|
344
|
+
List<String> records = Arrays.asList(line.split(",", 0));
|
345
|
+
|
346
|
+
builder.add(records);
|
347
|
+
}
|
348
|
+
return builder.build();
|
349
|
+
}
|
350
|
+
|
351
|
+
private static String getDirectory(String dir)
|
352
|
+
{
|
353
|
+
if (!dir.isEmpty() && !dir.endsWith("/")) {
|
354
|
+
dir = dir + "/";
|
355
|
+
}
|
356
|
+
if (dir.startsWith("/")) {
|
357
|
+
dir = dir.replaceFirst("/", "");
|
358
|
+
}
|
359
|
+
return dir;
|
360
|
+
}
|
361
|
+
|
362
|
+
private byte[] convertInputStreamToByte(InputStream is) throws IOException
|
363
|
+
{
|
364
|
+
ByteArrayOutputStream bo = new ByteArrayOutputStream();
|
365
|
+
byte [] buffer = new byte[1024];
|
366
|
+
while (true) {
|
367
|
+
int len = is.read(buffer);
|
368
|
+
if (len < 0) {
|
369
|
+
break;
|
370
|
+
}
|
371
|
+
bo.write(buffer, 0, len);
|
372
|
+
}
|
373
|
+
return bo.toByteArray();
|
374
|
+
}
|
5
375
|
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
id,account,time,purchase,comment,json_column
|
2
|
+
1,32864,2015-01-27 19:23:49,20150127,embulk,{"k":true}
|
3
|
+
2,14824,2015-01-27 19:01:23,20150127,embulk jruby,{"k":1}
|
4
|
+
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin",{"k":1.23}
|
5
|
+
4,11270,2015-01-29 11:54:36,20150129,NULL,{"k":"v"}
|
6
|
+
5,53231,2015-01-30 13:48:12,20150130,NULL,{"k":"2015-02-03 08:13:45"}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
id,account,time,purchase,comment,json_column
|
2
|
+
1,32864,2015-01-27 19:23:49,20150127,embulk,{"k":true}
|
3
|
+
2,14824,2015-01-27 19:01:23,20150127,embulk jruby,{"k":1}
|
4
|
+
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin",{"k":1.23}
|
5
|
+
4,11270,2015-01-29 11:54:36,20150129,NULL,{"k":"v"}
|
6
|
+
5,53231,2015-01-30 13:48:12,20150130,NULL,{"k":"2015-02-03 08:13:45"}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-ftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Satoshi Akama
|
@@ -59,17 +59,17 @@ files:
|
|
59
59
|
- gradlew.bat
|
60
60
|
- lib/embulk/output/ftp.rb
|
61
61
|
- libs/ftp4j-1.7.2.jar
|
62
|
-
- src/main/java/org/embulk/output/ftp/BlockingTransfer.java
|
63
62
|
- src/main/java/org/embulk/output/ftp/FtpFileOutputPlugin.java
|
64
63
|
- src/main/java/org/embulk/output/ftp/SSLPlugins.java
|
65
64
|
- src/main/java/org/embulk/output/ftp/TrustManagers.java
|
66
|
-
- src/test/java/org/embulk/output/ftp/TestBlockingTransfer.java
|
67
65
|
- src/test/java/org/embulk/output/ftp/TestFtpFileOutputPlugin.java
|
68
66
|
- src/test/java/org/embulk/output/ftp/TestSSLPlugins.java
|
69
67
|
- src/test/java/org/embulk/output/ftp/TrustedManagers.java
|
68
|
+
- src/test/resources/sample_01.csv
|
69
|
+
- src/test/resources/sample_02.csv
|
70
70
|
- classpath/bcpkix-jdk15on-1.52.jar
|
71
71
|
- classpath/bcprov-jdk15on-1.52.jar
|
72
|
-
- classpath/embulk-output-ftp-0.1.
|
72
|
+
- classpath/embulk-output-ftp-0.1.1.jar
|
73
73
|
- classpath/ftp4j-1.7.2.jar
|
74
74
|
homepage: https://github.com/sakama/embulk-output-ftp
|
75
75
|
licenses:
|
Binary file
|
@@ -1,277 +0,0 @@
|
|
1
|
-
package org.embulk.output.ftp;
|
2
|
-
|
3
|
-
import com.google.common.base.Function;
|
4
|
-
|
5
|
-
import java.io.EOFException;
|
6
|
-
import java.io.IOException;
|
7
|
-
import java.io.InterruptedIOException;
|
8
|
-
import java.nio.ByteBuffer;
|
9
|
-
import java.nio.channels.ReadableByteChannel;
|
10
|
-
import java.nio.channels.WritableByteChannel;
|
11
|
-
import java.util.concurrent.Callable;
|
12
|
-
import java.util.concurrent.CancellationException;
|
13
|
-
import java.util.concurrent.ExecutionException;
|
14
|
-
import java.util.concurrent.ExecutorService;
|
15
|
-
import java.util.concurrent.Future;
|
16
|
-
|
17
|
-
public class BlockingTransfer
|
18
|
-
{
|
19
|
-
private final WriterChannel writerChannel;
|
20
|
-
private final ReaderChannel readerChannel;
|
21
|
-
private Future<?> transferCompletionFuture;
|
22
|
-
|
23
|
-
public static BlockingTransfer submit(ExecutorService executor,
|
24
|
-
Function<BlockingTransfer, Runnable> starterFactory)
|
25
|
-
{
|
26
|
-
BlockingTransfer transfer = new BlockingTransfer();
|
27
|
-
final Runnable starter = starterFactory.apply(transfer);
|
28
|
-
transfer.setTransferCompletionFuture(
|
29
|
-
executor.submit(new Callable<Void>() {
|
30
|
-
public Void call() throws Exception
|
31
|
-
{
|
32
|
-
starter.run();
|
33
|
-
return null;
|
34
|
-
}
|
35
|
-
})
|
36
|
-
);
|
37
|
-
return transfer;
|
38
|
-
}
|
39
|
-
|
40
|
-
private BlockingTransfer()
|
41
|
-
{
|
42
|
-
this.writerChannel = new WriterChannel();
|
43
|
-
this.readerChannel = new ReaderChannel();
|
44
|
-
}
|
45
|
-
|
46
|
-
private void setTransferCompletionFuture(Future<?> future)
|
47
|
-
{
|
48
|
-
this.transferCompletionFuture = future;
|
49
|
-
}
|
50
|
-
|
51
|
-
public ReadableByteChannel getReaderChannel()
|
52
|
-
{
|
53
|
-
return readerChannel;
|
54
|
-
}
|
55
|
-
|
56
|
-
public WritableByteChannel getWriterChannel()
|
57
|
-
{
|
58
|
-
return writerChannel;
|
59
|
-
}
|
60
|
-
|
61
|
-
public void transferFailed(Throwable exception)
|
62
|
-
{
|
63
|
-
readerChannel.overwriteException(exception);
|
64
|
-
}
|
65
|
-
|
66
|
-
void waitForTransferCompletion() throws IOException
|
67
|
-
{
|
68
|
-
Future<?> f = transferCompletionFuture;
|
69
|
-
if (f != null) {
|
70
|
-
try {
|
71
|
-
f.get();
|
72
|
-
}
|
73
|
-
catch (CancellationException | InterruptedException ex) {
|
74
|
-
throw new InterruptedIOException();
|
75
|
-
}
|
76
|
-
catch (ExecutionException ex) {
|
77
|
-
// transfer failed
|
78
|
-
Throwable e = ex.getCause();
|
79
|
-
if (e instanceof IOException) {
|
80
|
-
throw (IOException) e;
|
81
|
-
}
|
82
|
-
else if (e instanceof RuntimeException) {
|
83
|
-
throw (RuntimeException) e;
|
84
|
-
}
|
85
|
-
else if (e instanceof Error) {
|
86
|
-
throw (Error) e;
|
87
|
-
}
|
88
|
-
else {
|
89
|
-
throw new IOException(e);
|
90
|
-
}
|
91
|
-
}
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
public class WriterChannel implements WritableByteChannel
|
96
|
-
{
|
97
|
-
public int write(ByteBuffer src) throws IOException
|
98
|
-
{
|
99
|
-
int sz = src.remaining();
|
100
|
-
if (sz <= 0) {
|
101
|
-
return sz;
|
102
|
-
}
|
103
|
-
|
104
|
-
synchronized (readerChannel) {
|
105
|
-
if (!readerChannel.waitForWritable()) {
|
106
|
-
return -1;
|
107
|
-
}
|
108
|
-
|
109
|
-
readerChannel.setBuffer(src);
|
110
|
-
|
111
|
-
if (!readerChannel.waitForWritable()) { // wait for complete processing src
|
112
|
-
return -1;
|
113
|
-
}
|
114
|
-
}
|
115
|
-
|
116
|
-
return sz - src.remaining();
|
117
|
-
}
|
118
|
-
|
119
|
-
public boolean isOpen()
|
120
|
-
{
|
121
|
-
return readerChannel.isOpen();
|
122
|
-
}
|
123
|
-
|
124
|
-
public void close() throws IOException
|
125
|
-
{
|
126
|
-
readerChannel.closePeer();
|
127
|
-
waitForTransferCompletion();
|
128
|
-
}
|
129
|
-
}
|
130
|
-
|
131
|
-
private static int transferByteBuffer(ByteBuffer src, ByteBuffer dst)
|
132
|
-
{
|
133
|
-
int pos = dst.position();
|
134
|
-
|
135
|
-
int srcrem = src.remaining();
|
136
|
-
int dstrem = dst.remaining();
|
137
|
-
if (dstrem < srcrem) {
|
138
|
-
int lim = src.limit();
|
139
|
-
try {
|
140
|
-
src.limit(src.position() + dstrem);
|
141
|
-
dst.put(src);
|
142
|
-
}
|
143
|
-
finally {
|
144
|
-
src.limit(lim);
|
145
|
-
}
|
146
|
-
}
|
147
|
-
else {
|
148
|
-
dst.put(src);
|
149
|
-
}
|
150
|
-
|
151
|
-
return dst.position() - pos;
|
152
|
-
}
|
153
|
-
|
154
|
-
public class ReaderChannel implements ReadableByteChannel
|
155
|
-
{
|
156
|
-
private ByteBuffer buffer;
|
157
|
-
private Throwable exception;
|
158
|
-
|
159
|
-
public synchronized int read(ByteBuffer dst) throws IOException
|
160
|
-
{
|
161
|
-
if (!waitForReadable()) {
|
162
|
-
return -1;
|
163
|
-
}
|
164
|
-
|
165
|
-
int len = transferByteBuffer(buffer, dst);
|
166
|
-
if (!buffer.hasRemaining()) {
|
167
|
-
setBuffer(null);
|
168
|
-
notifyAll();
|
169
|
-
}
|
170
|
-
|
171
|
-
return len;
|
172
|
-
}
|
173
|
-
|
174
|
-
public synchronized boolean isOpen()
|
175
|
-
{
|
176
|
-
return exception == null;
|
177
|
-
}
|
178
|
-
|
179
|
-
public void close() throws IOException
|
180
|
-
{
|
181
|
-
setException(new EOFException("reader closed channel"));
|
182
|
-
}
|
183
|
-
|
184
|
-
private void setBuffer(ByteBuffer buffer)
|
185
|
-
{
|
186
|
-
this.buffer = buffer;
|
187
|
-
notifyAll();
|
188
|
-
}
|
189
|
-
|
190
|
-
private synchronized boolean waitForWritable() throws IOException
|
191
|
-
{
|
192
|
-
while (buffer != null) {
|
193
|
-
if (exception != null) {
|
194
|
-
if (exception instanceof EOFException) {
|
195
|
-
return false;
|
196
|
-
}
|
197
|
-
throwException();
|
198
|
-
}
|
199
|
-
|
200
|
-
try {
|
201
|
-
wait();
|
202
|
-
}
|
203
|
-
catch (InterruptedException ex) {
|
204
|
-
// TODO throws ClosedByInterruptException or InterruptedIOException?
|
205
|
-
}
|
206
|
-
}
|
207
|
-
|
208
|
-
return true;
|
209
|
-
}
|
210
|
-
|
211
|
-
private boolean waitForReadable() throws IOException
|
212
|
-
{
|
213
|
-
while (buffer == null) {
|
214
|
-
if (exception != null) {
|
215
|
-
if (exception instanceof EOFException) {
|
216
|
-
return false;
|
217
|
-
}
|
218
|
-
throwException();
|
219
|
-
}
|
220
|
-
|
221
|
-
try {
|
222
|
-
wait();
|
223
|
-
}
|
224
|
-
catch (InterruptedException ex) {
|
225
|
-
// TODO throws ClosedByInterruptException or InterruptedIOException?
|
226
|
-
}
|
227
|
-
}
|
228
|
-
|
229
|
-
return true;
|
230
|
-
}
|
231
|
-
|
232
|
-
public synchronized void closePeer() throws IOException
|
233
|
-
{
|
234
|
-
waitForWritable();
|
235
|
-
if (exception != null && !(exception instanceof EOFException)) {
|
236
|
-
throwException();
|
237
|
-
}
|
238
|
-
setException(new EOFException("writer closed channel"));
|
239
|
-
}
|
240
|
-
|
241
|
-
public synchronized void setException(Throwable exception)
|
242
|
-
{
|
243
|
-
if (this.exception == null) {
|
244
|
-
this.exception = exception;
|
245
|
-
}
|
246
|
-
notifyAll();
|
247
|
-
}
|
248
|
-
|
249
|
-
public synchronized void overwriteException(Throwable exception)
|
250
|
-
{
|
251
|
-
this.exception = exception;
|
252
|
-
notifyAll();
|
253
|
-
}
|
254
|
-
|
255
|
-
public boolean hasException()
|
256
|
-
{
|
257
|
-
return exception != null;
|
258
|
-
}
|
259
|
-
|
260
|
-
public void throwException() throws IOException
|
261
|
-
{
|
262
|
-
Throwable ex = exception;
|
263
|
-
if (ex instanceof IOException) {
|
264
|
-
throw (IOException) ex;
|
265
|
-
}
|
266
|
-
else if (ex instanceof RuntimeException) {
|
267
|
-
throw (RuntimeException) ex;
|
268
|
-
}
|
269
|
-
else if (ex instanceof Error) {
|
270
|
-
throw (Error) ex;
|
271
|
-
}
|
272
|
-
else {
|
273
|
-
throw new IOException(ex);
|
274
|
-
}
|
275
|
-
}
|
276
|
-
}
|
277
|
-
}
|