1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.provider;
18
19 import java.io.BufferedInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.security.cert.Certificate;
24 import java.util.Collections;
25 import java.util.Map;
26 import java.util.Set;
27
28 import org.apache.commons.lang3.ArrayUtils;
29 import org.apache.commons.vfs2.FileContent;
30 import org.apache.commons.vfs2.FileContentInfo;
31 import org.apache.commons.vfs2.FileContentInfoFactory;
32 import org.apache.commons.vfs2.FileObject;
33 import org.apache.commons.vfs2.FileSystemException;
34 import org.apache.commons.vfs2.RandomAccessContent;
35 import org.apache.commons.vfs2.util.MonitorInputStream;
36 import org.apache.commons.vfs2.util.MonitorOutputStream;
37 import org.apache.commons.vfs2.util.MonitorRandomAccessContent;
38 import org.apache.commons.vfs2.util.RandomAccessMode;
39 import org.apache.commons.vfs2.util.RawMonitorInputStream;
40
41
42
43
44 public final class DefaultFileContent implements FileContent {
45
46
47
48
49
50
51 private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = new Certificate[0];
52 static final int STATE_CLOSED = 0;
53 static final int STATE_OPENED = 1;
54
55
56
57
58 private static final int WRITE_BUFFER_SIZE = 4096;
59
60 private final AbstractFileObject<?> fileObject;
61 private Map<String, Object> attrs;
62 private Map<String, Object> roAttrs;
63 private FileContentInfo fileContentInfo;
64 private final FileContentInfoFactory fileContentInfoFactory;
65
66 private final ThreadLocal<FileContentThreadData> threadLocal = ThreadLocal.withInitial(FileContentThreadData::new);
67 private boolean resetAttributes;
68
69
70
71
72 private int openStreams;
73
74 public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory) {
75 this.fileObject = file;
76 this.fileContentInfoFactory = fileContentInfoFactory;
77 }
78
79 private FileContentThreadData getFileContentThreadData() {
80 return this.threadLocal.get();
81 }
82
83 void streamOpened() {
84 synchronized (this) {
85 openStreams++;
86 }
87 ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened();
88 }
89
90 void streamClosed() {
91 synchronized (this) {
92 if (openStreams > 0) {
93 openStreams--;
94 if (openStreams < 1) {
95 fileObject.notifyAllStreamsClosed();
96 }
97 }
98 }
99 ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed();
100 }
101
102
103
104
105
106
107 @Override
108 public FileObject getFile() {
109 return fileObject;
110 }
111
112
113
114
115
116
117
118 @Override
119 public long getSize() throws FileSystemException {
120
121 if (!fileObject.getType().hasContent()) {
122 throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject);
123 }
124
125
126
127
128
129 try {
130
131 return fileObject.doGetContentSize();
132 } catch (final Exception exc) {
133 throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject);
134 }
135 }
136
137
138
139
140
141
142
143 @Override
144 public long getLastModifiedTime() throws FileSystemException {
145
146
147
148
149 if (!fileObject.getType().hasAttributes()) {
150 throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject);
151 }
152 try {
153 return fileObject.doGetLastModifiedTime();
154 } catch (final Exception e) {
155 throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e);
156 }
157 }
158
159
160
161
162
163
164
165 @Override
166 public void setLastModifiedTime(final long modTime) throws FileSystemException {
167
168
169
170
171 if (!fileObject.getType().hasAttributes()) {
172 throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject);
173 }
174 try {
175 if (!fileObject.doSetLastModifiedTime(modTime)) {
176 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject);
177 }
178 } catch (final Exception e) {
179 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e);
180 }
181 }
182
183
184
185
186
187
188
189
190
191 @Override
192 public boolean hasAttribute(final String attrName) throws FileSystemException {
193 if (!fileObject.getType().hasAttributes()) {
194 throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject);
195 }
196 getAttributes();
197 return attrs.containsKey(attrName);
198 }
199
200
201
202
203
204
205
206 @Override
207 public Map<String, Object> getAttributes() throws FileSystemException {
208 if (!fileObject.getType().hasAttributes()) {
209 throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject);
210 }
211 if (resetAttributes || roAttrs == null) {
212 try {
213 synchronized (this) {
214 attrs = fileObject.doGetAttributes();
215 roAttrs = Collections.unmodifiableMap(attrs);
216 resetAttributes = false;
217 }
218 } catch (final Exception e) {
219 throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e);
220 }
221 }
222 return roAttrs;
223 }
224
225
226
227
228
229
230 public void resetAttributes() {
231 resetAttributes = true;
232 }
233
234
235
236
237
238
239
240 @Override
241 public String[] getAttributeNames() throws FileSystemException {
242 getAttributes();
243 final Set<String> names = attrs.keySet();
244 return names.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
245 }
246
247
248
249
250
251
252
253
254 @Override
255 public Object getAttribute(final String attrName) throws FileSystemException {
256 getAttributes();
257 return attrs.get(attrName);
258 }
259
260
261
262
263
264
265
266
267 @Override
268 public void setAttribute(final String attrName, final Object value) throws FileSystemException {
269 if (!fileObject.getType().hasAttributes()) {
270 throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject);
271 }
272 try {
273 fileObject.doSetAttribute(attrName, value);
274 } catch (final Exception e) {
275 throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject);
276 }
277
278 if (attrs != null) {
279 attrs.put(attrName, value);
280 }
281 }
282
283
284
285
286
287
288
289
290 @Override
291 public void removeAttribute(final String attrName) throws FileSystemException {
292 if (!fileObject.getType().hasAttributes()) {
293 throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject);
294 }
295
296 try {
297 fileObject.doRemoveAttribute(attrName);
298 } catch (final Exception e) {
299 throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject);
300 }
301
302 if (attrs != null) {
303 attrs.remove(attrName);
304 }
305 }
306
307
308
309
310
311
312
313 @Override
314 public Certificate[] getCertificates() throws FileSystemException {
315 if (!fileObject.exists()) {
316 throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject);
317 }
318
319
320
321
322
323 try {
324 final Certificate[] certs = fileObject.doGetCertificates();
325 if (certs != null) {
326 return certs;
327 }
328 return EMPTY_CERTIFICATE_ARRAY;
329 } catch (final Exception e) {
330 throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e);
331 }
332 }
333
334
335
336
337
338
339
340 @Override
341 public InputStream getInputStream() throws FileSystemException {
342 return buildInputStream(0);
343 }
344
345
346
347
348
349
350
351
352
353 @Override
354 public InputStream getInputStream(final int bufferSize) throws FileSystemException {
355 return buildInputStream(bufferSize);
356 }
357
358
359
360
361
362
363
364
365 @Override
366 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
367
368
369
370
371
372
373 final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode);
374
375 final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr);
376
377 getFileContentThreadData().add(rac);
378 streamOpened();
379
380 return rac;
381 }
382
383
384
385
386
387
388
389 @Override
390 public OutputStream getOutputStream() throws FileSystemException {
391 return getOutputStream(false);
392 }
393
394
395
396
397
398
399
400
401 @Override
402 public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException {
403 return buildOutputStream(bAppend, 0);
404 }
405
406
407
408
409
410
411
412
413
414 @Override
415 public OutputStream getOutputStream(final int bufferSize) throws FileSystemException {
416 return buildOutputStream(false, bufferSize);
417 }
418
419
420
421
422
423
424
425
426
427
428 @Override
429 public OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
430 return buildOutputStream(bAppend, bufferSize);
431 }
432
433
434
435
436
437
438 @Override
439 public void close() throws FileSystemException {
440 FileSystemException caught = null;
441 try {
442 final FileContentThreadData threadData = getFileContentThreadData();
443
444
445 while (threadData.hasInputStream()) {
446 final InputStream inputStream = threadData.removeInputStream(0);
447 try {
448 if (inputStream instanceof FileContentInputStream) {
449 ((FileContentInputStream) inputStream).close();
450 } else if (inputStream instanceof RawFileContentInputStream) {
451 ((RawFileContentInputStream) inputStream).close();
452 } else {
453 caught = new FileSystemException("Unsupported InputStream type: " + inputStream);
454 }
455 } catch (final FileSystemException ex) {
456 caught = ex;
457
458 }
459 }
460
461
462 while (threadData.hasRandomAccessContent()) {
463 final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) threadData
464 .removeRandomAccessContent(0);
465 try {
466 randomAccessContent.close();
467 } catch (final FileSystemException ex) {
468 caught = ex;
469 }
470 }
471
472
473 final FileContentOutputStream outputStream = threadData.getOutputStream();
474 if (outputStream != null) {
475 threadData.setOutputStream(null);
476 try {
477 outputStream.close();
478 } catch (final FileSystemException ex) {
479 caught = ex;
480 }
481 }
482 } finally {
483 threadLocal.remove();
484 }
485
486
487 if (caught != null) {
488 throw caught;
489 }
490 }
491
492 private InputStream buildInputStream(final int bufferSize) throws FileSystemException {
493
494
495
496
497
498
499 final InputStream inputStream = bufferSize == 0
500 ? fileObject.getInputStream()
501 : fileObject.getInputStream(bufferSize);
502
503
504
505
506
507
508 final InputStream wrappedInputStream;
509 if (inputStream instanceof BufferedInputStream) {
510
511 wrappedInputStream = new RawFileContentInputStream(fileObject, inputStream);
512 } else
513 {
514
515 wrappedInputStream = bufferSize == 0
516 ? new FileContentInputStream(fileObject, inputStream)
517 : new FileContentInputStream(fileObject, inputStream, bufferSize);
518
519 }
520 getFileContentThreadData().add(wrappedInputStream);
521 streamOpened();
522
523 return wrappedInputStream;
524 }
525
526 private OutputStream buildOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
527
528
529
530 final FileContentThreadData threadData = getFileContentThreadData();
531
532 if (threadData.getOutputStream() != null) {
533 throw new FileSystemException("vfs.provider/write-in-use.error", fileObject);
534 }
535
536
537 final OutputStream outstr = fileObject.getOutputStream(bAppend);
538
539
540 final FileContentOutputStream wrapped = bufferSize == 0 ?
541 new FileContentOutputStream(fileObject, outstr) :
542 new FileContentOutputStream(fileObject, outstr, bufferSize);
543 threadData.setOutputStream(wrapped);
544 streamOpened();
545
546 return wrapped;
547 }
548
549
550
551
552 private void endInput(final InputStream instr) {
553 final FileContentThreadData fileContentThreadData = threadLocal.get();
554 if (fileContentThreadData != null) {
555 fileContentThreadData.remove(instr);
556 }
557 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
558
559 threadLocal.remove();
560 }
561 streamClosed();
562 }
563
564
565
566
567 private void endRandomAccess(final RandomAccessContent rac) {
568 final FileContentThreadData fileContentThreadData = threadLocal.get();
569 if (fileContentThreadData != null) {
570 fileContentThreadData.remove(rac);
571 }
572 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
573
574 threadLocal.remove();
575 }
576 streamClosed();
577 }
578
579
580
581
582 private void endOutput() throws Exception {
583 final FileContentThreadData fileContentThreadData = threadLocal.get();
584 if (fileContentThreadData != null) {
585 fileContentThreadData.setOutputStream(null);
586 }
587 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
588
589 threadLocal.remove();
590 }
591 streamClosed();
592 fileObject.endOutput();
593 }
594
595
596
597
598
599
600
601
602
603 @Override
604 public boolean isOpen() {
605 final FileContentThreadData fileContentThreadData = threadLocal.get();
606 if (fileContentThreadData != null && fileContentThreadData.hasStreams()) {
607 return true;
608 }
609
610 threadLocal.remove();
611 return false;
612 }
613
614
615
616
617
618
619 public boolean isOpenGlobal() {
620 synchronized (this) {
621 return openStreams > 0;
622 }
623 }
624
625
626
627
628 private final class FileContentInputStream extends MonitorInputStream {
629
630 private final FileObject file;
631
632 FileContentInputStream(final FileObject file, final InputStream instr) {
633 super(instr);
634 this.file = file;
635 }
636
637 FileContentInputStream(final FileObject file, final InputStream instr, final int bufferSize) {
638 super(instr, bufferSize);
639 this.file = file;
640 }
641
642
643
644
645 @Override
646 public void close() throws FileSystemException {
647 try {
648 super.close();
649 } catch (final IOException e) {
650 throw new FileSystemException("vfs.provider/close-instr.error", file, e);
651 }
652 }
653
654
655
656
657 @Override
658 protected void onClose() throws IOException {
659 try {
660 super.onClose();
661 } finally {
662 endInput(this);
663 }
664 }
665 }
666
667
668
669
670
671
672
673 private final class RawFileContentInputStream extends RawMonitorInputStream {
674
675 private final FileObject file;
676
677 RawFileContentInputStream(final FileObject file, final InputStream instr) {
678 super(instr);
679 this.file = file;
680 }
681
682
683
684
685 @Override
686 public void close() throws FileSystemException {
687 try {
688 super.close();
689 } catch (final IOException e) {
690 throw new FileSystemException("vfs.provider/close-instr.error", file, e);
691 }
692 }
693
694
695
696
697 @Override
698 protected void onClose() throws IOException {
699 try {
700 super.onClose();
701 } finally {
702 endInput(this);
703 }
704 }
705 }
706
707
708
709
710 private final class FileRandomAccessContent extends MonitorRandomAccessContent {
711
712 private final FileObject file;
713
714 FileRandomAccessContent(final FileObject file, final RandomAccessContent content) {
715 super(content);
716 this.file = file;
717 }
718
719
720
721
722 @Override
723 protected void onClose() throws IOException {
724 try {
725 super.onClose();
726 } finally {
727 endRandomAccess(this);
728 }
729 }
730
731 @Override
732 public void close() throws FileSystemException {
733 try {
734 super.close();
735 } catch (final IOException e) {
736 throw new FileSystemException("vfs.provider/close-rac.error", file, e);
737 }
738 }
739 }
740
741
742
743
744 final class FileContentOutputStream extends MonitorOutputStream {
745
746 private final FileObject file;
747
748 FileContentOutputStream(final FileObject file, final OutputStream outstr) {
749 super(outstr);
750 this.file = file;
751 }
752
753 FileContentOutputStream(final FileObject file, final OutputStream outstr, final int bufferSize) {
754 super(outstr, bufferSize);
755 this.file = file;
756 }
757
758
759
760
761 @Override
762 public void close() throws FileSystemException {
763 try {
764 super.close();
765 } catch (final IOException e) {
766 throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
767 }
768 }
769
770
771
772
773 @Override
774 protected void onClose() throws IOException {
775 try {
776 super.onClose();
777 } finally {
778 try {
779 endOutput();
780 } catch (final Exception e) {
781 throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
782 }
783 }
784 }
785 }
786
787
788
789
790
791
792
793 @Override
794 public FileContentInfo getContentInfo() throws FileSystemException {
795 if (fileContentInfo == null) {
796 fileContentInfo = fileContentInfoFactory.create(this);
797 }
798
799 return fileContentInfo;
800 }
801
802
803
804
805
806
807
808
809
810 @Override
811 public long write(final FileContent fileContent) throws IOException {
812 try (OutputStream output = fileContent.getOutputStream()) {
813 return this.write(output);
814 }
815 }
816
817
818
819
820
821
822
823
824
825 @Override
826 public long write(final FileObject file) throws IOException {
827 return write(file.getContent());
828 }
829
830
831
832
833
834
835
836
837
838 @Override
839 public long write(final OutputStream output) throws IOException {
840 return write(output, WRITE_BUFFER_SIZE);
841 }
842
843
844
845
846
847
848
849
850
851
852 @Override
853 public long write(final OutputStream output, final int bufferSize) throws IOException {
854 final InputStream input = this.getInputStream();
855 long count = 0;
856 try {
857
858 final byte[] buffer = new byte[bufferSize];
859 int n;
860 while (-1 != (n = input.read(buffer))) {
861 output.write(buffer, 0, n);
862 count += n;
863 }
864 } finally {
865 input.close();
866 }
867 return count;
868 }
869 }