1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.github.jinahya.simple.file.back;
19
20
21 import java.io.IOException;
22 import static java.lang.invoke.MethodHandles.lookup;
23 import java.nio.ByteBuffer;
24 import static java.nio.channels.Channels.newInputStream;
25 import static java.nio.channels.Channels.newOutputStream;
26 import java.nio.channels.FileChannel;
27 import java.nio.channels.ReadableByteChannel;
28 import java.nio.channels.WritableByteChannel;
29 import java.nio.file.Files;
30 import static java.nio.file.Files.newByteChannel;
31 import java.nio.file.Path;
32 import java.nio.file.StandardCopyOption;
33 import java.nio.file.StandardOpenOption;
34 import java.security.NoSuchAlgorithmException;
35 import static java.util.Optional.ofNullable;
36 import static java.util.stream.Collectors.joining;
37 import java.util.stream.StreamSupport;
38 import javax.inject.Inject;
39 import org.slf4j.Logger;
40 import static org.slf4j.LoggerFactory.getLogger;
41
42
43
44
45
46
47 public class LocalFileBack implements FileBack {
48
49
50 private static final String KEY_DIGEST_ALGORITHM = "SHA-1";
51
52
53 private static final int PATH_TOKEN_LENGTH = 3;
54
55
56 private static final String PATH_TOKEN_DELIMITER = "/";
57
58
59 static Path leafPath(final Path rootPath, final ByteBuffer fileKey,
60 final boolean createParent) {
61
62 final Logger logger = getLogger(lookup().lookupClass());
63
64 logger.trace("leafPath({}, {}, {})", rootPath, fileKey, createParent);
65
66 String pathName = null;
67 try {
68 pathName = FileBackUtilities.fileKeyToPathName(
69 fileKey, KEY_DIGEST_ALGORITHM, PATH_TOKEN_LENGTH,
70 PATH_TOKEN_DELIMITER);
71 } catch (final NoSuchAlgorithmException nsae) {
72 throw new RuntimeException(nsae);
73 }
74 logger.trace("path name: {}", pathName);
75
76 final Path leafPath = rootPath.resolve(pathName.replace(
77 PATH_TOKEN_DELIMITER, rootPath.getFileSystem().getSeparator()));
78 logger.trace("leaf path: {}", leafPath);
79
80 if (createParent) {
81 final Path parent = leafPath.getParent();
82 logger.trace("parent: {}", parent);
83 logger.trace("parent.directory: {}", Files.isDirectory(parent));
84 if (!Files.isDirectory(parent)) {
85 try {
86 final Path created = Files.createDirectories(parent);
87 logger.trace("parent created: {}", created);
88 } catch (Exception e) {
89 logger.error("failed to create parent directory: " + parent,
90 e);
91 throw new RuntimeException(e);
92 }
93 }
94 }
95
96 return leafPath;
97 }
98
99
100 @Override
101 public void operate(final FileContext fileContext)
102 throws IOException, FileBackException {
103
104 if (fileContext == null) {
105 throw new NullPointerException("null fileContext");
106 }
107
108 final FileOperation fileOperation
109 = ofNullable(fileContext.fileOperationSupplier())
110 .orElseThrow(
111 () -> new FileBackException("no file operation supplier set"))
112 .get();
113 logger.trace("file operation: {}", fileOperation);
114 if (fileOperation == null) {
115 logger.error("null file operation supplied");
116 return;
117 }
118
119 switch (fileOperation) {
120 case COPY:
121 copy(fileContext);
122 break;
123 case DELETE:
124 delete(fileContext);
125 break;
126 case READ:
127 read(fileContext);
128 break;
129 case WRITE:
130 write(fileContext);
131 break;
132 default:
133 throw new FileBackException(
134 "unsupported operation: " + fileOperation);
135 }
136 }
137
138
139 public void copy(final FileContext fileContext)
140 throws IOException, FileBackException {
141
142 logger.trace("copy({})", fileContext);
143
144 if (fileContext == null) {
145 throw new NullPointerException("null fileContext");
146 }
147
148 final Path[] sourceLeafPath_ = new Path[1];
149 if (sourceLeafPath_[0] == null) {
150 ofNullable(fileContext.sourceKeySupplier()).ifPresent(s -> {
151 ofNullable(s.get()).ifPresent(v -> {
152 logger.trace("source key: {}", v);
153 sourceLeafPath_[0] = leafPath(rootPath, v, false);
154 });
155 });
156 }
157 final Path sourceLeafPath = sourceLeafPath_[0];
158 logger.trace("source leaf path: {}", sourceLeafPath);
159 ofNullable(fileContext.sourceObjectConsumer()).ifPresent(
160 c -> c.accept(sourceLeafPath));
161 if (sourceLeafPath == null) {
162 logger.error("no source leaf path located");
163 return;
164 }
165 if (!Files.isReadable(sourceLeafPath)) {
166 logger.error("source leaf path is not readable: {}",
167 sourceLeafPath);
168 return;
169 }
170
171 final Path[] targetLeafPath_ = new Path[1];
172 if (targetLeafPath_[0] == null) {
173 ofNullable(fileContext.targetKeySupplier()).ifPresent(s -> {
174 ofNullable(s.get()).ifPresent(v -> {
175 logger.trace("target key: {}", v);
176 targetLeafPath_[0] = leafPath(rootPath, v, true);
177 });
178 });
179 }
180 final Path targetLeafPath = targetLeafPath_[0];
181 logger.trace("target leaf path: {}", targetLeafPath);
182 ofNullable(fileContext.targetObjectConsumer()).ifPresent(
183 c -> c.accept(targetLeafPath));
184 if (targetLeafPath == null) {
185 logger.error("no target leaf path located");
186 return;
187 }
188
189 if (sourceLeafPath.equals(targetLeafPath)) {
190 logger.error("source leaf path == target leaf path");
191 return;
192 }
193
194 Files.copy(sourceLeafPath, targetLeafPath,
195 StandardCopyOption.REPLACE_EXISTING);
196 logger.trace("file copied");
197
198 final String pathName = StreamSupport
199 .stream(((Iterable<Path>) () -> rootPath.relativize(targetLeafPath)
200 .iterator()).spliterator(), false)
201 .map(Path::toString).collect(joining("/"));
202 logger.trace("path name: {}", pathName);
203 ofNullable(fileContext.pathNameConsumer()).ifPresent(
204 c -> c.accept(pathName));
205
206 ofNullable(fileContext.sourceCopiedConsumer()).ifPresent(
207 c -> c.accept(sourceLeafPath.toFile().length()));
208 ofNullable(fileContext.targetCopiedConsumer()).ifPresent(
209 c -> c.accept(targetLeafPath.toFile().length()));
210 }
211
212
213 public void delete(final FileContext fileContext)
214 throws IOException, FileBackException {
215
216 if (fileContext == null) {
217 throw new NullPointerException("null fileContext");
218 }
219
220 final Path[] leafPath_ = new Path[1];
221 if (leafPath_[0] == null) {
222 ofNullable(fileContext.sourceKeySupplier()).ifPresent(s -> {
223 ofNullable(s.get()).ifPresent(v -> {
224 logger.trace("source key: {}", v);
225 leafPath_[0] = leafPath(rootPath, v, false);
226 });
227 });
228 }
229 if (leafPath_[0] == null) {
230 ofNullable(fileContext.targetKeySupplier()).ifPresent(s -> {
231 ofNullable(s.get()).ifPresent(v -> {
232 logger.trace("target key: {}", v);
233 leafPath_[0] = leafPath(rootPath, v, false);
234 });
235 });
236 }
237 final Path leafPath = leafPath_[0];
238 logger.trace("leaf path: {}", leafPath);
239 ofNullable(fileContext.sourceObjectConsumer()).ifPresent(
240 c -> c.accept(leafPath));
241 ofNullable(fileContext.targetObjectConsumer()).ifPresent(
242 c -> c.accept(leafPath));
243
244 final boolean fileDeleted = Files.deleteIfExists(leafPath);
245 logger.trace("file deleted: {}", fileDeleted);
246 }
247
248
249 public void read(final FileContext fileContext)
250 throws IOException, FileBackException {
251
252 if (fileContext == null) {
253 throw new NullPointerException("null fileContext");
254 }
255
256 final Path[] sourceLeafPath_ = new Path[1];
257 if (sourceLeafPath_[0] == null) {
258 ofNullable(fileContext.sourceKeySupplier()).ifPresent(s -> {
259 ofNullable(s.get()).ifPresent(v -> {
260 logger.trace("source key: {}", v);
261 sourceLeafPath_[0] = leafPath(rootPath, v, false);
262 });
263 });
264 }
265 if (sourceLeafPath_[0] == null) {
266 ofNullable(fileContext.pathNameSupplier()).ifPresent(s -> {
267 ofNullable(s.get()).ifPresent(v -> {
268 logger.trace("path name: {}", v);
269 sourceLeafPath_[0] = rootPath.resolve(v);
270 });
271 });
272 }
273 final Path sourceLeafPath = sourceLeafPath_[0];
274 logger.trace("source leaf path: {}", sourceLeafPath);
275 ofNullable(fileContext.sourceObjectConsumer()).ifPresent(
276 c -> c.accept(sourceLeafPath));
277 if (sourceLeafPath == null) {
278 logger.warn("no source leaf path located");
279 return;
280 }
281 if (!Files.isRegularFile(sourceLeafPath)) {
282 logger.warn("source leaf path is not a regular file: {}",
283 sourceLeafPath);
284 return;
285 }
286
287 final String pathName = StreamSupport
288 .stream(((Iterable<Path>) () -> rootPath.relativize(sourceLeafPath)
289 .iterator()).spliterator(), false)
290 .map(Path::toString).collect(joining("/"));
291 logger.trace("path name: {}", pathName);
292 ofNullable(fileContext.pathNameConsumer()).ifPresent(
293 c -> c.accept(pathName));
294
295 ofNullable(fileContext.sourceChannelConsumer()).ifPresent(c -> {
296 logger.trace("source channel consumer presents");
297 try {
298 try (ReadableByteChannel sourceChannel = newByteChannel(
299 sourceLeafPath, StandardOpenOption.READ)) {
300 c.accept(sourceChannel);
301 }
302 } catch (IOException ioe) {
303 logger.error(
304 "failed to open source leaf path: " + sourceLeafPath, ioe);
305 }
306 });
307
308 ofNullable(fileContext.targetChannelSupplier()).ifPresent(s -> {
309 logger.trace("target channel supplier presents");
310 final WritableByteChannel targetChannel = s.get();
311 logger.trace("target channel: {}", targetChannel);
312 try {
313 final long copied = Files.copy(
314 sourceLeafPath, newOutputStream(targetChannel));
315 ofNullable(fileContext.sourceCopiedConsumer()).ifPresent(
316 c -> c.accept(copied));
317 ofNullable(fileContext.targetCopiedConsumer()).ifPresent(
318 c -> c.accept(copied));
319 } catch (final IOException ioe) {
320 logger.error(
321 "failed to copy from source leaf path to target channel",
322 ioe);
323 }
324 });
325 }
326
327
328 public void write(final FileContext fileContext)
329 throws IOException, FileBackException {
330
331 if (fileContext == null) {
332 throw new NullPointerException("null fileContext");
333 }
334
335 final Path[] targetLeafPath_ = new Path[1];
336 if (targetLeafPath_[0] == null) {
337 ofNullable(fileContext.targetKeySupplier()).ifPresent(s -> {
338 ofNullable(s.get()).ifPresent(v -> {
339 logger.trace("target key: {}", v);
340 targetLeafPath_[0] = leafPath(rootPath, v, true);
341 });
342 });
343 }
344 final Path targetLeafPath = targetLeafPath_[0];
345 logger.trace("target leaf path: {}", targetLeafPath);
346 ofNullable(fileContext.targetObjectConsumer()).ifPresent(
347 c -> c.accept(targetLeafPath));
348 if (targetLeafPath == null) {
349 logger.warn("no target leaf path located");
350 return;
351 }
352
353 final String pathName = StreamSupport
354 .stream(((Iterable<Path>) () -> rootPath.relativize(targetLeafPath)
355 .iterator()).spliterator(), false)
356 .map(Path::toString).collect(joining("/"));
357 logger.trace("path name: {}", pathName);
358 ofNullable(fileContext.pathNameConsumer()).ifPresent(
359 c -> {
360 logger.trace("accepting path name consumer");
361 c.accept(pathName);
362 });
363
364 ofNullable(fileContext.targetChannelConsumer()).ifPresent(c -> {
365 logger.trace("target channel consumer presents");
366 try {
367 try (FileChannel targetChannel = FileChannel.open(
368 targetLeafPath, StandardOpenOption.CREATE,
369 StandardOpenOption.WRITE)) {
370 c.accept(targetChannel);
371 targetChannel.force(true);
372 }
373 } catch (IOException ioe) {
374 logger.error(
375 "failed to open target leaf path: " + targetLeafPath, ioe);
376 }
377 });
378
379 ofNullable(fileContext.sourceChannelSupplier()).ifPresent(s -> {
380 logger.trace("source channel supplier: {}", s);
381 final ReadableByteChannel sourceChannel = s.get();
382 logger.trace("target channel: {}", sourceChannel);
383 try {
384 final long copied = Files.copy(
385 newInputStream(sourceChannel), targetLeafPath,
386 StandardCopyOption.REPLACE_EXISTING);
387 ofNullable(fileContext.sourceCopiedConsumer()).ifPresent(
388 c -> c.accept(copied));
389 ofNullable(fileContext.targetCopiedConsumer()).ifPresent(
390 c -> c.accept(copied));
391 } catch (final IOException ioe) {
392 logger.error(
393 "failed to copy from source channel to target leaf path",
394 ioe);
395 }
396 });
397 }
398
399
400 Path rootPath() {
401
402 return rootPath;
403 }
404
405
406 private transient final Logger logger = getLogger(lookup().lookupClass());
407
408
409 @Inject
410 @RootPath
411 private Path rootPath;
412
413
414 }
415