001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.security.cert.Certificate; 023import java.util.Collections; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.commons.vfs2.FileContent; 028import org.apache.commons.vfs2.FileContentInfo; 029import org.apache.commons.vfs2.FileContentInfoFactory; 030import org.apache.commons.vfs2.FileObject; 031import org.apache.commons.vfs2.FileSystemException; 032import org.apache.commons.vfs2.RandomAccessContent; 033import org.apache.commons.vfs2.util.MonitorInputStream; 034import org.apache.commons.vfs2.util.MonitorOutputStream; 035import org.apache.commons.vfs2.util.MonitorRandomAccessContent; 036import org.apache.commons.vfs2.util.RandomAccessMode; 037 038/** 039 * The content of a file. 040 */ 041public final class DefaultFileContent implements FileContent { 042 043 /* 044 * static final int STATE_NONE = 0; static final int STATE_READING = 1; static final int STATE_WRITING = 2; static 045 * final int STATE_RANDOM_ACCESS = 3; 046 */ 047 048 static final int STATE_CLOSED = 0; 049 static final int STATE_OPENED = 1; 050 051 /** 052 * The default buffer size for {@link #write(OutputStream)} 053 */ 054 private static final int WRITE_BUFFER_SIZE = 4096; 055 056 private final AbstractFileObject fileObject; 057 private Map<String, Object> attrs; 058 private Map<String, Object> roAttrs; 059 private FileContentInfo fileContentInfo; 060 private final FileContentInfoFactory fileContentInfoFactory; 061 062 private final ThreadLocal<FileContentThreadData> threadLocal = new ThreadLocal<>(); 063 private boolean resetAttributes; 064 065 /** 066 * Counts open streams for this file. 067 */ 068 private int openStreams; 069 070 public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory) { 071 this.fileObject = file; 072 this.fileContentInfoFactory = fileContentInfoFactory; 073 } 074 075 private FileContentThreadData getOrCreateThreadData() { 076 FileContentThreadData data = this.threadLocal.get(); 077 if (data == null) { 078 data = new FileContentThreadData(); 079 this.threadLocal.set(data); 080 } 081 return data; 082 } 083 084 void streamOpened() { 085 synchronized (this) { 086 openStreams++; 087 } 088 ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened(); 089 } 090 091 void streamClosed() { 092 synchronized (this) { 093 if (openStreams > 0) { 094 openStreams--; 095 if (openStreams < 1) { 096 fileObject.notifyAllStreamsClosed(); 097 } 098 } 099 } 100 ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed(); 101 } 102 103 /** 104 * Returns the file that this is the content of. 105 * 106 * @return the FileObject. 107 */ 108 @Override 109 public FileObject getFile() { 110 return fileObject; 111 } 112 113 /** 114 * Returns the size of the content (in bytes). 115 * 116 * @return The size of the content (in bytes). 117 * @throws FileSystemException if an error occurs. 118 */ 119 @Override 120 public long getSize() throws FileSystemException { 121 // Do some checking 122 if (!fileObject.getType().hasContent()) { 123 throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject); 124 } 125 /* 126 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 127 * new FileSystemException("vfs.provider/get-size-write.error", file); } 128 */ 129 130 try { 131 // Get the size 132 return fileObject.doGetContentSize(); 133 } catch (final Exception exc) { 134 throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject); 135 } 136 } 137 138 /** 139 * Returns the last-modified timestamp. 140 * 141 * @return The last modified timestamp. 142 * @throws FileSystemException if an error occurs. 143 */ 144 @Override 145 public long getLastModifiedTime() throws FileSystemException { 146 /* 147 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 148 * new FileSystemException("vfs.provider/get-last-modified-writing.error", file); } 149 */ 150 if (!fileObject.getType().hasAttributes()) { 151 throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject); 152 } 153 try { 154 return fileObject.doGetLastModifiedTime(); 155 } catch (final Exception e) { 156 throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e); 157 } 158 } 159 160 /** 161 * Sets the last-modified timestamp. 162 * 163 * @param modTime The last modified timestamp. 164 * @throws FileSystemException if an error occurs. 165 */ 166 @Override 167 public void setLastModifiedTime(final long modTime) throws FileSystemException { 168 /* 169 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 170 * new FileSystemException("vfs.provider/set-last-modified-writing.error", file); } 171 */ 172 if (!fileObject.getType().hasAttributes()) { 173 throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject); 174 } 175 try { 176 if (!fileObject.doSetLastModifiedTime(modTime)) { 177 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject); 178 } 179 } catch (final Exception e) { 180 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e); 181 } 182 } 183 184 /** 185 * Checks if an attribute exists. 186 * 187 * @param attrName The name of the attribute to check. 188 * @return true if the attribute is associated with the file. 189 * @throws FileSystemException if an error occurs. 190 * @since 2.0 191 */ 192 @Override 193 public boolean hasAttribute(final String attrName) throws FileSystemException { 194 if (!fileObject.getType().hasAttributes()) { 195 throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject); 196 } 197 getAttributes(); 198 return attrs.containsKey(attrName); 199 } 200 201 /** 202 * Returns a read-only map of this file's attributes. 203 * 204 * @return a Map of the file's attributes. 205 * @throws FileSystemException if an error occurs. 206 */ 207 @Override 208 public Map<String, Object> getAttributes() throws FileSystemException { 209 if (!fileObject.getType().hasAttributes()) { 210 throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject); 211 } 212 if (resetAttributes || roAttrs == null) { 213 try { 214 synchronized (this) { 215 attrs = fileObject.doGetAttributes(); 216 roAttrs = Collections.unmodifiableMap(attrs); 217 resetAttributes = false; 218 } 219 } catch (final Exception e) { 220 throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e); 221 } 222 } 223 return roAttrs; 224 } 225 226 /** 227 * Used internally to flag situations where the file attributes should be reretrieved. 228 * 229 * @since 2.0 230 */ 231 public void resetAttributes() { 232 resetAttributes = true; 233 } 234 235 /** 236 * Lists the attributes of this file. 237 * 238 * @return An array of attribute names. 239 * @throws FileSystemException if an error occurs. 240 */ 241 @Override 242 public String[] getAttributeNames() throws FileSystemException { 243 getAttributes(); 244 final Set<String> names = attrs.keySet(); 245 return names.toArray(new String[names.size()]); 246 } 247 248 /** 249 * Gets the value of an attribute. 250 * 251 * @param attrName The attribute name. 252 * @return The value of the attribute or null. 253 * @throws FileSystemException if an error occurs. 254 */ 255 @Override 256 public Object getAttribute(final String attrName) throws FileSystemException { 257 getAttributes(); 258 return attrs.get(attrName); 259 } 260 261 /** 262 * Sets the value of an attribute. 263 * 264 * @param attrName The name of the attribute to add. 265 * @param value The value of the attribute. 266 * @throws FileSystemException if an error occurs. 267 */ 268 @Override 269 public void setAttribute(final String attrName, final Object value) throws FileSystemException { 270 if (!fileObject.getType().hasAttributes()) { 271 throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject); 272 } 273 try { 274 fileObject.doSetAttribute(attrName, value); 275 } catch (final Exception e) { 276 throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject); 277 } 278 279 if (attrs != null) { 280 attrs.put(attrName, value); 281 } 282 } 283 284 /** 285 * Removes an attribute. 286 * 287 * @param attrName The name of the attribute to remove. 288 * @throws FileSystemException if an error occurs. 289 * @since 2.0 290 */ 291 @Override 292 public void removeAttribute(final String attrName) throws FileSystemException { 293 if (!fileObject.getType().hasAttributes()) { 294 throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject); 295 } 296 297 try { 298 fileObject.doRemoveAttribute(attrName); 299 } catch (final Exception e) { 300 throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject); 301 } 302 303 if (attrs != null) { 304 attrs.remove(attrName); 305 } 306 } 307 308 /** 309 * Returns the certificates used to sign this file. 310 * 311 * @return An array of Certificates. 312 * @throws FileSystemException if an error occurs. 313 */ 314 @Override 315 public Certificate[] getCertificates() throws FileSystemException { 316 if (!fileObject.exists()) { 317 throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject); 318 } 319 /* 320 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 321 * new FileSystemException("vfs.provider/get-certificates-writing.error", file); } 322 */ 323 324 try { 325 final Certificate[] certs = fileObject.doGetCertificates(); 326 if (certs != null) { 327 return certs; 328 } 329 return new Certificate[0]; 330 } catch (final Exception e) { 331 throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e); 332 } 333 } 334 335 /** 336 * Returns an input stream for reading the content. 337 * 338 * @return The InputStream 339 * @throws FileSystemException if an error occurs. 340 */ 341 @Override 342 public InputStream getInputStream() throws FileSystemException { 343 /* 344 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 345 * new FileSystemException("vfs.provider/read-in-use.error", file); } 346 */ 347 348 // Get the raw input stream 349 final InputStream inputStream = fileObject.getInputStream(); 350 351 final InputStream wrappedInputStream = new FileContentInputStream(fileObject, inputStream); 352 353 getOrCreateThreadData().addInstr(wrappedInputStream); 354 streamOpened(); 355 356 return wrappedInputStream; 357 } 358 359 /** 360 * Returns an input/output stream to use to read and write the content of the file in an random manner. 361 * 362 * @param mode The RandomAccessMode. 363 * @return A RandomAccessContent object to access the file. 364 * @throws FileSystemException if an error occurs. 365 */ 366 @Override 367 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException { 368 /* 369 * if (getThreadData().getState() != STATE_NONE) { throw new 370 * FileSystemException("vfs.provider/read-in-use.error", file); } 371 */ 372 373 // Get the content 374 final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode); 375 376 final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr); 377 378 getOrCreateThreadData().addRastr(rac); 379 streamOpened(); 380 381 return rac; 382 } 383 384 /** 385 * Returns an output stream for writing the content. 386 * 387 * @return The OutputStream for the file. 388 * @throws FileSystemException if an error occurs. 389 */ 390 @Override 391 public OutputStream getOutputStream() throws FileSystemException { 392 return getOutputStream(false); 393 } 394 395 /** 396 * Returns an output stream for writing the content in append mode. 397 * 398 * @param bAppend true if the data written should be appended. 399 * @return The OutputStream for the file. 400 * @throws FileSystemException if an error occurs. 401 */ 402 @Override 403 public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException { 404 /* 405 * if (getThreadData().getState() != STATE_NONE) 406 */ 407 final FileContentThreadData streams = getOrCreateThreadData(); 408 if (streams.getOutstr() != null) { 409 throw new FileSystemException("vfs.provider/write-in-use.error", fileObject); 410 } 411 412 // Get the raw output stream 413 final OutputStream outstr = fileObject.getOutputStream(bAppend); 414 415 // Create and set wrapper 416 final FileContentOutputStream wrapped = new FileContentOutputStream(fileObject, outstr); 417 streams.setOutstr(wrapped); 418 streamOpened(); 419 420 return wrapped; 421 } 422 423 /** 424 * Closes all resources used by the content, including all streams, readers and writers. 425 * 426 * @throws FileSystemException if an error occurs. 427 */ 428 @Override 429 public void close() throws FileSystemException { 430 FileSystemException caught = null; 431 try { 432 final FileContentThreadData fileContentThreadData = getOrCreateThreadData(); 433 434 // Close the input stream 435 while (fileContentThreadData.getInstrsSize() > 0) { 436 final FileContentInputStream inputStream = (FileContentInputStream) fileContentThreadData 437 .removeInstr(0); 438 try { 439 inputStream.close(); 440 } catch (final FileSystemException ex) { 441 caught = ex; 442 443 } 444 } 445 446 // Close the randomAccess stream 447 while (fileContentThreadData.getRastrsSize() > 0) { 448 final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) fileContentThreadData 449 .removeRastr(0); 450 try { 451 randomAccessContent.close(); 452 } catch (final FileSystemException ex) { 453 caught = ex; 454 } 455 } 456 457 // Close the output stream 458 final FileContentOutputStream outputStream = fileContentThreadData.getOutstr(); 459 if (outputStream != null) { 460 fileContentThreadData.setOutstr(null); 461 try { 462 outputStream.close(); 463 } catch (final FileSystemException ex) { 464 caught = ex; 465 } 466 } 467 } finally { 468 threadLocal.remove(); 469 } 470 471 // throw last error (out >> rac >> input) after all closes have been tried 472 if (caught != null) { 473 throw caught; 474 } 475 } 476 477 /** 478 * Handles the end of input stream. 479 */ 480 private void endInput(final FileContentInputStream instr) { 481 final FileContentThreadData fileContentThreadData = threadLocal.get(); 482 if (fileContentThreadData != null) { 483 fileContentThreadData.removeInstr(instr); 484 } 485 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 486 // remove even when no value is set to remove key 487 threadLocal.remove(); 488 } 489 streamClosed(); 490 } 491 492 /** 493 * Handles the end of random access. 494 */ 495 private void endRandomAccess(final RandomAccessContent rac) { 496 final FileContentThreadData fileContentThreadData = threadLocal.get(); 497 if (fileContentThreadData != null) { 498 fileContentThreadData.removeRastr(rac); 499 } 500 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 501 // remove even when no value is set to remove key 502 threadLocal.remove(); 503 } 504 streamClosed(); 505 } 506 507 /** 508 * Handles the end of output stream. 509 */ 510 private void endOutput() throws Exception { 511 final FileContentThreadData fileContentThreadData = threadLocal.get(); 512 if (fileContentThreadData != null) { 513 fileContentThreadData.setOutstr(null); 514 } 515 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 516 // remove even when no value is set to remove key 517 threadLocal.remove(); 518 } 519 streamClosed(); 520 fileObject.endOutput(); 521 } 522 523 /** 524 * Checks if a input and/or output stream is open. 525 * <p> 526 * This checks only the scope of the current thread. 527 * 528 * @return true if this is the case 529 */ 530 @Override 531 public boolean isOpen() { 532 final FileContentThreadData fileContentThreadData = threadLocal.get(); 533 if (fileContentThreadData != null && fileContentThreadData.hasStreams()) { 534 return true; 535 } 536 // threadData.get() created empty entry 537 threadLocal.remove(); 538 return false; 539 } 540 541 /** 542 * Checks if an input or output stream is open. This checks all threads. 543 * 544 * @return true if this is the case 545 */ 546 public boolean isOpenGlobal() { 547 synchronized (this) { 548 return openStreams > 0; 549 } 550 } 551 552 /** 553 * An input stream for reading content. Provides buffering, and end-of-stream monitoring. 554 */ 555 private final class FileContentInputStream extends MonitorInputStream { 556 // avoid gc 557 private final FileObject file; 558 559 FileContentInputStream(final FileObject file, final InputStream instr) { 560 super(instr); 561 this.file = file; 562 } 563 564 /** 565 * Closes this input stream. 566 */ 567 @Override 568 public void close() throws FileSystemException { 569 try { 570 super.close(); 571 } catch (final IOException e) { 572 throw new FileSystemException("vfs.provider/close-instr.error", file, e); 573 } 574 } 575 576 /** 577 * Called after the stream has been closed. 578 */ 579 @Override 580 protected void onClose() throws IOException { 581 try { 582 super.onClose(); 583 } finally { 584 endInput(this); 585 } 586 } 587 } 588 589 /** 590 * An input/output stream for reading/writing content on random positions 591 */ 592 private final class FileRandomAccessContent extends MonitorRandomAccessContent { 593 // also avoids gc 594 private final FileObject file; 595 596 FileRandomAccessContent(final FileObject file, final RandomAccessContent content) { 597 super(content); 598 this.file = file; 599 } 600 601 /** 602 * Called after the stream has been closed. 603 */ 604 @Override 605 protected void onClose() throws IOException { 606 try { 607 super.onClose(); 608 } finally { 609 endRandomAccess(this); 610 } 611 } 612 613 @Override 614 public void close() throws FileSystemException { 615 try { 616 super.close(); 617 } catch (final IOException e) { 618 throw new FileSystemException("vfs.provider/close-rac.error", file, e); 619 } 620 } 621 } 622 623 /** 624 * An output stream for writing content. 625 */ 626 final class FileContentOutputStream extends MonitorOutputStream { 627 // avoid gc 628 private final FileObject file; 629 630 FileContentOutputStream(final FileObject file, final OutputStream outstr) { 631 super(outstr); 632 this.file = file; 633 } 634 635 /** 636 * Closes this output stream. 637 */ 638 @Override 639 public void close() throws FileSystemException { 640 try { 641 super.close(); 642 } catch (final IOException e) { 643 throw new FileSystemException("vfs.provider/close-outstr.error", file, e); 644 } 645 } 646 647 /** 648 * Called after this stream is closed. 649 */ 650 @Override 651 protected void onClose() throws IOException { 652 try { 653 super.onClose(); 654 } finally { 655 try { 656 endOutput(); 657 } catch (final Exception e) { 658 throw new FileSystemException("vfs.provider/close-outstr.error", file, e); 659 } 660 } 661 } 662 } 663 664 /** 665 * Gets the FileContentInfo which describes the content-type, content-encoding 666 * 667 * @return The FileContentInfo. 668 * @throws FileSystemException if an error occurs. 669 */ 670 @Override 671 public FileContentInfo getContentInfo() throws FileSystemException { 672 if (fileContentInfo == null) { 673 fileContentInfo = fileContentInfoFactory.create(this); 674 } 675 676 return fileContentInfo; 677 } 678 679 /** 680 * Writes this content to another FileContent. 681 * 682 * @param fileContent The target FileContent. 683 * @return the total number of bytes written 684 * @throws IOException if an error occurs writing the content. 685 * @since 2.1 686 */ 687 @Override 688 public long write(final FileContent fileContent) throws IOException { 689 final OutputStream output = fileContent.getOutputStream(); 690 try { 691 return this.write(output); 692 } finally { 693 output.close(); 694 } 695 } 696 697 /** 698 * Writes this content to another FileObject. 699 * 700 * @param file The target FileObject. 701 * @return the total number of bytes written 702 * @throws IOException if an error occurs writing the content. 703 * @since 2.1 704 */ 705 @Override 706 public long write(final FileObject file) throws IOException { 707 return write(file.getContent()); 708 } 709 710 /** 711 * Writes this content to an OutputStream. 712 * 713 * @param output The target OutputStream. 714 * @return the total number of bytes written 715 * @throws IOException if an error occurs writing the content. 716 * @since 2.1 717 */ 718 @Override 719 public long write(final OutputStream output) throws IOException { 720 return write(output, WRITE_BUFFER_SIZE); 721 } 722 723 /** 724 * Writes this content to an OutputStream. 725 * 726 * @param output The target OutputStream. 727 * @param bufferSize The buffer size to write data chunks. 728 * @return the total number of bytes written 729 * @throws IOException if an error occurs writing the file. 730 * @since 2.1 731 */ 732 @Override 733 public long write(final OutputStream output, final int bufferSize) throws IOException { 734 final InputStream input = this.getInputStream(); 735 long count = 0; 736 try { 737 // This read/write code from Apache Commons IO 738 final byte[] buffer = new byte[bufferSize]; 739 int n = 0; 740 while (-1 != (n = input.read(buffer))) { 741 output.write(buffer, 0, n); 742 count += n; 743 } 744 } finally { 745 input.close(); 746 } 747 return count; 748 } 749}