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.cocoon.components.source;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.FileNotFoundException;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.net.MalformedURLException;
027: import java.util.ConcurrentModificationException;
028:
029: import org.apache.avalon.framework.component.ComponentManager;
030: import org.apache.cocoon.ProcessingException;
031: import org.apache.cocoon.ResourceNotFoundException;
032:
033: /**
034: * A <code>org.apache.cocoon.environment.WriteableSource</code> for 'file:/' system IDs.
035: *
036: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
037: * @version CVS $Id: FileSource.java 433543 2006-08-22 06:22:54Z crossley $
038: * @deprecated Use the new avalon source resolving instead
039: */
040: public class FileSource extends AbstractStreamWriteableSource implements
041: org.apache.cocoon.environment.WriteableSource {
042:
043: /** The underlying file. */
044: protected File file;
045:
046: /** The system ID for this source (lazily created by getSystemId()) */
047: private String systemId;
048:
049: /** Is this an html file ? */
050: private boolean isHTMLContent;
051:
052: /**
053: * Create a file source from a 'file:' url and a component manager.
054: */
055: public FileSource(String url, ComponentManager manager) {
056:
057: super (manager);
058:
059: if (!url.startsWith("file:")) {
060: throw new IllegalArgumentException(
061: "Malformed url for a file source : " + url);
062: }
063:
064: if (url.endsWith(".htm") || url.endsWith(".html")) {
065: this .isHTMLContent = true;
066: }
067:
068: this .file = new File(url.substring(5)); // 5 == "file:".length()
069: }
070:
071: public boolean exists() {
072: return this .file.exists();
073: }
074:
075: /**
076: * Returns <code>true</code> if the file name ends with ".htm" or ".html".
077: */
078: protected boolean isHTMLContent() {
079: return this .isHTMLContent;
080: }
081:
082: /**
083: * Return the unique identifer for this source
084: */
085: public String getSystemId() {
086: if (this .systemId == null) {
087: try {
088: this .systemId = this .file.toURL().toExternalForm();
089: } catch (MalformedURLException mue) {
090: // Can this really happen ?
091: this .systemId = "file:" + this .file.getPath();
092: }
093: }
094: return this .systemId;
095: }
096:
097: /**
098: * Get the input stream for this source.
099: */
100: public InputStream getInputStream() throws IOException,
101: ProcessingException {
102: try {
103: return new FileInputStream(this .file);
104: } catch (FileNotFoundException e) {
105: throw new ResourceNotFoundException("Resource not found "
106: + getSystemId(), e);
107: }
108: }
109:
110: public long getLastModified() {
111: return this .file.lastModified();
112: }
113:
114: public long getContentLength() {
115: return this .file.length();
116: }
117:
118: /**
119: * Get an output stream to write to this source. The output stream returned
120: * actually writes to a temp file that replaces the real one on close. This
121: * temp file is used as lock to forbid multiple simultaneous writes. The
122: * real file is updated atomically when the output stream is closed.
123: *
124: * @throws ConcurrentModificationException if another thread is currently
125: * writing to this file.
126: */
127: public OutputStream getOutputStream() throws IOException,
128: ProcessingException {
129:
130: // Create a temp file. It will replace the right one when writing terminates,
131: // and serve as a lock to prevent concurrent writes.
132: File tmpFile = new File(this .file.getPath() + ".tmp");
133:
134: // Ensure the directory exists
135: tmpFile.getParentFile().mkdirs();
136:
137: // Can we write the file ?
138: if (this .file.exists() && !this .file.canWrite()) {
139: throw new IOException("Cannot write to file "
140: + this .file.getPath());
141: }
142:
143: // Check if it temp file already exists, meaning someone else currently writing
144: if (!tmpFile.createNewFile()) {
145: throw new ConcurrentModificationException("File "
146: + this .file.getPath()
147: + " is already being written by another thread");
148: }
149:
150: // Return a stream that will rename the temp file on close.
151: return new FileSourceOutputStream(tmpFile);
152: }
153:
154: /**
155: * Always return <code>false</code>. To be redefined by implementations that support
156: * <code>cancel()</code>.
157: */
158: public boolean canCancel(OutputStream stream) {
159: if (stream instanceof FileSourceOutputStream) {
160: FileSourceOutputStream fsos = (FileSourceOutputStream) stream;
161: if (fsos.getSource() == this ) {
162: return fsos.canCancel();
163: }
164: }
165:
166: // Not a valid stream for this source
167: throw new IllegalArgumentException(
168: "The stream is not associated to this source");
169: }
170:
171: /**
172: * Cancels the output stream.
173: */
174: public void cancel(OutputStream stream) throws Exception {
175: if (stream instanceof FileSourceOutputStream) {
176: FileSourceOutputStream fsos = (FileSourceOutputStream) stream;
177: if (fsos.getSource() == this ) {
178: fsos.cancel();
179: return;
180: }
181: }
182:
183: // Not a valid stream for this source
184: throw new IllegalArgumentException(
185: "The stream is not associated to this source");
186: }
187:
188: /**
189: * A file outputStream that will rename the temp file to the destination file upon close()
190: * and discard the temp file upon cancel().
191: */
192: private class FileSourceOutputStream extends FileOutputStream {
193:
194: private File tmpFile;
195: private boolean isClosed = false;
196:
197: public FileSourceOutputStream(File tmpFile) throws IOException {
198: super (tmpFile);
199: this .tmpFile = tmpFile;
200: }
201:
202: public FileSource getSource() {
203: return FileSource.this ;
204: }
205:
206: public void close() throws IOException {
207: super .close();
208:
209: try {
210: // Delete destination file
211: if (FileSource.this .file.exists()) {
212: FileSource.this .file.delete();
213: }
214: // Rename temp file to destination file
215: tmpFile.renameTo(FileSource.this .file);
216:
217: } finally {
218: // Ensure temp file is deleted, ie lock is released.
219: // If there was a failure above, written data is lost.
220: if (tmpFile.exists()) {
221: tmpFile.delete();
222: }
223: this .isClosed = true;
224: }
225: }
226:
227: public boolean canCancel() {
228: return !this .isClosed;
229: }
230:
231: public void cancel() throws Exception {
232: if (this .isClosed) {
233: throw new IllegalStateException(
234: "Cannot cancel : outputstrem is already closed");
235: }
236:
237: this .isClosed = true;
238: super .close();
239: this .tmpFile.delete();
240: }
241:
242: public void finalize() {
243: if (!this .isClosed && tmpFile.exists()) {
244: // Something wrong happened while writing : delete temp file
245: tmpFile.delete();
246: }
247: }
248: }
249: }
|