embulk-input-ftp 0.1.0
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 +7 -0
- data/.gitignore +8 -0
- data/ChangeLog +5 -0
- data/README.md +35 -0
- data/build.gradle +73 -0
- data/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/gradle/wrapper/gradle-wrapper.properties +6 -0
- data/gradlew +164 -0
- data/gradlew.bat +90 -0
- data/lib/embulk/input/ftp.rb +3 -0
- data/libs/ftp4j-1.7.2.jar +0 -0
- data/src/main/java/org/embulk/input/FtpFileInputPlugin.java +583 -0
- data/src/main/java/org/embulk/input/ftp/BlockingTransfer.java +267 -0
- data/src/main/java/org/embulk/input/ftp/RetryExecutor.java +131 -0
- data/src/main/java/org/embulk/input/ftp/RetryableInputStream.java +129 -0
- data/src/test/java/org/embulk/input/TestFtpFileInputPlugin.java +5 -0
- metadata +89 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
package org.embulk.input.ftp;
|
2
|
+
|
3
|
+
import java.util.concurrent.Future;
|
4
|
+
import java.util.concurrent.Callable;
|
5
|
+
import java.util.concurrent.CancellationException;
|
6
|
+
import java.util.concurrent.ExecutionException;
|
7
|
+
import java.util.concurrent.ExecutorService;
|
8
|
+
import java.io.IOException;
|
9
|
+
import java.io.EOFException;
|
10
|
+
import java.io.InterruptedIOException;
|
11
|
+
import java.nio.ByteBuffer;
|
12
|
+
import java.nio.channels.ReadableByteChannel;
|
13
|
+
import java.nio.channels.WritableByteChannel;
|
14
|
+
import com.google.common.base.Function;
|
15
|
+
|
16
|
+
public class BlockingTransfer
|
17
|
+
{
|
18
|
+
private final WriterChannel writerChannel;
|
19
|
+
private final ReaderChannel readerChannel;
|
20
|
+
private Future<?> transferCompletionFuture;
|
21
|
+
|
22
|
+
public static BlockingTransfer submit(ExecutorService executor,
|
23
|
+
Function<BlockingTransfer, Runnable> starterFactory)
|
24
|
+
{
|
25
|
+
BlockingTransfer transfer = new BlockingTransfer();
|
26
|
+
final Runnable starter = starterFactory.apply(transfer);
|
27
|
+
transfer.setTransferCompletionFuture(
|
28
|
+
executor.submit(new Callable<Void>() {
|
29
|
+
public Void call() throws Exception
|
30
|
+
{
|
31
|
+
starter.run();
|
32
|
+
return null;
|
33
|
+
}
|
34
|
+
})
|
35
|
+
);
|
36
|
+
return transfer;
|
37
|
+
}
|
38
|
+
|
39
|
+
private BlockingTransfer()
|
40
|
+
{
|
41
|
+
this.writerChannel = new WriterChannel();
|
42
|
+
this.readerChannel = new ReaderChannel();
|
43
|
+
}
|
44
|
+
|
45
|
+
private void setTransferCompletionFuture(Future<?> future)
|
46
|
+
{
|
47
|
+
this.transferCompletionFuture = future;
|
48
|
+
}
|
49
|
+
|
50
|
+
public ReadableByteChannel getReaderChannel()
|
51
|
+
{
|
52
|
+
return readerChannel;
|
53
|
+
}
|
54
|
+
|
55
|
+
public WritableByteChannel getWriterChannel()
|
56
|
+
{
|
57
|
+
return writerChannel;
|
58
|
+
}
|
59
|
+
|
60
|
+
public void transferFailed(Throwable exception)
|
61
|
+
{
|
62
|
+
readerChannel.overwriteException(exception);
|
63
|
+
}
|
64
|
+
|
65
|
+
void waitForTransferCompletion() throws IOException
|
66
|
+
{
|
67
|
+
Future<?> f = transferCompletionFuture;
|
68
|
+
if (f != null) {
|
69
|
+
try {
|
70
|
+
f.get();
|
71
|
+
} catch (CancellationException ex) {
|
72
|
+
throw new InterruptedIOException();
|
73
|
+
} catch (InterruptedException ex) {
|
74
|
+
throw new InterruptedIOException();
|
75
|
+
} catch (ExecutionException ex) {
|
76
|
+
// transfer failed
|
77
|
+
Throwable e = ex.getCause();
|
78
|
+
if (e instanceof IOException) {
|
79
|
+
throw (IOException) e;
|
80
|
+
} else if (e instanceof RuntimeException) {
|
81
|
+
throw (RuntimeException) e;
|
82
|
+
} else if (e instanceof Error) {
|
83
|
+
throw (Error) e;
|
84
|
+
} else {
|
85
|
+
throw new IOException(e);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
public class WriterChannel implements WritableByteChannel
|
92
|
+
{
|
93
|
+
public int write(ByteBuffer src) throws IOException
|
94
|
+
{
|
95
|
+
int sz = src.remaining();
|
96
|
+
if (sz <= 0) {
|
97
|
+
return sz;
|
98
|
+
}
|
99
|
+
|
100
|
+
synchronized(readerChannel) {
|
101
|
+
if (!readerChannel.waitForWritable()) {
|
102
|
+
return -1;
|
103
|
+
}
|
104
|
+
|
105
|
+
readerChannel.setBuffer(src);
|
106
|
+
|
107
|
+
if (!readerChannel.waitForWritable()) { // wait for complete processing src
|
108
|
+
return -1;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
return sz - src.remaining();
|
113
|
+
}
|
114
|
+
|
115
|
+
public boolean isOpen()
|
116
|
+
{
|
117
|
+
return readerChannel.isOpen();
|
118
|
+
}
|
119
|
+
|
120
|
+
public void close() throws IOException
|
121
|
+
{
|
122
|
+
readerChannel.closePeer();
|
123
|
+
waitForTransferCompletion();
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
private static int transferByteBuffer(ByteBuffer src, ByteBuffer dst)
|
128
|
+
{
|
129
|
+
int pos = dst.position();
|
130
|
+
|
131
|
+
int srcrem = src.remaining();
|
132
|
+
int dstrem = dst.remaining();
|
133
|
+
if (dstrem < srcrem) {
|
134
|
+
int lim = src.limit();
|
135
|
+
try {
|
136
|
+
src.limit(src.position() + dstrem);
|
137
|
+
dst.put(src);
|
138
|
+
} finally {
|
139
|
+
src.limit(lim);
|
140
|
+
}
|
141
|
+
} else {
|
142
|
+
dst.put(src);
|
143
|
+
}
|
144
|
+
|
145
|
+
return dst.position() - pos;
|
146
|
+
}
|
147
|
+
|
148
|
+
public class ReaderChannel implements ReadableByteChannel
|
149
|
+
{
|
150
|
+
private ByteBuffer buffer;
|
151
|
+
private Throwable exception;
|
152
|
+
|
153
|
+
public synchronized int read(ByteBuffer dst) throws IOException
|
154
|
+
{
|
155
|
+
if (!waitForReadable()) {
|
156
|
+
return -1;
|
157
|
+
}
|
158
|
+
|
159
|
+
int len = transferByteBuffer(buffer, dst);
|
160
|
+
if (!buffer.hasRemaining()) {
|
161
|
+
setBuffer(null);
|
162
|
+
notifyAll();
|
163
|
+
}
|
164
|
+
|
165
|
+
return len;
|
166
|
+
}
|
167
|
+
|
168
|
+
public synchronized boolean isOpen()
|
169
|
+
{
|
170
|
+
return exception == null;
|
171
|
+
}
|
172
|
+
|
173
|
+
public void close() throws IOException
|
174
|
+
{
|
175
|
+
setException(new EOFException("reader closed channel"));
|
176
|
+
}
|
177
|
+
|
178
|
+
private void setBuffer(ByteBuffer buffer)
|
179
|
+
{
|
180
|
+
this.buffer = buffer;
|
181
|
+
notifyAll();
|
182
|
+
}
|
183
|
+
|
184
|
+
private synchronized boolean waitForWritable() throws IOException
|
185
|
+
{
|
186
|
+
while (buffer != null) {
|
187
|
+
if (exception != null) {
|
188
|
+
if (exception instanceof EOFException) {
|
189
|
+
return false;
|
190
|
+
}
|
191
|
+
throwException();
|
192
|
+
}
|
193
|
+
|
194
|
+
try {
|
195
|
+
wait();
|
196
|
+
} catch (InterruptedException ex) {
|
197
|
+
// TODO throws ClosedByInterruptException or InterruptedIOException?
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
return true;
|
202
|
+
}
|
203
|
+
|
204
|
+
private boolean waitForReadable() throws IOException
|
205
|
+
{
|
206
|
+
while(buffer == null) {
|
207
|
+
if (exception != null) {
|
208
|
+
if (exception instanceof EOFException) {
|
209
|
+
return false;
|
210
|
+
}
|
211
|
+
throwException();
|
212
|
+
}
|
213
|
+
|
214
|
+
try {
|
215
|
+
wait();
|
216
|
+
} catch (InterruptedException ex) {
|
217
|
+
// TODO throws ClosedByInterruptException or InterruptedIOException?
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
return true;
|
222
|
+
}
|
223
|
+
|
224
|
+
public synchronized void closePeer() throws IOException
|
225
|
+
{
|
226
|
+
waitForWritable();
|
227
|
+
if( exception != null && !(exception instanceof EOFException)) {
|
228
|
+
throwException();
|
229
|
+
}
|
230
|
+
setException(new EOFException("writer closed channel"));
|
231
|
+
}
|
232
|
+
|
233
|
+
public synchronized void setException(Throwable exception)
|
234
|
+
{
|
235
|
+
if (this.exception == null) {
|
236
|
+
this.exception = exception;
|
237
|
+
}
|
238
|
+
notifyAll();
|
239
|
+
}
|
240
|
+
|
241
|
+
public synchronized void overwriteException(Throwable exception)
|
242
|
+
{
|
243
|
+
this.exception = exception;
|
244
|
+
notifyAll();
|
245
|
+
}
|
246
|
+
|
247
|
+
public boolean hasException()
|
248
|
+
{
|
249
|
+
return exception != null;
|
250
|
+
}
|
251
|
+
|
252
|
+
public void throwException() throws IOException
|
253
|
+
{
|
254
|
+
Throwable ex = exception;
|
255
|
+
if (ex instanceof IOException) {
|
256
|
+
throw (IOException) ex;
|
257
|
+
} else if (ex instanceof RuntimeException) {
|
258
|
+
throw (RuntimeException) ex;
|
259
|
+
} else if (ex instanceof Error) {
|
260
|
+
throw (Error) ex;
|
261
|
+
} else {
|
262
|
+
throw new IOException(ex);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
}
|
267
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
// TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
|
2
|
+
package org.embulk.input.ftp;
|
3
|
+
|
4
|
+
import java.util.concurrent.Callable;
|
5
|
+
import java.util.concurrent.ExecutionException;
|
6
|
+
|
7
|
+
public class RetryExecutor
|
8
|
+
{
|
9
|
+
public static RetryExecutor retryExecutor()
|
10
|
+
{
|
11
|
+
// TODO default configuration
|
12
|
+
return new RetryExecutor(3, 500, 30*60*1000);
|
13
|
+
}
|
14
|
+
|
15
|
+
public static class RetryGiveupException
|
16
|
+
extends ExecutionException
|
17
|
+
{
|
18
|
+
public RetryGiveupException(String message, Exception cause)
|
19
|
+
{
|
20
|
+
super(cause);
|
21
|
+
}
|
22
|
+
|
23
|
+
public RetryGiveupException(Exception cause)
|
24
|
+
{
|
25
|
+
super(cause);
|
26
|
+
}
|
27
|
+
|
28
|
+
public Exception getCause()
|
29
|
+
{
|
30
|
+
return (Exception) super.getCause();
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
public static interface Retryable<T>
|
35
|
+
extends Callable<T>
|
36
|
+
{
|
37
|
+
public T call()
|
38
|
+
throws Exception;
|
39
|
+
|
40
|
+
public boolean isRetryableException(Exception exception);
|
41
|
+
|
42
|
+
public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait)
|
43
|
+
throws RetryGiveupException;
|
44
|
+
|
45
|
+
public void onGiveup(Exception firstException, Exception lastException)
|
46
|
+
throws RetryGiveupException;
|
47
|
+
}
|
48
|
+
|
49
|
+
private final int retryLimit;
|
50
|
+
private final int initialRetryWait;
|
51
|
+
private final int maxRetryWait;
|
52
|
+
|
53
|
+
private RetryExecutor(int retryLimit, int initialRetryWait, int maxRetryWait)
|
54
|
+
{
|
55
|
+
this.retryLimit = retryLimit;
|
56
|
+
this.initialRetryWait = initialRetryWait;
|
57
|
+
this.maxRetryWait = maxRetryWait;
|
58
|
+
}
|
59
|
+
|
60
|
+
public RetryExecutor withRetryLimit(int count)
|
61
|
+
{
|
62
|
+
return new RetryExecutor(count, initialRetryWait, maxRetryWait);
|
63
|
+
}
|
64
|
+
|
65
|
+
public RetryExecutor withInitialRetryWait(int msec)
|
66
|
+
{
|
67
|
+
return new RetryExecutor(retryLimit, msec, maxRetryWait);
|
68
|
+
}
|
69
|
+
|
70
|
+
public RetryExecutor withMaxRetryWait(int msec)
|
71
|
+
{
|
72
|
+
return new RetryExecutor(retryLimit, initialRetryWait, msec);
|
73
|
+
}
|
74
|
+
|
75
|
+
public <T> T runInterruptible(Retryable<T> op)
|
76
|
+
throws InterruptedException, RetryGiveupException
|
77
|
+
{
|
78
|
+
return run(op, true);
|
79
|
+
}
|
80
|
+
|
81
|
+
public <T> T run(Retryable<T> op)
|
82
|
+
throws RetryGiveupException
|
83
|
+
{
|
84
|
+
try {
|
85
|
+
return run(op, false);
|
86
|
+
} catch (InterruptedException ex) {
|
87
|
+
throw new RetryGiveupException("Unexpected interruption", ex);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
private <T> T run(Retryable<T> op, boolean interruptible)
|
92
|
+
throws InterruptedException, RetryGiveupException
|
93
|
+
{
|
94
|
+
int retryWait = initialRetryWait;
|
95
|
+
int retryCount = 0;
|
96
|
+
|
97
|
+
Exception firstException = null;
|
98
|
+
|
99
|
+
while(true) {
|
100
|
+
try {
|
101
|
+
return op.call();
|
102
|
+
} catch (Exception exception) {
|
103
|
+
if (firstException == null) {
|
104
|
+
firstException = exception;
|
105
|
+
}
|
106
|
+
if (!op.isRetryableException(exception) || retryCount >= retryLimit) {
|
107
|
+
op.onGiveup(firstException, exception);
|
108
|
+
throw new RetryGiveupException(firstException);
|
109
|
+
}
|
110
|
+
|
111
|
+
retryCount++;
|
112
|
+
op.onRetry(exception, retryCount, retryLimit, retryWait);
|
113
|
+
|
114
|
+
try {
|
115
|
+
Thread.sleep(retryWait);
|
116
|
+
} catch (InterruptedException ex) {
|
117
|
+
if (interruptible) {
|
118
|
+
throw ex;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
// exponential back-off with hard limit
|
123
|
+
retryWait *= 2;
|
124
|
+
if (retryWait > maxRetryWait) {
|
125
|
+
retryWait = maxRetryWait;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
// TODO copied from S3FileInputPlugin. This should be moved to org.embulk.
|
2
|
+
package org.embulk.input.ftp;
|
3
|
+
|
4
|
+
import java.io.InputStream;
|
5
|
+
import java.io.IOException;
|
6
|
+
|
7
|
+
public class RetryableInputStream
|
8
|
+
extends InputStream
|
9
|
+
{
|
10
|
+
public interface Opener
|
11
|
+
{
|
12
|
+
public InputStream open(long offset, Exception exception) throws IOException;
|
13
|
+
}
|
14
|
+
|
15
|
+
private final Opener opener;
|
16
|
+
protected InputStream in;
|
17
|
+
private long offset;
|
18
|
+
private long markedOffset;
|
19
|
+
|
20
|
+
public RetryableInputStream(InputStream initialInputStream, Opener reopener)
|
21
|
+
{
|
22
|
+
this.opener = reopener;
|
23
|
+
this.in = initialInputStream;
|
24
|
+
this.offset = 0L;
|
25
|
+
this.markedOffset = 0L;
|
26
|
+
}
|
27
|
+
|
28
|
+
public RetryableInputStream(Opener opener) throws IOException
|
29
|
+
{
|
30
|
+
this(opener.open(0, null), opener);
|
31
|
+
}
|
32
|
+
|
33
|
+
private void reopen(Exception exception) throws IOException
|
34
|
+
{
|
35
|
+
if (in != null) {
|
36
|
+
in.close();
|
37
|
+
in = null;
|
38
|
+
}
|
39
|
+
in = opener.open(offset, exception);
|
40
|
+
}
|
41
|
+
|
42
|
+
@Override
|
43
|
+
public int read() throws IOException
|
44
|
+
{
|
45
|
+
while (true) {
|
46
|
+
try {
|
47
|
+
int v = in.read();
|
48
|
+
offset += 1;
|
49
|
+
return v;
|
50
|
+
} catch (IOException | RuntimeException ex) {
|
51
|
+
reopen(ex);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
@Override
|
57
|
+
public int read(byte[] b) throws IOException
|
58
|
+
{
|
59
|
+
while (true) {
|
60
|
+
try {
|
61
|
+
int r = in.read(b);
|
62
|
+
offset += r;
|
63
|
+
return r;
|
64
|
+
} catch (IOException | RuntimeException ex) {
|
65
|
+
reopen(ex);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
@Override
|
71
|
+
public int read(byte[] b, int off, int len) throws IOException
|
72
|
+
{
|
73
|
+
while (true) {
|
74
|
+
try {
|
75
|
+
int r = in.read(b, off, len);
|
76
|
+
offset += r;
|
77
|
+
return r;
|
78
|
+
} catch (IOException | RuntimeException ex) {
|
79
|
+
reopen(ex);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
@Override
|
85
|
+
public long skip(long n) throws IOException
|
86
|
+
{
|
87
|
+
while (true) {
|
88
|
+
try {
|
89
|
+
long r = in.skip(n);
|
90
|
+
offset += r;
|
91
|
+
return r;
|
92
|
+
} catch (IOException | RuntimeException ex) {
|
93
|
+
reopen(ex);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
@Override
|
99
|
+
public int available() throws IOException
|
100
|
+
{
|
101
|
+
return in.available();
|
102
|
+
}
|
103
|
+
|
104
|
+
@Override
|
105
|
+
public void close() throws IOException
|
106
|
+
{
|
107
|
+
in.close();
|
108
|
+
}
|
109
|
+
|
110
|
+
@Override
|
111
|
+
public void mark(int readlimit)
|
112
|
+
{
|
113
|
+
in.mark(readlimit);
|
114
|
+
markedOffset = offset;
|
115
|
+
}
|
116
|
+
|
117
|
+
@Override
|
118
|
+
public void reset() throws IOException
|
119
|
+
{
|
120
|
+
in.reset();
|
121
|
+
offset = markedOffset;
|
122
|
+
}
|
123
|
+
|
124
|
+
@Override
|
125
|
+
public boolean markSupported()
|
126
|
+
{
|
127
|
+
return in.markSupported();
|
128
|
+
}
|
129
|
+
}
|