blob: 8b5accf561d766ca08933c4ba24298e1ef75b29d [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.cocoon.components.source;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ConcurrentModificationException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
/**
* A <code>org.apache.cocoon.environment.WriteableSource</code> for 'file:/' system IDs.
*
* @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
* @version CVS $Id$
* @deprecated Use the new avalon source resolving instead
*/
public class FileSource extends AbstractStreamWriteableSource
implements org.apache.cocoon.environment.WriteableSource {
/** The underlying file. */
protected File file;
/** The system ID for this source (lazily created by getSystemId()) */
private String systemId;
/** Is this an html file ? */
private boolean isHTMLContent;
/**
* Create a file source from a 'file:' url and a component manager.
*/
public FileSource(String url, ComponentManager manager) {
super(manager);
if (!url.startsWith("file:")) {
throw new IllegalArgumentException("Malformed url for a file source : " + url);
}
if (url.endsWith(".htm") || url.endsWith(".html")) {
this.isHTMLContent = true;
}
this.file = new File(url.substring(5)); // 5 == "file:".length()
}
public boolean exists() {
return this.file.exists();
}
/**
* Returns <code>true</code> if the file name ends with ".htm" or ".html".
*/
protected boolean isHTMLContent() {
return this.isHTMLContent;
}
/**
* Return the unique identifer for this source
*/
public String getSystemId() {
if (this.systemId == null) {
try {
this.systemId = this.file.toURL().toExternalForm();
} catch(MalformedURLException mue) {
// Can this really happen ?
this.systemId = "file:" + this.file.getPath();
}
}
return this.systemId;
}
/**
* Get the input stream for this source.
*/
public InputStream getInputStream() throws IOException, ProcessingException {
try {
return new FileInputStream(this.file);
} catch (FileNotFoundException e) {
throw new ResourceNotFoundException("Resource not found "
+ getSystemId(), e);
}
}
public long getLastModified() {
return this.file.lastModified();
}
public long getContentLength() {
return this.file.length();
}
/**
* Get an output stream to write to this source. The output stream returned
* actually writes to a temp file that replaces the real one on close. This
* temp file is used as lock to forbid multiple simultaneous writes. The
* real file is updated atomically when the output stream is closed.
*
* @throws ConcurrentModificationException if another thread is currently
* writing to this file.
*/
public OutputStream getOutputStream() throws IOException, ProcessingException {
// Create a temp file. It will replace the right one when writing terminates,
// and serve as a lock to prevent concurrent writes.
File tmpFile = new File(this.file.getPath() + ".tmp");
// Ensure the directory exists
tmpFile.getParentFile().mkdirs();
// Can we write the file ?
if (this.file.exists() && !this.file.canWrite()) {
throw new IOException("Cannot write to file " + this.file.getPath());
}
// Check if it temp file already exists, meaning someone else currently writing
if (!tmpFile.createNewFile()) {
throw new ConcurrentModificationException("File " + this.file.getPath() +
" is already being written by another thread");
}
// Return a stream that will rename the temp file on close.
return new FileSourceOutputStream(tmpFile);
}
/**
* Always return <code>false</code>. To be redefined by implementations that support
* <code>cancel()</code>.
*/
public boolean canCancel(OutputStream stream) {
if (stream instanceof FileSourceOutputStream) {
FileSourceOutputStream fsos = (FileSourceOutputStream)stream;
if (fsos.getSource() == this) {
return fsos.canCancel();
}
}
// Not a valid stream for this source
throw new IllegalArgumentException("The stream is not associated to this source");
}
/**
* Cancels the output stream.
*/
public void cancel(OutputStream stream) throws Exception {
if (stream instanceof FileSourceOutputStream) {
FileSourceOutputStream fsos = (FileSourceOutputStream)stream;
if (fsos.getSource() == this) {
fsos.cancel();
return;
}
}
// Not a valid stream for this source
throw new IllegalArgumentException("The stream is not associated to this source");
}
/**
* A file outputStream that will rename the temp file to the destination file upon close()
* and discard the temp file upon cancel().
*/
private class FileSourceOutputStream extends FileOutputStream {
private File tmpFile;
private boolean isClosed = false;
public FileSourceOutputStream(File tmpFile) throws IOException {
super(tmpFile);
this.tmpFile = tmpFile;
}
public FileSource getSource() {
return FileSource.this;
}
public void close() throws IOException {
super.close();
try {
// Delete destination file
if (FileSource.this.file.exists()) {
FileSource.this.file.delete();
}
// Rename temp file to destination file
tmpFile.renameTo(FileSource.this.file);
} finally {
// Ensure temp file is deleted, ie lock is released.
// If there was a failure above, written data is lost.
if (tmpFile.exists()) {
tmpFile.delete();
}
this.isClosed = true;
}
}
public boolean canCancel() {
return !this.isClosed;
}
public void cancel() throws Exception {
if (this.isClosed) {
throw new IllegalStateException("Cannot cancel : outputstrem is already closed");
}
this.isClosed = true;
super.close();
this.tmpFile.delete();
}
public void finalize() {
if (!this.isClosed && tmpFile.exists()) {
// Something wrong happened while writing : delete temp file
tmpFile.delete();
}
}
}
}