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: */
017: package org.apache.commons.vfs.provider;
018:
019: import org.apache.commons.vfs.FileContent;
020: import org.apache.commons.vfs.FileContentInfo;
021: import org.apache.commons.vfs.FileContentInfoFactory;
022: import org.apache.commons.vfs.FileObject;
023: import org.apache.commons.vfs.FileSystemException;
024: import org.apache.commons.vfs.RandomAccessContent;
025: import org.apache.commons.vfs.util.MonitorInputStream;
026: import org.apache.commons.vfs.util.MonitorOutputStream;
027: import org.apache.commons.vfs.util.MonitorRandomAccessContent;
028: import org.apache.commons.vfs.util.RandomAccessMode;
029:
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.OutputStream;
033: import java.security.cert.Certificate;
034: import java.util.Collections;
035: import java.util.Map;
036: import java.util.Set;
037:
038: /**
039: * The content of a file.
040: *
041: * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
042: * @version $Revision: 537943 $ $Date: 2007-05-14 11:37:14 -0700 (Mon, 14 May 2007) $
043: */
044: public final class DefaultFileContent implements FileContent {
045: /*
046: static final int STATE_NONE = 0;
047: static final int STATE_READING = 1;
048: static final int STATE_WRITING = 2;
049: static final int STATE_RANDOM_ACCESS = 3;
050: */
051:
052: static final int STATE_CLOSED = 0;
053: static final int STATE_OPENED = 1;
054:
055: private final AbstractFileObject file;
056: private Map attrs;
057: private Map roAttrs;
058: private FileContentInfo fileContentInfo;
059: private final FileContentInfoFactory fileContentInfoFactory;
060:
061: private final ThreadLocal threadData = new ThreadLocal();
062:
063: /**
064: * open streams counter for this file
065: */
066: private int openStreams = 0;
067:
068: public DefaultFileContent(final AbstractFileObject file,
069: final FileContentInfoFactory fileContentInfoFactory) {
070: this .file = file;
071: this .fileContentInfoFactory = fileContentInfoFactory;
072: }
073:
074: private FileContentThreadData getThreadData() {
075: FileContentThreadData data = (FileContentThreadData) this .threadData
076: .get();
077: if (data == null) {
078: data = new FileContentThreadData();
079: this .threadData.set(data);
080: }
081: return data;
082: }
083:
084: void streamOpened() {
085: synchronized (this ) {
086: openStreams++;
087: }
088: ((AbstractFileSystem) file.getFileSystem()).streamOpened();
089: }
090:
091: void streamClosed() {
092: synchronized (this ) {
093: if (openStreams > 0) {
094: openStreams--;
095: if (openStreams < 1) {
096: file.notifyAllStreamsClosed();
097: }
098: }
099: }
100: ((AbstractFileSystem) file.getFileSystem()).streamClosed();
101: }
102:
103: /**
104: * Returns the file that this is the content of.
105: */
106: public FileObject getFile() {
107: return file;
108: }
109:
110: /**
111: * Returns the size of the content (in bytes).
112: */
113: public long getSize() throws FileSystemException {
114: // Do some checking
115: if (!file.getType().hasContent()) {
116: throw new FileSystemException(
117: "vfs.provider/get-size-not-file.error", file);
118: }
119: /*
120: if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
121: {
122: throw new FileSystemException("vfs.provider/get-size-write.error", file);
123: }
124: */
125:
126: try {
127: // Get the size
128: return file.doGetContentSize();
129: } catch (final Exception exc) {
130: throw new FileSystemException(
131: "vfs.provider/get-size.error",
132: new Object[] { file }, exc);
133: }
134: }
135:
136: /**
137: * Returns the last-modified timestamp.
138: */
139: public long getLastModifiedTime() throws FileSystemException {
140: /*
141: if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
142: {
143: throw new FileSystemException("vfs.provider/get-last-modified-writing.error", file);
144: }
145: */
146: if (!file.getType().hasAttributes()) {
147: throw new FileSystemException(
148: "vfs.provider/get-last-modified-no-exist.error",
149: file);
150: }
151: try {
152: return file.doGetLastModifiedTime();
153: } catch (final Exception e) {
154: throw new FileSystemException(
155: "vfs.provider/get-last-modified.error", file, e);
156: }
157: }
158:
159: /**
160: * Sets the last-modified timestamp.
161: */
162: public void setLastModifiedTime(final long modTime)
163: throws FileSystemException {
164: /*
165: if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
166: {
167: throw new FileSystemException("vfs.provider/set-last-modified-writing.error", file);
168: }
169: */
170: if (!file.getType().hasAttributes()) {
171: throw new FileSystemException(
172: "vfs.provider/set-last-modified-no-exist.error",
173: file);
174: }
175: try {
176: if (!file.doSetLastModTime(modTime)) {
177: throw new FileSystemException(
178: "vfs.provider/set-last-modified.error", file);
179: }
180: } catch (final Exception e) {
181: throw new FileSystemException(
182: "vfs.provider/set-last-modified.error", file, e);
183: }
184: }
185:
186: /**
187: * Checks if an attribute exists.
188: */
189: public boolean hasAttribute(final String attrName)
190: throws FileSystemException {
191: if (!file.getType().hasAttributes()) {
192: throw new FileSystemException(
193: "vfs.provider/exists-attributes-no-exist.error",
194: file);
195: }
196: getAttributes();
197: return attrs.containsKey(attrName);
198: }
199:
200: /**
201: * Returns a read-only map of this file's attributes.
202: */
203: public Map getAttributes() throws FileSystemException {
204: if (!file.getType().hasAttributes()) {
205: throw new FileSystemException(
206: "vfs.provider/get-attributes-no-exist.error", file);
207: }
208: if (roAttrs == null) {
209: try {
210: attrs = file.doGetAttributes();
211: roAttrs = Collections.unmodifiableMap(attrs);
212: } catch (final Exception e) {
213: throw new FileSystemException(
214: "vfs.provider/get-attributes.error", file, e);
215: }
216: }
217: return roAttrs;
218: }
219:
220: /**
221: * Lists the attributes of this file.
222: */
223: public String[] getAttributeNames() throws FileSystemException {
224: getAttributes();
225: final Set names = attrs.keySet();
226: return (String[]) names.toArray(new String[names.size()]);
227: }
228:
229: /**
230: * Gets the value of an attribute.
231: */
232: public Object getAttribute(final String attrName)
233: throws FileSystemException {
234: getAttributes();
235: /* imario@apache.org: VFS-132 since I do not know why I've done this, I'll disable it again
236: if (attrs.containsKey(attrName))
237: {
238: return attrs.get(attrName);
239: }
240: */
241: return attrs.get(attrName.toLowerCase());
242: }
243:
244: /**
245: * Sets the value of an attribute.
246: */
247: public void setAttribute(final String attrName, final Object value)
248: throws FileSystemException {
249: if (!file.getType().hasAttributes()) {
250: throw new FileSystemException(
251: "vfs.provider/set-attribute-no-exist.error",
252: new Object[] { attrName, file });
253: }
254: try {
255: file.doSetAttribute(attrName, value);
256: } catch (final Exception e) {
257: throw new FileSystemException(
258: "vfs.provider/set-attribute.error", new Object[] {
259: attrName, file }, e);
260: }
261:
262: if (attrs != null) {
263: attrs.put(attrName, value);
264: }
265: }
266:
267: /**
268: * Removes an attribute.
269: */
270: public void removeAttribute(final String attrName)
271: throws FileSystemException {
272: if (!file.getType().hasAttributes()) {
273: throw new FileSystemException(
274: "vfs.provider/remove-attribute-no-exist.error",
275: file);
276: }
277:
278: try {
279: file.doRemoveAttribute(attrName);
280: } catch (final Exception e) {
281: throw new FileSystemException(
282: "vfs.provider/remove-attribute.error",
283: new Object[] { attrName, file }, e);
284: }
285:
286: if (attrs != null) {
287: attrs.remove(attrName);
288: }
289: }
290:
291: /**
292: * Returns the certificates used to sign this file.
293: */
294: public Certificate[] getCertificates() throws FileSystemException {
295: if (!file.exists()) {
296: throw new FileSystemException(
297: "vfs.provider/get-certificates-no-exist.error",
298: file);
299: }
300: /*
301: if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
302: {
303: throw new FileSystemException("vfs.provider/get-certificates-writing.error", file);
304: }
305: */
306:
307: try {
308: final Certificate[] certs = file.doGetCertificates();
309: if (certs != null) {
310: return certs;
311: } else {
312: return new Certificate[0];
313: }
314: } catch (final Exception e) {
315: throw new FileSystemException(
316: "vfs.provider/get-certificates.error", file, e);
317: }
318: }
319:
320: /**
321: * Returns an input stream for reading the content.
322: */
323: public InputStream getInputStream() throws FileSystemException {
324: /*
325: if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
326: {
327: throw new FileSystemException("vfs.provider/read-in-use.error", file);
328: }
329: */
330:
331: // Get the raw input stream
332: final InputStream instr = file.getInputStream();
333: final InputStream wrappedInstr = new FileContentInputStream(
334: file, instr);
335:
336: this .getThreadData().addInstr(wrappedInstr);
337: streamOpened();
338:
339: // setState(STATE_OPENED);
340: return wrappedInstr;
341: }
342:
343: /**
344: * Returns an input/output stream to use to read and write the content of the file in an
345: * random manner.
346: */
347: public RandomAccessContent getRandomAccessContent(
348: final RandomAccessMode mode) throws FileSystemException {
349: /*
350: if (getThreadData().getState() != STATE_NONE)
351: {
352: throw new FileSystemException("vfs.provider/read-in-use.error", file);
353: }
354: */
355:
356: // Get the content
357: final RandomAccessContent rastr = file
358: .getRandomAccessContent(mode);
359:
360: FileRandomAccessContent rac = new FileRandomAccessContent(file,
361: rastr);
362: this .getThreadData().addRastr(rac);
363: streamOpened();
364:
365: // setState(STATE_OPENED);
366: return rac;
367: }
368:
369: /**
370: * Returns an output stream for writing the content.
371: */
372: public OutputStream getOutputStream() throws FileSystemException {
373: return getOutputStream(false);
374: }
375:
376: /**
377: * Returns an output stream for writing the content in append mode.
378: */
379: public OutputStream getOutputStream(boolean bAppend)
380: throws FileSystemException {
381: /*
382: if (getThreadData().getState() != STATE_NONE)
383: */
384: if (this .getThreadData().getOutstr() != null) {
385: throw new FileSystemException(
386: "vfs.provider/write-in-use.error", file);
387: }
388:
389: // Get the raw output stream
390: final OutputStream outstr = file.getOutputStream(bAppend);
391:
392: // Create wrapper
393: this .getThreadData().setOutstr(
394: new FileContentOutputStream(file, outstr));
395: streamOpened();
396:
397: // setState(STATE_OPENED);
398: return this .getThreadData().getOutstr();
399: }
400:
401: /**
402: * Closes all resources used by the content, including all streams, readers
403: * and writers.
404: */
405: public void close() throws FileSystemException {
406: try {
407: // Close the input stream
408: while (getThreadData().getInstrsSize() > 0) {
409: final FileContentInputStream instr = (FileContentInputStream) getThreadData()
410: .removeInstr(0);
411: instr.close();
412: }
413:
414: // Close the randomAccess stream
415: while (getThreadData().getRastrsSize() > 0) {
416: final RandomAccessContent ra = (RandomAccessContent) getThreadData()
417: .removeRastr(0);
418: try {
419: ra.close();
420: } catch (IOException e) {
421: throw new FileSystemException(e);
422: }
423: }
424:
425: // Close the output stream
426: if (this .getThreadData().getOutstr() != null) {
427: this .getThreadData().closeOutstr();
428: }
429: } finally {
430: threadData.set(null);
431: }
432: }
433:
434: /**
435: * Handles the end of input stream.
436: */
437: private void endInput(final FileContentInputStream instr) {
438: getThreadData().removeInstr(instr);
439: streamClosed();
440: /*
441: if (!getThreadData().hasStreams())
442: {
443: setState(STATE_CLOSED);
444: }
445: */
446: }
447:
448: /**
449: * Handles the end of random access.
450: */
451: private void endRandomAccess(RandomAccessContent rac) {
452: getThreadData().removeRastr(rac);
453: streamClosed();
454: // setState(STATE_CLOSED);
455: }
456:
457: /**
458: * Handles the end of output stream.
459: */
460: private void endOutput() throws Exception {
461: streamClosed();
462:
463: this .getThreadData().setOutstr(null);
464: // setState(STATE_CLOSED);
465:
466: file.endOutput();
467: }
468:
469: /*
470: private void setState(int state)
471: {
472: getThreadData().setState(state);
473: }
474: */
475:
476: /**
477: * check if a input and/or output stream is open.<br />
478: * This checks only the scope of the current thread.
479: *
480: * @return true if this is the case
481: */
482: public boolean isOpen() {
483: // return getThreadData().getState() == STATE_OPENED;
484: return getThreadData().hasStreams();
485: }
486:
487: /**
488: * check if a input and/or output stream is open.<br />
489: * This checks all threads.
490: *
491: * @return true if this is the case
492: */
493: public boolean isOpenGlobal() {
494: synchronized (this ) {
495: return openStreams > 0;
496: }
497: }
498:
499: /**
500: * An input stream for reading content. Provides buffering, and
501: * end-of-stream monitoring.
502: */
503: private final class FileContentInputStream extends
504: MonitorInputStream {
505: // avoid gc
506: private final FileObject file;
507:
508: FileContentInputStream(final FileObject file,
509: final InputStream instr) {
510: super (instr);
511: this .file = file;
512: }
513:
514: /**
515: * Closes this input stream.
516: */
517: public void close() throws FileSystemException {
518: try {
519: super .close();
520: } catch (final IOException e) {
521: throw new FileSystemException(
522: "vfs.provider/close-instr.error", file, e);
523: }
524: }
525:
526: /**
527: * Called after the stream has been closed.
528: */
529: protected void onClose() throws IOException {
530: try {
531: super .onClose();
532: } finally {
533: endInput(this );
534: }
535: }
536: }
537:
538: /**
539: * An input/output stream for reading/writing content on random positions
540: */
541: private final class FileRandomAccessContent extends
542: MonitorRandomAccessContent {
543: // avoid gc
544: private final FileObject file;
545: private final RandomAccessContent content;
546:
547: FileRandomAccessContent(final FileObject file,
548: final RandomAccessContent content) {
549: super (content);
550: this .file = file;
551: this .content = content;
552: }
553:
554: /**
555: * Called after the stream has been closed.
556: */
557: protected void onClose() throws IOException {
558: try {
559: super .onClose();
560: } finally {
561: endRandomAccess(this );
562: }
563: }
564: }
565:
566: /**
567: * An output stream for writing content.
568: */
569: final class FileContentOutputStream extends MonitorOutputStream {
570: // avoid gc
571: private final FileObject file;
572:
573: FileContentOutputStream(final FileObject file,
574: final OutputStream outstr) {
575: super (outstr);
576: this .file = file;
577: }
578:
579: /**
580: * Closes this output stream.
581: */
582: public void close() throws FileSystemException {
583: try {
584: super .close();
585: } catch (final IOException e) {
586: throw new FileSystemException(
587: "vfs.provider/close-outstr.error", file, e);
588: }
589: }
590:
591: /**
592: * Called after this stream is closed.
593: */
594: protected void onClose() throws IOException {
595: try {
596: super .onClose();
597: } finally {
598: try {
599: endOutput();
600: } catch (Exception e) {
601: throw new FileSystemException(
602: "vfs.provider/close-outstr.error", file, e);
603: }
604: }
605: }
606: }
607:
608: /**
609: * get the content info. e.g. content-type, content-encoding
610: */
611: public FileContentInfo getContentInfo() throws FileSystemException {
612: if (fileContentInfo == null) {
613: fileContentInfo = fileContentInfoFactory.create(this);
614: }
615:
616: return fileContentInfo;
617: }
618: }
|