/* * 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(); } } } }