blob: 68afbd1fd352093d4ef822a4cdf16a2276ddceda [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.vfs.provider;
import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileContentInfo;
import org.apache.commons.vfs.FileContentInfoFactory;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.RandomAccessContent;
import org.apache.commons.vfs.util.MonitorInputStream;
import org.apache.commons.vfs.util.MonitorOutputStream;
import org.apache.commons.vfs.util.MonitorRandomAccessContent;
import org.apache.commons.vfs.util.RandomAccessMode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* The content of a file.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public final class DefaultFileContent implements FileContent
{
/*
static final int STATE_NONE = 0;
static final int STATE_READING = 1;
static final int STATE_WRITING = 2;
static final int STATE_RANDOM_ACCESS = 3;
*/
static final int STATE_CLOSED = 0;
static final int STATE_OPENED = 1;
private final AbstractFileObject file;
private Map attrs;
private Map roAttrs;
private FileContentInfo fileContentInfo;
private final FileContentInfoFactory fileContentInfoFactory;
private final ThreadLocal threadData = new ThreadLocal();
private boolean resetAttributes;
/**
* open streams counter for this file
*/
private int openStreams;
public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory)
{
this.file = file;
this.fileContentInfoFactory = fileContentInfoFactory;
}
private FileContentThreadData getThreadData()
{
FileContentThreadData data = (FileContentThreadData) this.threadData.get();
if (data == null)
{
data = new FileContentThreadData();
this.threadData.set(data);
}
return data;
}
void streamOpened()
{
synchronized (this)
{
openStreams++;
}
((AbstractFileSystem) file.getFileSystem()).streamOpened();
}
void streamClosed()
{
synchronized (this)
{
if (openStreams > 0)
{
openStreams--;
if (openStreams < 1)
{
file.notifyAllStreamsClosed();
}
}
}
((AbstractFileSystem) file.getFileSystem()).streamClosed();
}
/**
* Returns the file that this is the content of.
* @return the FileObject.
*/
public FileObject getFile()
{
return file;
}
/**
* Returns the size of the content (in bytes).
* @return The size of the content (in bytes).
* @throws FileSystemException if an error occurs.
*/
public long getSize() throws FileSystemException
{
// Do some checking
if (!file.getType().hasContent())
{
throw new FileSystemException("vfs.provider/get-size-not-file.error", file);
}
/*
if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
{
throw new FileSystemException("vfs.provider/get-size-write.error", file);
}
*/
try
{
// Get the size
return file.doGetContentSize();
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/get-size.error", new Object[]{file}, exc);
}
}
/**
* Returns the last-modified timestamp.
* @return The last modified timestamp.
* @throws FileSystemException if an error occurs.
*/
public long getLastModifiedTime() throws FileSystemException
{
/*
if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
{
throw new FileSystemException("vfs.provider/get-last-modified-writing.error", file);
}
*/
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", file);
}
try
{
return file.doGetLastModifiedTime();
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/get-last-modified.error", file, e);
}
}
/**
* Sets the last-modified timestamp.
* @param modTime The last modified timestamp.
* @throws FileSystemException if an error occurs.
*/
public void setLastModifiedTime(final long modTime) throws FileSystemException
{
/*
if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
{
throw new FileSystemException("vfs.provider/set-last-modified-writing.error", file);
}
*/
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", file);
}
try
{
if (!file.doSetLastModTime(modTime))
{
throw new FileSystemException("vfs.provider/set-last-modified.error", file);
}
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/set-last-modified.error", file, e);
}
}
/**
* Checks if an attribute exists.
* @param attrName The name of the attribute to check.
* @return true if the attribute is associated with the file.
* @throws FileSystemException if an error occurs.
*/
public boolean hasAttribute(final String attrName) throws FileSystemException
{
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", file);
}
getAttributes();
return attrs.containsKey(attrName);
}
/**
* Returns a read-only map of this file's attributes.
* @return a Map of the file's attributes.
* @throws FileSystemException if an error occurs.
*/
public Map getAttributes() throws FileSystemException
{
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", file);
}
if (resetAttributes || roAttrs == null)
{
try
{
synchronized (this)
{
attrs = file.doGetAttributes();
roAttrs = Collections.unmodifiableMap(attrs);
resetAttributes = false;
}
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/get-attributes.error", file, e);
}
}
return roAttrs;
}
/**
* Used internally to flag situations where the file attributes should be
* reretrieved.
*/
public void resetAttributes()
{
resetAttributes = true;
}
/**
* Lists the attributes of this file.
* @return An array of attribute names.
* @throws FileSystemException if an error occurs.
*/
public String[] getAttributeNames() throws FileSystemException
{
getAttributes();
final Set names = attrs.keySet();
return (String[]) names.toArray(new String[names.size()]);
}
/**
* Gets the value of an attribute.
* @param attrName The attribute name.
* @return The value of the attribute or null.
* @throws FileSystemException if an error occurs.
*/
public Object getAttribute(final String attrName)
throws FileSystemException
{
getAttributes();
return attrs.get(attrName);
}
/**
* Sets the value of an attribute.
* @param attrName The name of the attribute to add.
* @param value The value of the attribute.
* @throws FileSystemException if an error occurs.
*/
public void setAttribute(final String attrName, final Object value)
throws FileSystemException
{
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", new Object[]{attrName, file});
}
try
{
file.doSetAttribute(attrName, value);
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/set-attribute.error", new Object[]{attrName, file}, e);
}
if (attrs != null)
{
attrs.put(attrName, value);
}
}
/**
* Removes an attribute.
* @param attrName The name of the attribute to remove.
* @throws FileSystemException if an error occurs.
*/
public void removeAttribute(final String attrName) throws FileSystemException
{
if (!file.getType().hasAttributes())
{
throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", file);
}
try
{
file.doRemoveAttribute(attrName);
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/remove-attribute.error", new Object[]{attrName, file}, e);
}
if (attrs != null)
{
attrs.remove(attrName);
}
}
/**
* Returns the certificates used to sign this file.
* @return An array of Certificates.
* @throws FileSystemException if an error occurs.
*/
public Certificate[] getCertificates() throws FileSystemException
{
if (!file.exists())
{
throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", file);
}
/*
if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
{
throw new FileSystemException("vfs.provider/get-certificates-writing.error", file);
}
*/
try
{
final Certificate[] certs = file.doGetCertificates();
if (certs != null)
{
return certs;
}
else
{
return new Certificate[0];
}
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/get-certificates.error", file, e);
}
}
/**
* Returns an input stream for reading the content.
* @return The InputStream
* @throws FileSystemException if an error occurs.
*/
public InputStream getInputStream() throws FileSystemException
{
/*
if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS)
{
throw new FileSystemException("vfs.provider/read-in-use.error", file);
}
*/
// Get the raw input stream
final InputStream instr = file.getInputStream();
final InputStream wrappedInstr = new FileContentInputStream(file, instr);
this.getThreadData().addInstr(wrappedInstr);
streamOpened();
// setState(STATE_OPENED);
return wrappedInstr;
}
/**
* Returns an input/output stream to use to read and write the content of the file in an
* random manner.
* @param mode The RandomAccessMode.
* @return A RandomAccessContent object to access the file.
* @throws FileSystemException if an error occurs.
*/
public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException
{
/*
if (getThreadData().getState() != STATE_NONE)
{
throw new FileSystemException("vfs.provider/read-in-use.error", file);
}
*/
// Get the content
final RandomAccessContent rastr = file.getRandomAccessContent(mode);
FileRandomAccessContent rac = new FileRandomAccessContent(file, rastr);
this.getThreadData().addRastr(rac);
streamOpened();
// setState(STATE_OPENED);
return rac;
}
/**
* Returns an output stream for writing the content.
* @return The OutputStream for the file.
* @throws FileSystemException if an error occurs.
*/
public OutputStream getOutputStream() throws FileSystemException
{
return getOutputStream(false);
}
/**
* Returns an output stream for writing the content in append mode.
* @param bAppend true if the data written should be appended.
* @return The OutputStream for the file.
* @throws FileSystemException if an error occurs.
*/
public OutputStream getOutputStream(boolean bAppend) throws FileSystemException
{
/*
if (getThreadData().getState() != STATE_NONE)
*/
if (this.getThreadData().getOutstr() != null)
{
throw new FileSystemException("vfs.provider/write-in-use.error", file);
}
// Get the raw output stream
final OutputStream outstr = file.getOutputStream(bAppend);
// Create wrapper
this.getThreadData().setOutstr(new FileContentOutputStream(file, outstr));
streamOpened();
// setState(STATE_OPENED);
return this.getThreadData().getOutstr();
}
/**
* Closes all resources used by the content, including all streams, readers
* and writers.
* @throws FileSystemException if an error occurs.
*/
public void close() throws FileSystemException
{
try
{
// Close the input stream
while (getThreadData().getInstrsSize() > 0)
{
final FileContentInputStream instr = (FileContentInputStream) getThreadData().removeInstr(0);
instr.close();
}
// Close the randomAccess stream
while (getThreadData().getRastrsSize() > 0)
{
final RandomAccessContent ra = (RandomAccessContent) getThreadData().removeRastr(0);
try
{
ra.close();
}
catch (IOException e)
{
throw new FileSystemException(e);
}
}
// Close the output stream
if (this.getThreadData().getOutstr() != null)
{
this.getThreadData().closeOutstr();
}
}
finally
{
threadData.set(null);
}
}
/**
* Handles the end of input stream.
*/
private void endInput(final FileContentInputStream instr)
{
getThreadData().removeInstr(instr);
streamClosed();
/*
if (!getThreadData().hasStreams())
{
setState(STATE_CLOSED);
}
*/
}
/**
* Handles the end of random access.
*/
private void endRandomAccess(RandomAccessContent rac)
{
getThreadData().removeRastr(rac);
streamClosed();
// setState(STATE_CLOSED);
}
/**
* Handles the end of output stream.
*/
private void endOutput() throws Exception
{
streamClosed();
this.getThreadData().setOutstr(null);
// setState(STATE_CLOSED);
file.endOutput();
}
/*
private void setState(int state)
{
getThreadData().setState(state);
}
*/
/**
* check if a input and/or output stream is open.<br />
* This checks only the scope of the current thread.
*
* @return true if this is the case
*/
public boolean isOpen()
{
// return getThreadData().getState() == STATE_OPENED;
return getThreadData().hasStreams();
}
/**
* check if a input and/or output stream is open.<br />
* This checks all threads.
*
* @return true if this is the case
*/
public boolean isOpenGlobal()
{
synchronized (this)
{
return openStreams > 0;
}
}
/**
* An input stream for reading content. Provides buffering, and
* end-of-stream monitoring.
*/
private final class FileContentInputStream
extends MonitorInputStream
{
// avoid gc
private final FileObject file;
FileContentInputStream(final FileObject file, final InputStream instr)
{
super(instr);
this.file = file;
}
/**
* Closes this input stream.
*/
public void close() throws FileSystemException
{
try
{
super.close();
}
catch (final IOException e)
{
throw new FileSystemException("vfs.provider/close-instr.error", file, e);
}
}
/**
* Called after the stream has been closed.
*/
protected void onClose() throws IOException
{
try
{
super.onClose();
}
finally
{
endInput(this);
}
}
}
/**
* An input/output stream for reading/writing content on random positions
*/
private final class FileRandomAccessContent extends MonitorRandomAccessContent
{
// avoid gc
private final FileObject file;
private final RandomAccessContent content;
FileRandomAccessContent(final FileObject file, final RandomAccessContent content)
{
super(content);
this.file = file;
this.content = content;
}
/**
* Called after the stream has been closed.
*/
protected void onClose() throws IOException
{
try
{
super.onClose();
}
finally
{
endRandomAccess(this);
}
}
}
/**
* An output stream for writing content.
*/
final class FileContentOutputStream extends MonitorOutputStream
{
// avoid gc
private final FileObject file;
FileContentOutputStream(final FileObject file, final OutputStream outstr)
{
super(outstr);
this.file = file;
}
/**
* Closes this output stream.
*/
public void close() throws FileSystemException
{
try
{
super.close();
}
catch (final IOException e)
{
throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
}
}
/**
* Called after this stream is closed.
*/
protected void onClose() throws IOException
{
try
{
super.onClose();
}
finally
{
try
{
endOutput();
}
catch (Exception e)
{
throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
}
}
}
}
/**
* get the content info. e.g. content-type, content-encoding
* @return The FileContentInfo.
* @throws FileSystemException if an error occurs.
*/
public FileContentInfo getContentInfo() throws FileSystemException
{
if (fileContentInfo == null)
{
fileContentInfo = fileContentInfoFactory.create(this);
}
return fileContentInfo;
}
}