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