View Javadoc
1   /*
2    * Copyright 2014 Jin Kwon.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  
18  package com.github.jinahya.simple.file.front;
19  
20  
21  import com.github.jinahya.simple.file.back.DefaultFileContext;
22  import com.github.jinahya.simple.file.back.FileBack;
23  import com.github.jinahya.simple.file.back.FileBack.FileOperation;
24  import com.github.jinahya.simple.file.back.FileBackException;
25  import com.github.jinahya.simple.file.back.FileContext;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import static java.lang.invoke.MethodHandles.lookup;
29  import java.net.URI;
30  import java.nio.ByteBuffer;
31  import java.nio.channels.Channels;
32  import java.nio.channels.FileChannel;
33  import java.nio.charset.StandardCharsets;
34  import java.nio.file.Files;
35  import java.nio.file.StandardCopyOption;
36  import java.nio.file.StandardOpenOption;
37  import java.util.ArrayList;
38  import java.util.List;
39  import static java.util.Optional.ofNullable;
40  import java.util.concurrent.ExecutionException;
41  import java.util.concurrent.Future;
42  import javax.annotation.PostConstruct;
43  import javax.annotation.PreDestroy;
44  import javax.inject.Inject;
45  import javax.ws.rs.Consumes;
46  import javax.ws.rs.DELETE;
47  import javax.ws.rs.DefaultValue;
48  import javax.ws.rs.GET;
49  import javax.ws.rs.HeaderParam;
50  import javax.ws.rs.NotFoundException;
51  import javax.ws.rs.POST;
52  import javax.ws.rs.PUT;
53  import javax.ws.rs.Path;
54  import javax.ws.rs.PathParam;
55  import javax.ws.rs.ProcessingException;
56  import javax.ws.rs.Produces;
57  import javax.ws.rs.QueryParam;
58  import javax.ws.rs.WebApplicationException;
59  import javax.ws.rs.client.Client;
60  import javax.ws.rs.client.ClientBuilder;
61  import javax.ws.rs.client.Entity;
62  import javax.ws.rs.client.WebTarget;
63  import javax.ws.rs.core.Context;
64  import javax.ws.rs.core.MediaType;
65  import javax.ws.rs.core.Response;
66  import javax.ws.rs.core.StreamingOutput;
67  import javax.ws.rs.core.UriInfo;
68  import org.glassfish.jersey.client.ClientProperties;
69  import org.slf4j.Logger;
70  import static org.slf4j.LoggerFactory.getLogger;
71  
72  
73  /**
74   *
75   * @author Jin Kwon <jinahya_at_gmail.com>
76   */
77  public abstract class AbstractLocatorsResource {
78  
79  
80      public static final String PREFERRED_PATH_VALUE = "locators";
81  
82  
83      protected static ByteBuffer key(final String locator) {
84  
85          final Logger logger = getLogger(lookup().lookupClass());
86  
87          logger.trace("key({})", locator);
88  
89          return ByteBuffer.wrap(locator.getBytes(StandardCharsets.UTF_8));
90      }
91  
92  
93      @PostConstruct
94      private void constructed() {
95  
96          logger.trace("fileBack: {}", fileBack);
97          logger.trace("fileFronts: {}", fileFronts);
98          logger.trace("Header.Accept: {}", accept);
99  
100         try {
101             tempPath = Files.createTempFile("prefix", "suffix");
102             logger.trace("temp path created: {}", tempPath);
103         } catch (final IOException ioe) {
104             logger.error("failed to create temp path", ioe);
105         }
106     }
107 
108 
109     @PreDestroy
110     private void destroying() {
111 
112         if (tempPath != null) {
113             try {
114                 Files.deleteIfExists(tempPath);
115             } catch (final IOException ioe) {
116                 logger.error("failed to delete temp path: " + tempPath, ioe);
117             }
118         }
119     }
120 
121 
122     protected Response copySingle(final FileContext fileContext,
123                                   final String sourceLocator,
124                                   final String targetLocator,
125                                   final boolean distributeFlag)
126         throws IOException, FileBackException {
127 
128         logger.trace("copySingle({}, {}, {}, {})", fileContext, sourceLocator,
129                      targetLocator, distributeFlag);
130 
131         fileContext.fileOperationSupplier(() -> FileOperation.COPY);
132 
133         fileContext.sourceKeySupplier(
134             ofNullable(fileContext.sourceKeySupplier()).orElse(
135                 () -> key(sourceLocator)));
136         fileContext.targetKeySupplier(
137             ofNullable(fileContext.targetKeySupplier()).orElse(
138                 () -> key(targetLocator)));
139 
140         final Object[] sourceObject_ = new Object[1];
141         fileContext.sourceObjectConsumer(
142             ofNullable(fileContext.sourceObjectConsumer()).orElse(
143                 sourceObject -> {
144                     logger.trace("consuming source object: {}", sourceObject);
145                     sourceObject_[0] = sourceObject;
146                 }));
147 
148         final Long[] sourceCopied_ = new Long[1];
149         fileContext.sourceCopiedConsumer(
150             ofNullable(fileContext.sourceCopiedConsumer()).orElse(
151                 sourceCopied -> {
152                     logger.trace("consuming source copied: {}", sourceCopied);
153                     sourceCopied_[0] = sourceCopied;
154                 }));
155 
156         final Object[] targetObject_ = new Object[1];
157         fileContext.targetObjectConsumer(
158             ofNullable(fileContext.targetObjectConsumer()).orElse(
159                 targetObject -> {
160                     logger.trace("consuming target object: {}", targetObject);
161                     targetObject_[0] = targetObject;
162                 }));
163 
164         final Long[] targetCopied_ = new Long[1];
165         fileContext.targetCopiedConsumer(
166             ofNullable(fileContext.targetCopiedConsumer()).orElse(
167                 targetCopied -> {
168                     logger.trace("consuming target copied: {}", targetCopied);
169                     targetCopied_[0] = targetCopied;
170                 }));
171 
172         final String[] pathName_ = new String[1];
173         fileContext.pathNameConsumer(
174             ofNullable(fileContext.pathNameConsumer()).orElse(
175                 pathName -> {
176                     logger.trace("consuming path name: {}", pathName);
177                     pathName_[0] = pathName;
178                 }));
179 
180         try {
181             fileBack.operate(fileContext);
182         } catch (IOException | FileBackException e) {
183             final String message = "failed to operate file back";
184             logger.error(message, e);
185             throw new WebApplicationException(message, e);
186         }
187 
188         if (distributeFlag) {
189             final URI baseUri = uriInfo.getBaseUri();
190             logger.trace("uriInfo.baseUri: {}", baseUri);
191             final String path = uriInfo.getPath();
192             logger.trace("uriInfo.path: {}", path);
193             final List<Future<Response>> futures = new ArrayList<>();
194             for (final URI fileFront : fileFronts) {
195                 logger.trace("fileFront: {}", fileFront);
196                 if (baseUri.equals(fileFront)) {
197                     logger.trace("skipping self: " + fileFront);
198                     continue;
199                 }
200                 if (!fileFront.isAbsolute()) {
201                     logger.warn("not an absolute uri: {}", fileFront);
202                     continue;
203                 }
204                 final Client client = ClientBuilder.newClient()
205                     .property(ClientProperties.CONNECT_TIMEOUT, 1000)
206                     .property(ClientProperties.READ_TIMEOUT, 1000);
207                 final WebTarget target = client.target(fileFront).path(path)
208                     .queryParam("locator", targetLocator)
209                     .queryParam("distribute", Boolean.FALSE.toString());
210                 logger.trace("target.uri: {}", target.getUri().toString());
211                 final Future<Response> future
212                     = target.request().async().method("POST");
213                 logger.trace("future: {}", future);
214                 futures.add(future);
215             }
216             logger.trace("futures: {}", futures);
217             futures.forEach(future -> {
218                 try {
219                     final Response response = future.get();
220                     logger.trace("response: {}", response);
221                     logger.trace("response.statusInfo: {}",
222                                  response.getStatusInfo());
223                 } catch (InterruptedException | ExecutionException e) {
224                     logger.error("fail to get response", e);
225                 }
226             });
227         }
228 
229         return Response.noContent()
230             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
231             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
232             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
233             .build();
234     }
235 
236 
237     @POST
238     @Path("/{locator: .+}/copy")
239     public Response copySingle(
240         @PathParam("locator") final String sourceLocator,
241         @QueryParam("locator") final String targetLocator,
242         @QueryParam("distribute") @DefaultValue("true")
243         final boolean distribute)
244         throws IOException, FileBackException {
245 
246         logger.trace("copySingle({}, {}, {})", sourceLocator, targetLocator,
247                      distribute);
248 
249         final FileContext fileContext = new DefaultFileContext();
250 
251         fileContext.fileOperationSupplier(() -> FileOperation.COPY);
252 
253         fileContext.sourceKeySupplier(() -> ByteBuffer.wrap(
254             sourceLocator.getBytes(StandardCharsets.UTF_8)));
255         fileContext.targetKeySupplier(() -> ByteBuffer.wrap(
256             targetLocator.getBytes(StandardCharsets.UTF_8)));
257 
258         final Object[] sourceObject_ = new Object[1];
259         fileContext.sourceObjectConsumer(sourceObject -> {
260             logger.trace("source object: {}", sourceObject);
261             sourceObject_[0] = sourceObject;
262         });
263 
264         final Long[] sourceCopied_ = new Long[1];
265         fileContext.sourceCopiedConsumer(sourceCopied -> {
266             logger.trace("source copied: {}", sourceCopied);
267             sourceCopied_[0] = sourceCopied;
268         });
269 
270         final Object[] targetObject_ = new Object[1];
271         fileContext.targetObjectConsumer(targetObject -> {
272             logger.trace("target object: {}", targetObject);
273             targetObject_[0] = targetObject;
274         });
275 
276         final Long[] targetCopied_ = new Long[0];
277         fileContext.targetCopiedConsumer(targetCopied -> {
278             logger.trace("target copied: {}", targetCopied);
279             targetCopied_[0] = targetCopied;
280         });
281 
282         final String[] pathName_ = new String[1];
283         fileContext.pathNameConsumer(pathName -> {
284             logger.trace("path name: {}", pathName);
285             pathName_[0] = pathName;
286         });
287 
288         fileBack.operate(fileContext); // ------------------------------ OPERATE
289 
290         if (distribute) {
291             final URI baseUri = uriInfo.getBaseUri();
292             logger.trace("uriInfo.baseUri: {}", baseUri);
293             final String path = uriInfo.getPath();
294             logger.trace("uriInfo.path: {}", path);
295             final List<Future<Response>> futures = new ArrayList<>();
296             for (final URI fileFront : fileFronts) {
297                 logger.trace("fileFront: {}", fileFront);
298                 if (baseUri.equals(fileFront)) {
299                     logger.trace("skipping self: " + fileFront);
300                     continue;
301                 }
302                 if (!fileFront.isAbsolute()) {
303                     logger.warn("not an absolute uri: {}", fileFront);
304                     continue;
305                 }
306                 final Client client = ClientBuilder.newClient()
307                     .property(ClientProperties.CONNECT_TIMEOUT, 1000)
308                     .property(ClientProperties.READ_TIMEOUT, 1000);
309                 final WebTarget target = client.target(fileFront).path(path)
310                     .queryParam("source_locator", sourceLocator)
311                     .queryParam("target_locator", targetLocator)
312                     .queryParam("distribute", Boolean.FALSE.toString());
313                 logger.trace("target.uri: {}", target.getUri().toString());
314                 try {
315                     final Future<Response> future
316                         = target.request().async().method("POST");
317                     logger.trace("future: {}", future);
318                     futures.add(future);
319                 } catch (final ProcessingException pe) {
320                     logger.error("failed to distribute to " + fileFront, pe);
321                 }
322             }
323             logger.trace("futures: {}", futures);
324             futures.forEach(future -> {
325                 try {
326                     final Response response = future.get();
327                     logger.trace("response: {}", response);
328                     logger.trace("response.statusInfo: {}",
329                                  response.getStatusInfo());
330                 } catch (InterruptedException | ExecutionException e) {
331                     logger.error("fail to get response", e);
332                 }
333             });
334         }
335 
336         return Response.noContent()
337             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
338             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
339             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
340             .build();
341     }
342 
343 
344     /**
345      *
346      * @param locator
347      * @param distribute
348      *
349      * @return
350      *
351      * @throws IOException if an I/O error occurs.
352      * @throws FileBackException if a file back error occurs.
353      * @see FileBack#operate(com.github.jinahya.simple.file.back.FileContext)
354      */
355     @DELETE
356     @Path("{locator: .+}")
357     public Response deleteSingle(
358         @PathParam("locator") final String locator,
359         @QueryParam("distribute") @DefaultValue("true")
360         final boolean distribute)
361         throws IOException, FileBackException {
362 
363         logger.trace("deleteSingle({}, {})", locator, distribute);
364 
365         final FileContext fileContext = new DefaultFileContext();
366 
367         fileContext.fileOperationSupplier(() -> FileOperation.DELETE);
368 
369         fileContext.targetKeySupplier(
370             () -> ByteBuffer.wrap(locator.getBytes(StandardCharsets.UTF_8)));
371 
372         final Object[] sourceObject_ = new Object[1];
373         fileContext.sourceObjectConsumer(sourceObject -> {
374             logger.trace("consuming source object: {}", sourceObject);
375             sourceObject_[0] = sourceObject;
376         });
377 
378         final Long[] sourceCopied_ = new Long[1];
379         fileContext.sourceCopiedConsumer(sourceCopied -> {
380             logger.trace("consuming source copied: {}", sourceCopied);
381             sourceCopied_[0] = sourceCopied;
382         });
383 
384         final Object[] targetObject_ = new Object[1];
385         fileContext.targetObjectConsumer(targetObject -> {
386             logger.trace("consuming target object: {}", targetObject);
387             targetObject_[0] = targetObject;
388         });
389 
390         final Long[] targetCopied_ = new Long[1];
391         fileContext.targetCopiedConsumer(targetCopied -> {
392             logger.trace("consuming target copied: {}", targetCopied);
393             targetCopied_[0] = targetCopied;
394         });
395 
396         final String[] pathName_ = new String[1];
397         fileContext.pathNameConsumer(pathName -> {
398             logger.trace("consuming path name: {}", pathName);
399             pathName_[0] = pathName;
400         });
401 
402         fileBack.operate(fileContext); // ------------------------------ OPERATE
403 
404         if (distribute) {
405             final URI baseUri = uriInfo.getBaseUri();
406             logger.trace("uriInfo.baseUri: {}", baseUri);
407             final String path = uriInfo.getPath();
408             logger.trace("uriInfo.path: {}", path);
409             final List<Future<Response>> futures = new ArrayList<>();
410             for (final URI fileFront : fileFronts) {
411                 logger.trace("fileFront: {}", fileFront);
412                 if (baseUri.equals(fileFront)) {
413                     logger.trace("skipping self: " + fileFront);
414                     continue;
415                 }
416                 if (!fileFront.isAbsolute()) {
417                     logger.warn("not an absolute uri: {}", fileFront);
418                     continue;
419                 }
420                 final Client client = ClientBuilder.newClient()
421                     .property(ClientProperties.CONNECT_TIMEOUT, 1000)
422                     .property(ClientProperties.READ_TIMEOUT, 1000);
423                 final WebTarget target = client.target(fileFront).path(path)
424                     .queryParam("distribute", Boolean.FALSE.toString());
425                 logger.trace("target: {}", target.getUri().toString());
426                 try {
427                     final Future<Response> future
428                         = target.request().async().delete();
429                     logger.trace("future: {}", future);
430                     futures.add(future);
431                 } catch (final ProcessingException pe) {
432                     logger.error("failed to distribute to " + fileFront, pe);
433                 }
434             }
435             logger.trace("futures: {}", futures);
436             futures.forEach(future -> {
437                 try {
438                     final Response response = future.get();
439                     logger.trace("response: {}", response);
440                     logger.trace("response.statusInfo: {}",
441                                  response.getStatusInfo());
442                 } catch (InterruptedException | ExecutionException e) {
443                     logger.error("fail to get from future", e);
444                 }
445             });
446         }
447 
448         return Response.noContent()
449             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
450             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
451             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
452             .build();
453     }
454 
455 
456     protected Response readSingle(final FileContext fileContext,
457                                   final String sourceLocator)
458         throws IOException, FileBackException {
459 
460         logger.trace("readSingle({}, {})", fileContext, sourceLocator);
461 
462         fileContext.fileOperationSupplier(() -> FileOperation.READ);
463 
464         fileContext.sourceKeySupplier(() -> key(sourceLocator));
465 
466         final Object[] sourceObject_ = new Object[1];
467         fileContext.sourceObjectConsumer(sourceObject -> {
468             logger.trace("consuming source object: {}", sourceObject);
469             sourceObject_[0] = sourceObject;
470         });
471 
472         final Long[] sourceCopied_ = new Long[1];
473         fileContext.sourceCopiedConsumer(sourceCopied -> {
474             logger.trace("consuming source copied: {}", sourceCopied);
475             sourceCopied_[0] = sourceCopied;
476         });
477 
478         final Object[] targetObject_ = new Object[1];
479         fileContext.targetObjectConsumer(targetObject -> {
480             logger.trace("consuming target object: {}", targetObject);
481             targetObject_[0] = targetObject;
482         });
483 
484         final Long[] targetCopied_ = new Long[1];
485         fileContext.targetCopiedConsumer(targetCopied -> {
486             logger.trace("consuming target copied: {}", targetCopied);
487             targetCopied_[0] = targetCopied;
488         });
489 
490         final String[] pathName_ = new String[1];
491         fileContext.pathNameConsumer(pathName -> {
492             logger.trace("consuming path name: {}", pathName);
493             pathName_[0] = pathName;
494         });
495 
496         fileContext.sourceChannelConsumer(sourceChannel -> {
497             logger.trace("consuming source channel : {}", sourceChannel);
498             try {
499                 final long sourceCopied = Files.copy(
500                     Channels.newInputStream(sourceChannel), tempPath,
501                     StandardCopyOption.REPLACE_EXISTING);
502                 sourceCopied_[0] = sourceCopied;
503             } catch (final IOException ioe) {
504                 final String message
505                     = "failed from source channel to temp path";
506                 logger.error(message, ioe);
507                 throw new WebApplicationException(message, ioe);
508             }
509         });
510 
511         final FileChannel[] targetChannel_ = new FileChannel[1];
512         fileContext.targetChannelSupplier(true ? null : () -> { // _not_usd_!!!
513             try {
514                 targetChannel_[0] = FileChannel.open(
515                     tempPath, StandardOpenOption.CREATE_NEW,
516                     StandardOpenOption.WRITE);
517                 logger.trace("target channel: {}", targetChannel_[0]);
518                 return targetChannel_[0];
519             } catch (final IOException ioe) {
520                 final String message
521                     = "failed to open temp path for writing";
522                 logger.error(message, ioe);
523                 throw new WebApplicationException(message, ioe);
524             }
525         });
526 
527         fileBack.operate(fileContext);
528 
529         if (sourceCopied_[0] == null) {
530             throw new NotFoundException(
531                 "no file for locator: " + sourceLocator);
532         }
533 
534         return Response
535             .ok((StreamingOutput) output -> Files.copy(tempPath, output))
536             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
537             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
538             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
539             .build();
540     }
541 
542 
543     /**
544      *
545      * @param locator the file locator.
546      *
547      * @return a response.
548      *
549      * @throws IOException if an I/O error occurs.
550      * @throws FileBackException if a file back error occurs.
551      */
552     @Produces(MediaType.WILDCARD)
553     @GET
554     @Path("{locator: .+}")
555     public Response readSingle(@PathParam("locator") final String locator)
556         throws IOException, FileBackException {
557 
558         logger.trace("readSingle({})", locator);
559 
560         final FileContext fileContext = new DefaultFileContext();
561 
562         return readSingle(fileContext, locator);
563     }
564 
565 
566     protected Response updateSingle(final FileContext fileContext,
567                                     final String targetLocator,
568                                     final InputStream sourceStream,
569                                     final boolean distributeFlag) {
570 
571         logger.trace("updateSingle({}, {}, {}, {})", fileContext, targetLocator,
572                      sourceStream, distributeFlag);
573 
574         try {
575             Files.copy(sourceStream, tempPath,
576                        StandardCopyOption.REPLACE_EXISTING);
577             logger.trace("source stream copied to temp path");
578         } catch (final IOException ioe) {
579             logger.error("failed to copy source stream to temp path", ioe);
580             throw new WebApplicationException(ioe);
581         }
582 
583         fileContext.fileOperationSupplier(() -> FileOperation.WRITE);
584 
585         fileContext.targetKeySupplier(() -> key(targetLocator));
586 
587         final Object[] sourceObject_ = new Object[1];
588         fileContext.sourceObjectConsumer(
589             ofNullable(fileContext.sourceObjectConsumer()).orElse(
590                 sourceObject -> {
591                     logger.trace("consuming source object: {}", sourceObject);
592                     sourceObject_[0] = sourceObject;
593                 }));
594 
595         final Long[] sourceCopied_ = new Long[1];
596         fileContext.sourceCopiedConsumer(
597             ofNullable(fileContext.sourceCopiedConsumer()).orElse(
598                 sourceCopied -> {
599                     logger.trace("consuming source copied: {}", sourceCopied);
600                     sourceCopied_[0] = sourceCopied;
601                 }));
602 
603         final Object[] targetObject_ = new Object[1];
604         fileContext.targetObjectConsumer(
605             ofNullable(fileContext.targetObjectConsumer()).orElse(
606                 targetObject -> {
607                     logger.trace("consuming target object: {}", targetObject);
608                     targetObject_[0] = targetObject;
609                 }));
610 
611         final Long[] targetCopied_ = new Long[1];
612         fileContext.targetCopiedConsumer(
613             ofNullable(fileContext.targetCopiedConsumer()).orElse(
614                 targetCopied -> {
615                     logger.trace("consuming target copied: {}", targetCopied);
616                     targetCopied_[0] = targetCopied;
617                 }));
618 
619         final String[] pathName_ = new String[1];
620         fileContext.pathNameConsumer(
621             ofNullable(fileContext.pathNameConsumer()).orElse(
622                 pathName -> {
623                     logger.trace("consuming path name: {}", pathName);
624                     pathName_[0] = pathName;
625                 }));
626 
627         fileContext.targetChannelConsumer(targetChannel -> {
628             logger.trace("consuming target channel : {}", targetChannel);
629             try {
630                 final long targetCopied = Files.copy(
631                     tempPath, Channels.newOutputStream(targetChannel));
632                 logger.trace("target copied: {}", targetCopied);
633                 targetCopied_[0] = targetCopied;
634             } catch (final IOException ioe) {
635                 final String message
636                     = "failed to copy from temp path to target channel";
637                 logger.error(message, ioe);
638                 throw new WebApplicationException(message, ioe);
639             }
640         });
641 
642         final FileChannel[] sourceChannel_ = new FileChannel[1];
643         fileContext.sourceChannelSupplier(true ? null : () -> { // _not_usd_!!!
644             try {
645                 sourceChannel_[0] = FileChannel.open(
646                     tempPath, StandardOpenOption.READ);
647                 logger.trace("suppling source channel: {}", sourceChannel_[0]);
648                 return sourceChannel_[0];
649             } catch (final IOException ioe) {
650                 final String message
651                     = "failed to open temp path for reading";
652                 logger.error(message, ioe);
653                 throw new WebApplicationException(message, ioe);
654             }
655         });
656 
657         try {
658             fileBack.operate(fileContext);
659         } catch (IOException | FileBackException e) {
660             final String message = "failed to operate file back";
661             logger.error(message, e);
662             throw new WebApplicationException(message, e);
663         }
664 
665         ofNullable(sourceChannel_[0]).ifPresent(fileChannel -> {
666             try {
667                 fileChannel.close();
668                 logger.trace("file channel closed: {}", fileChannel);
669             } catch (final IOException ioe) {
670                 final String message
671                     = "failed to close file channel: " + fileChannel;
672                 logger.error(message, ioe);
673                 throw new WebApplicationException(message, ioe);
674             }
675         });
676 
677         if (distributeFlag) {
678             logger.trace("distributing...");
679             final URI baseUri = uriInfo.getBaseUri();
680             logger.trace("uriInfo.baseUri: {}", baseUri);
681             final String path = uriInfo.getPath();
682             logger.trace("uriInfo.path: {}", path);
683             final List<Future<Response>> futures = new ArrayList<>();
684             logger.trace("fileFronts: {}", fileFronts);
685             for (final URI fileFront : fileFronts) {
686                 logger.trace("fileFront: {}", fileFront);
687                 if (!fileFront.isAbsolute()) {
688                     logger.warn("not an absolute uri: {}", fileFront);
689                     continue;
690                 }
691                 if (baseUri.equals(fileFront)) {
692                     logger.trace("skipping self: " + fileFront);
693                     continue;
694                 }
695                 final Client client = ClientBuilder.newClient()
696                     .property(ClientProperties.CONNECT_TIMEOUT, 2000)
697                     .property(ClientProperties.READ_TIMEOUT, 2000);
698                 final WebTarget target = client.target(fileFront).path(path)
699                     .queryParam("distribute", Boolean.FALSE.toString());
700                 logger.trace("target: {}", target.getUri().toString());
701 //            try {
702 //                final Response response = target.request().put(
703 //                    Entity.entity(tempPath.toFile(), contentType));
704 //                logger.trace("response: {}", response);
705 //                logger.trace("response.status: {}", response.getStatusInfo());
706 //            } catch (final ProcessingException pe) {
707 //                logger.error("failed to distribute to " + fileFront, pe);
708 //            }
709                 final Future<Response> future = target.request().async().put(
710                     Entity.entity(tempPath.toFile(), contentType));
711                 logger.trace("future: {}", future);
712                 futures.add(future);
713             }
714             logger.trace("futures: {}", futures);
715             futures.forEach(future -> {
716                 try {
717                     final Response response = future.get();
718                     logger.trace("response: {}", response);
719                     logger.trace("response.status: {}", response.getStatus());
720                 } catch (InterruptedException | ExecutionException e) {
721                     logger.error("fail to get response", e);
722                 }
723             });
724         }
725 
726         return Response.noContent()
727             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
728             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
729             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
730             .build();
731     }
732 
733 
734     /**
735      *
736      * @param locator file locator
737      * @param distribute distribute flag
738      * @param entity the entity to update.
739      *
740      * @return a response
741      *
742      * @throws IOException if an I/O error occurs.
743      * @throws FileBackException if a file back error occusr.
744      * @see FileBack#operate(com.github.jinahya.simple.file.back.FileContext)
745      */
746     @Consumes(MediaType.WILDCARD)
747     @PUT
748     @Path("{locator: .+}")
749     public Response updateSingle(
750         @PathParam("locator") final String locator,
751         @QueryParam("distribute") @DefaultValue("true")
752         final boolean distribute,
753         final InputStream entity)
754         throws IOException, FileBackException {
755 
756         logger.trace("updateSingle({}, {}, {})", locator, distribute, entity);
757 
758         if (true) {
759             return updateSingle(new DefaultFileContext(), locator, entity,
760                                 distribute);
761         }
762 
763         try {
764             Files.copy(entity, tempPath, StandardCopyOption.REPLACE_EXISTING);
765             logger.trace("entity copied to temp path");
766         } catch (final IOException ioe) {
767             logger.error("failed to copy entity to temp path", ioe);
768             throw new WebApplicationException(ioe);
769         }
770 
771         final FileContext fileContext = new DefaultFileContext();
772 
773         fileContext.fileOperationSupplier(() -> FileOperation.WRITE);
774 
775         fileContext.targetKeySupplier(
776             () -> ByteBuffer.wrap(locator.getBytes(StandardCharsets.UTF_8)));
777 
778         final Object[] sourceObject_ = new Object[1];
779         fileContext.sourceObjectConsumer(sourceObject -> {
780             logger.trace("source object: {}", sourceObject);
781             sourceObject_[0] = sourceObject;
782         });
783 
784         final Long[] sourceCopied_ = new Long[1];
785         fileContext.sourceCopiedConsumer(sourceCopied -> {
786             logger.trace("source copied: {}", sourceCopied);
787             sourceCopied_[0] = sourceCopied;
788         });
789 
790         final Object[] targetObject_ = new Object[1];
791         fileContext.targetObjectConsumer(targetObject -> {
792             logger.trace("target object: {}", targetObject);
793             targetObject_[0] = targetObject;
794         });
795 
796         final Long[] targetCopied_ = new Long[1];
797         fileContext.targetCopiedConsumer(targetCopied -> {
798             logger.trace("target copied: {}", targetCopied);
799             targetCopied_[0] = targetCopied;
800         });
801 
802         final String[] pathName_ = new String[1];
803         fileContext.pathNameConsumer(pathName -> {
804             logger.trace("path name: {}", pathName);
805             pathName_[0] = pathName;
806         });
807 
808         fileContext.targetChannelConsumer(targetChannel -> {
809             logger.trace("target channel : {}", targetChannel);
810             try {
811                 final long targetCopied = Files.copy(
812                     tempPath, Channels.newOutputStream(targetChannel));
813                 logger.trace("target copied: {}", targetCopied);
814                 targetCopied_[0] = targetCopied;
815             } catch (final IOException ioe) {
816                 final String message
817                     = "failed to copy from temp path to target channel";
818                 logger.error(message, ioe);
819                 throw new WebApplicationException(message, ioe);
820             }
821         });
822 
823         final FileChannel[] sourceChannel_ = new FileChannel[1];
824         fileContext.sourceChannelSupplier(true ? null : () -> { // _not_usd_!!!
825             try {
826                 sourceChannel_[0] = FileChannel.open(
827                     tempPath, StandardOpenOption.READ);
828                 logger.trace("source channel: {}", sourceChannel_[0]);
829                 return sourceChannel_[0];
830             } catch (final IOException ioe) {
831                 final String message
832                     = "failed to open temp path for reading";
833                 logger.error(message, ioe);
834                 throw new WebApplicationException(message, ioe);
835             }
836         });
837 
838         fileBack.operate(fileContext);
839 
840         // @todo: check fielback outputs
841 
842         if (distribute) {
843             logger.trace("distributing...");
844             final URI baseUri = uriInfo.getBaseUri();
845             logger.trace("uriInfo.baseUri: {}", baseUri);
846             final String path = uriInfo.getPath();
847             logger.trace("uriInfo.path: {}", path);
848             final List<Future<Response>> futures = new ArrayList<>();
849             logger.trace("fileFronts: {}", fileFronts);
850             for (final URI fileFront : fileFronts) {
851                 logger.trace("fileFront: {}", fileFront);
852                 if (!fileFront.isAbsolute()) {
853                     logger.warn("not an absolute uri: {}", fileFront);
854                     continue;
855                 }
856                 if (baseUri.equals(fileFront)) {
857                     logger.trace("skipping self: " + fileFront);
858                     continue;
859                 }
860                 final Client client = ClientBuilder.newClient()
861                     .property(ClientProperties.CONNECT_TIMEOUT, 2000)
862                     .property(ClientProperties.READ_TIMEOUT, 2000);
863                 final WebTarget target = client.target(fileFront).path(path)
864                     .queryParam("distribute", Boolean.FALSE.toString());
865                 logger.trace("target: {}", target.getUri().toString());
866 //            try {
867 //                final Response response = target.request().put(
868 //                    Entity.entity(tempPath.toFile(), contentType));
869 //                logger.trace("response: {}", response);
870 //                logger.trace("response.status: {}", response.getStatusInfo());
871 //            } catch (final ProcessingException pe) {
872 //                logger.error("failed to distribute to " + fileFront, pe);
873 //            }
874                 final Future<Response> future = target.request().async().put(
875                     Entity.entity(tempPath.toFile(), contentType));
876                 logger.trace("future: {]", future);
877                 futures.add(future);
878             }
879             logger.trace("futures: {}", futures);
880             futures.forEach(future -> {
881                 try {
882                     final Response response = future.get();
883                     logger.trace("response: {}", response);
884                     logger.trace("response.status: {}", response.getStatus());
885                 } catch (InterruptedException | ExecutionException e) {
886                     logger.error("fail to get response", e);
887                 } catch (final Exception e) {
888                     logger.error("unhandled", e);
889                 }
890             });
891         }
892 
893         return Response.noContent()
894             .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
895             .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
896             .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
897             .build();
898     }
899 
900 
901 //    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
902 //    @POST
903 //    @Path("/urlencoded/update")
904 //    public Response updateSingleUrlencoded(
905 //        @FormParam("locator") final String locator,
906 //        @FormParam("distribute") @DefaultValue("true") final boolean distribute,
907 //        @FormParam("entity") final InputStream entity)
908 //        throws IOException, FileBackException {
909 //
910 //        logger.trace("updateSingleUrlencoded({}, {}, {})", locator, distribute,
911 //                     entity);
912 //
913 //        if (true) {
914 //            return updateSingle(new DefaultFileContext(), locator, entity,
915 //                                distribute);
916 //        }
917 //
918 //        try {
919 //            Files.copy(entity, tempPath, StandardCopyOption.REPLACE_EXISTING);
920 //            logger.trace("entity copied to temp path");
921 //        } catch (final IOException ioe) {
922 //            logger.error("failed to copy entity to temp path", ioe);
923 //            throw new WebApplicationException(ioe);
924 //        }
925 //
926 //        final FileContext fileContext = new DefaultFileContext();
927 //
928 //        fileContext.fileOperationSupplier(() -> FileOperation.WRITE);
929 //
930 //        fileContext.targetKeySupplier(
931 //            () -> ByteBuffer.wrap(locator.getBytes(StandardCharsets.UTF_8)));
932 //
933 //        final Object[] sourceObject_ = new Object[1];
934 //        fileContext.sourceObjectConsumer(sourceObject -> {
935 //            logger.trace("source object: {}", sourceObject);
936 //            sourceObject_[0] = sourceObject;
937 //        });
938 //
939 //        final Long[] sourceCopied_ = new Long[1];
940 //        fileContext.sourceCopiedConsumer(sourceCopied -> {
941 //            logger.trace("source copied: {}", sourceCopied);
942 //            sourceCopied_[0] = sourceCopied;
943 //        });
944 //
945 //        final Object[] targetObject_ = new Object[1];
946 //        fileContext.targetObjectConsumer(targetObject -> {
947 //            logger.trace("target object: {}", targetObject);
948 //            targetObject_[0] = targetObject;
949 //        });
950 //
951 //        final Long[] targetCopied_ = new Long[1];
952 //        fileContext.targetCopiedConsumer(targetCopied -> {
953 //            logger.trace("target copied: {}", targetCopied);
954 //            targetCopied_[0] = targetCopied;
955 //        });
956 //
957 //        final String[] pathName_ = new String[1];
958 //        fileContext.pathNameConsumer(pathName -> {
959 //            logger.trace("path name: {}", pathName);
960 //            pathName_[0] = pathName;
961 //        });
962 //
963 //        fileContext.targetChannelConsumer(targetChannel -> {
964 //            logger.trace("target channel : {}", targetChannel);
965 //            try {
966 //                final long targetCopied = Files.copy(
967 //                    tempPath, Channels.newOutputStream(targetChannel));
968 //                logger.trace("target copied: {}", targetCopied);
969 //                targetCopied_[0] = targetCopied;
970 //            } catch (final IOException ioe) {
971 //                final String message
972 //                    = "failed to copy from temp path to target channel";
973 //                logger.error(message, ioe);
974 //                throw new WebApplicationException(message, ioe);
975 //            }
976 //        });
977 //
978 //        final FileChannel[] sourceChannel_ = new FileChannel[1];
979 //        fileContext.sourceChannelSupplier(true ? null : () -> { // _not_usd_!!!
980 //            try {
981 //                sourceChannel_[0] = FileChannel.open(
982 //                    tempPath, StandardOpenOption.READ);
983 //                logger.trace("source channel: {}", sourceChannel_[0]);
984 //                return sourceChannel_[0];
985 //            } catch (final IOException ioe) {
986 //                final String message
987 //                    = "failed to open temp path for reading";
988 //                logger.error(message, ioe);
989 //                throw new WebApplicationException(message, ioe);
990 //            }
991 //        });
992 //
993 //        fileBack.operate(fileContext);
994 //
995 //        // @todo: check fielback outputs
996 //
997 //        if (distribute) {
998 //            logger.trace("distributing...");
999 //            final URI baseUri = uriInfo.getBaseUri();
1000 //            logger.trace("uriInfo.baseUri: {}", baseUri);
1001 //            final String path = uriInfo.getPath();
1002 //            logger.trace("uriInfo.path: {}", path);
1003 //            final List<Future<Response>> futures = new ArrayList<>();
1004 //            logger.trace("fileFronts: {}", fileFronts);
1005 //            for (final URI fileFront : fileFronts) {
1006 //                logger.trace("fileFront: {}", fileFront);
1007 //                if (!fileFront.isAbsolute()) {
1008 //                    logger.warn("not an absolute uri: {}", fileFront);
1009 //                    continue;
1010 //                }
1011 //                if (baseUri.equals(fileFront)) {
1012 //                    logger.trace("skipping self: " + fileFront);
1013 //                    continue;
1014 //                }
1015 //                final Client client = ClientBuilder.newClient()
1016 //                    .property(ClientProperties.CONNECT_TIMEOUT, 2000)
1017 //                    .property(ClientProperties.READ_TIMEOUT, 2000);
1018 //                final WebTarget target = client.target(fileFront).path(path)
1019 //                    .queryParam("distribute", Boolean.FALSE.toString());
1020 //                logger.trace("target: {}", target.getUri().toString());
1021 ////            try {
1022 ////                final Response response = target.request().put(
1023 ////                    Entity.entity(tempPath.toFile(), contentType));
1024 ////                logger.trace("response: {}", response);
1025 ////                logger.trace("response.status: {}", response.getStatusInfo());
1026 ////            } catch (final ProcessingException pe) {
1027 ////                logger.error("failed to distribute to " + fileFront, pe);
1028 ////            }
1029 //                final Future<Response> future = target.request().async().put(
1030 //                    Entity.entity(tempPath.toFile(), contentType));
1031 //                logger.trace("future: {]", future);
1032 //                futures.add(future);
1033 //            }
1034 //            logger.trace("futures: {}", futures);
1035 //            futures.forEach(future -> {
1036 //                try {
1037 //                    final Response response = future.get();
1038 //                    logger.trace("response: {}", response);
1039 //                    logger.trace("response.status: {}", response.getStatus());
1040 //                } catch (InterruptedException | ExecutionException e) {
1041 //                    logger.error("fail to get response", e);
1042 //                } catch (final Exception e) {
1043 //                    logger.error("unhandled", e);
1044 //                }
1045 //            });
1046 //        }
1047 //
1048 //        return Response.noContent()
1049 //            .header(FileFrontConstants.HEADER_PATH_NAME, pathName_[0])
1050 //            .header(FileFrontConstants.HEADER_SOURCE_COPIED, sourceCopied_[0])
1051 //            .header(FileFrontConstants.HEADER_TARGET_COPIED, targetCopied_[0])
1052 //            .build();
1053 //    }
1054     /**
1055      * Returns the injected backing.
1056      *
1057      * @return the injected backing.
1058      */
1059     protected FileBack getFileBack() {
1060 
1061         return fileBack;
1062     }
1063 
1064 
1065     /**
1066      * Returns the injected siblings.
1067      *
1068      * @return the injected siblings.
1069      */
1070     protected List<URI> getFileFronts() {
1071 
1072         return fileFronts;
1073     }
1074 
1075 
1076     private transient final Logger logger = getLogger(lookup().lookupClass());
1077 
1078 
1079     private transient java.nio.file.Path tempPath;
1080 
1081 
1082     /**
1083      * A file back injected.
1084      */
1085     @Inject
1086     @Backing
1087     private FileBack fileBack;
1088 
1089 
1090     /**
1091      * A list of URIs to distribute files and commands.
1092      */
1093     @Inject
1094     @Siblings
1095     private List<URI> fileFronts;
1096 
1097 
1098     @Context
1099     private UriInfo uriInfo;
1100 
1101 
1102     @HeaderParam("Content-Type")
1103     private MediaType contentType = MediaType.WILDCARD_TYPE;
1104 
1105 
1106     @HeaderParam("Accept")
1107     private String accept;
1108 
1109 
1110 }
1111