Source code: com/RuntimeCollective/content/bean/File.java
1 /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/content/bean/File.java,v 1.27 2003/09/30 15:12:46 joe Exp $
2 * $Revision: 1.27 $
3 * $Date: 2003/09/30 15:12:46 $
4 *
5 * ====================================================================
6 *
7 * Josephine : http://www.runtime-collective.com/josephine/index.html
8 *
9 * Copyright (C) 2003 Runtime Collective
10 *
11 * This product includes software developed by the
12 * Apache Software Foundation (http://www.apache.org/).
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 */
29
30 package com.RuntimeCollective.content.bean;
31
32
33 import com.RuntimeCollective.webapps.bean.User;
34 import com.RuntimeCollective.webapps.RuntimeParameters;
35 import com.RuntimeCollective.webapps.RuntimeDataSource;
36 import com.RuntimeCollective.webapps.RuntimeParameters;
37 import com.RuntimeCollective.webapps.bean.DateBean;
38 import com.RuntimeCollective.webapps.bean.Duplicable;
39 import com.RuntimeCollective.webapps.bean.EntityBean;
40
41 import org.apache.struts.upload.DiskFile;
42 import org.apache.struts.upload.FormFile;
43
44 import java.io.BufferedInputStream;
45 import java.io.ByteArrayInputStream;
46 import java.io.FileInputStream;
47 import java.io.FileNotFoundException;
48 import java.io.FileOutputStream;
49 import java.io.InputStream;
50 import java.io.IOException;
51 import java.sql.SQLException;
52 import java.util.Iterator;
53 import java.util.Vector;
54
55 /**
56 * The File class will hold the content of a file, either directly, or via a
57 * filename reference. In the first case, the content will be saved in the permanence
58 * layer (e.g. database), in the other, only the reference to the file will be kept.
59 * <p> If the latter, then the following runtime parameters must be set:
60 * <ul>
61 * <li><code>contentFilesDataDir</code> -- The directory where the content files will be stored.
62 * <li><code>pageRoot</code> -- The page root of the application
63 * <li><code>contentFilesUrlStub</code> -- The url path, relative to the application page root, where the content files will be available.
64 * </ul>
65 *
66 * @version $Id: File.java,v 1.27 2003/09/30 15:12:46 joe Exp $
67 */
68 public class File extends SimpleContent {
69
70 // ---Inherited from EntityBean---------------------------
71
72 /** The name of the database table for this bean type. */
73 public static final String DATABASE_TABLE = "content_file";
74
75 /** Set the unique id of this File.
76 * @param an int value for the id
77 */
78 public void setId(int id) {
79 // delete the file(s) relating to the old Id
80 if (this.id > -1) {
81 deleteExistingFiles();
82 }
83 super.setId(id);
84 }
85
86 /** Delete the files saved in the filesystem */
87 protected void deleteExistingFiles() {
88
89 String dirfilename = RuntimeParameters.get("contentFilesDataDir");
90 java.io.File dirFile = new java.io.File(dirfilename);
91
92 if ((dirFile == null) || (!dirFile.exists()))
93 RuntimeParameters.logFatal(this, "Cannot find File data dir : "+dirfilename+"\nIs contentFilesDataDir properly set in web.xml ?");
94
95 else {
96 java.io.File[] files = dirFile.listFiles();
97 java.io.File a_file;
98 for (int i=0; i<files.length; i++) {
99 a_file = files[i];
100 if (a_file.isFile() && a_file.getName().startsWith(getId()+".")) {
101 RuntimeParameters.logInfo(this, "Deleting File file : "+a_file.getName());
102 a_file.delete();
103 }
104 }
105 }
106
107 }
108
109 /** Save this File to the database.
110 * @exception SQLException is thrown if the object couldn't be saved properly to the database.
111 */
112 public void save() throws SQLException {
113
114 String dbalias = RuntimeDataSource.getDefaultAlias();
115
116 // Save the SimpleContent attributes
117 super.save();
118
119 // Save the File's basic attributes
120 RuntimeDataSource.save(id, "content_file", new String[] { "mimetype", "file_ending", "original_filename" }, new Object[] { MimeType, FileEnding, OriginalFilename });
121 }
122
123 /** Delete this File from the database.
124 * This also deletes the suitable file(s) from the contentFilesDataDir directory.
125 * There may be more than one file to delete if we changed the FileEnding.
126 *
127 * @exception SQLException is thrown if the object couldn't be deleted properly from the database.
128 */
129 public void delete() throws SQLException {
130
131 String[] updates;
132
133 updates = new String[] { "delete from content_file where id = "+id };
134 RuntimeDataSource.update( updates);
135
136 // deleting the file(s)
137 deleteExistingFiles();
138
139 // Delete SimpleContent attributes
140 super.delete();
141 }
142
143
144 //---Inherited from Content---------------------
145
146 /** Get the title
147 * Return the filename and mimetype.
148 *
149 * @return A String which can be used as the title of this object.
150 */
151 public String getTitle() {
152 return (new StringBuffer(30)).append(getId()).append(".").append(getFileEnding()).append(" (").append(getMimeType()).append(")").toString();
153 }
154
155 /** Get the description
156 * Return the original filename and mimetype.
157 *
158 * @return A String which can be used as the description of this object.
159 */
160 public String getDescription() {
161 return (new StringBuffer(35)).append(getOriginalFilename()).append(" (").append(getMimeType()).append(")").toString();
162 }
163
164 /** Get the File in a certain format.
165 * For now, only "html" is supported, and it gives the URL of where to find the local file.
166 *
167 * @param A String representing the format in which to display the object.
168 * @return An Object which can be used to display the object in this format.
169 */
170 public Object viewFormat(String format) {
171 if (format.equals("html")) {
172 String filesUrlStub = RuntimeParameters.get("contentFilesUrlStub");
173 if (filesUrlStub != null) {
174 return (new StringBuffer(70)).append("http://").append(RuntimeParameters.get("pageRoot")).append("/").append(RuntimeParameters.get("contentFilesUrlStub")).append("/").append(getId()).append(".").append(getFileEnding()).append("?").append(Math.random()).toString();
175 // non-cacheing bit:
176 // .append("?").append(Math.random())
177 } else {
178 RuntimeParameters.logError(this, "contentFilesUrlStub not set");
179 throw new RuntimeException("contentFilesUrlStub not set");
180 }
181
182 } else {
183 return null;
184 }
185 }
186
187
188 //---Specific to File---------------------
189
190 /** The MimeType */
191 protected String MimeType;
192
193 /** Set the MimeType
194 * @param The Mime type
195 */
196 public void setMimeType(String mimeType) {
197 MimeType = mimeType;
198 }
199
200 /** Get the MimeType
201 * @return The Mime type
202 */
203 public String getMimeType() {
204 return MimeType;
205 }
206
207 /** The OriginalFilename */
208 protected String OriginalFilename;
209
210 /** Set the OriginalFilename
211 * @param The original file's name
212 */
213 public void setOriginalFilename(String originalFilename) {
214 OriginalFilename = originalFilename;
215 }
216
217 /** Get the OriginalFilename
218 * @return The original file's name
219 */
220 public String getOriginalFilename() {
221 return OriginalFilename;
222 }
223
224 /** The FileEnding */
225 protected String FileEnding;
226
227 /** Set the FileEnding
228 * This will delete old files - if the former FileEnding is not null
229 * (ie if we are not initialising this bean)
230 * @param The end of the file name (eg "gif")
231 */
232 public void setFileEnding(String fileEnding) {
233 if (!fileEnding.equals(FileEnding)) {
234 // get rid of files which use the old FileEnding
235 if (FileEnding != null)
236 deleteExistingFiles();
237
238 FileEnding = fileEnding;
239 }
240 }
241
242 /** Get the FileEnding
243 * @return The end of the file name (eg "gif")
244 */
245 public String getFileEnding() {
246 return FileEnding;
247 }
248
249 /** Construct a new blank File, giving it a new unique ID.
250 * @exception SQLException is thrown if a new ID cannot be created.
251 */
252 public File() throws SQLException {
253 // SimpleContent setup
254 super();
255 setFileEnding("");
256 setOriginalFilename("");
257 setMimeType("");
258 }
259
260 /** Get a File from the RuntimeDataSource, given an id.
261 * @param id ID of the File.
262 * @exception SQLException is thrown if no such File exists.
263 */
264 public File(int id) throws SQLException {
265
266 // SimpleContent setup
267 super(id);
268
269 String dbalias = RuntimeDataSource.getDefaultAlias();
270
271 Object[] result = RuntimeDataSource.queryRow( "select cf.mimetype, cf.file_ending, cf.original_filename from content_file cf where cf.id = "+id);
272 if (result.length != 3) {
273 throw new SQLException("Cannot load File "+id+": "+result.length+" fields found in content_textcomponent instead of 3.");
274 }
275
276 if (result[0] != null)
277 setMimeType(result[0].toString());
278 if (result[1] != null)
279 setFileEnding(result[1].toString());
280 if (result[2] != null)
281 setOriginalFilename(result[2].toString());
282 }
283
284
285 /** Change the content of the File, from a FormFile
286 * @param A FormFile
287 * @exception RuntimeException is thrown if there is any problem
288 */
289 public void setContent(FormFile formFile) throws RuntimeException {
290 try {
291 String fileName = formFile.getFileName();
292 setMimeType(formFile.getContentType());
293 setOriginalFilename(fileName);
294
295 RuntimeParameters.logDebug(this, "FormFile stored in : "+((DiskFile) formFile).getFilePath());
296
297 int lastDot = fileName.lastIndexOf(".");
298 if ((lastDot > -1) && (lastDot < fileName.length() - 2))
299 setFileEnding(fileName.substring(lastDot + 1));
300 else
301 setFileEnding("");
302
303 // do this last as it will use the attributes set above
304 setContent(formFile.getInputStream(), formFile.getFileSize());
305
306 formFile.destroy();
307
308 } catch (Exception e) {
309 throw new RuntimeException("Could not get a stream from the specified FormFile to upload : "+e);
310 }
311 }
312
313
314 /** Change the content of the File, from a java.io.File
315 * @param A File
316 * @exception RuntimeException is thrown if there is any problem
317 */
318 public void setContent(java.io.File file) throws RuntimeException {
319 try {
320 String fileName = file.getName();
321 int lastDot = fileName.lastIndexOf(".");
322 if ((lastDot > -1) && (lastDot < fileName.length() - 2))
323 setFileEnding(fileName.substring(lastDot + 1));
324 else
325 setFileEnding("");
326
327 setContent(new FileInputStream(file), (int)file.length());
328
329 } catch (Exception e) {
330 throw new RuntimeException("Could not get a stream from the specified File to upload : "+e);
331 }
332 }
333
334
335 /** Change the content of the File, from a String
336 * Be careful: Strings are 8-bit (no strange characters)
337 * @param A String
338 * @exception RuntimeException is thrown if there is any problem
339 */
340 public void setContent(String string) throws RuntimeException {
341 try {
342 setContent(new ByteArrayInputStream(string.getBytes()), string.length());
343 } catch (RuntimeException e) {
344 throw new RuntimeException("Could not upload the specified String : "+e);
345 }
346 }
347
348 /**
349 * A method to empty the content of this file.
350 * @exception RuntimeException is thrown if there is any problem
351 */
352 public void emptyContent() throws RuntimeException {
353 try {
354 setContent("");
355 setOriginalFilename("");
356 setMimeType("");
357 } catch (RuntimeException e) {
358 throw new RuntimeException("Could not emptyContent() : "+e);
359 }
360 }
361
362 /**
363 * Returns whether this file is valid or not, that is, whether it has valid content set.
364 */
365 public boolean isValid() {
366 java.io.File theFile = getFile();
367 if (theFile == null) {
368 return false;
369 } else {
370 return theFile.exists();
371 }
372 }
373
374
375 /**
376 * Sometimes, it's just useful to get the raw File itself. I'm sorry, but it is.
377 * <p>
378 * If there's more than one, just return the first one found. (There shouldn't be.)
379 */
380 public java.io.File getFile() {
381
382 Vector allFoundFiles = new Vector();
383
384 String dirfilename = RuntimeParameters.get("contentFilesDataDir");
385 java.io.File dirFile = new java.io.File(dirfilename);
386
387 if ((dirFile == null) || (!dirFile.exists())) {
388 RuntimeParameters.logError(this, "Cannot find File data dir : "+dirfilename+"\nIs contentFilesDataDir properly set in web.xml ?");
389 return null;
390
391 } else {
392 java.io.File[] files = dirFile.listFiles();
393 java.io.File a_file;
394
395 for (int i=0; i<files.length; i++) {
396 a_file = files[i];
397 if (a_file.isFile() && a_file.getName().startsWith(getId()+".")) {
398 RuntimeParameters.logInfo(this, "Found the file : "+a_file.getName());
399 allFoundFiles.add(a_file);
400 }
401 }
402 }
403
404 if (allFoundFiles.size() > 1) {
405 RuntimeParameters.logError(this, "More than one file found for "+getId()+" - just returning the first one.");
406 }
407
408 if (allFoundFiles.size() == 0) {
409 // No matching files!
410 RuntimeParameters.logDebug(this, "No files found for "+getId()+" - returning null!");
411 return null;
412
413 } else {
414 // Return the first matching file.
415 // (Hopefully there's just one.)
416 return (java.io.File) allFoundFiles.elementAt(0);
417 }
418 }
419
420
421
422 /** Change the content of the File, from an InputSteam and its size
423 * @param An InputStream
424 * @param An int, the size of the InputStream
425 * @exception RuntimeException is thrown if there is any problem
426 */
427 public void setContent(InputStream inputStream, int length) throws RuntimeException{
428
429 byte[] content = new byte[length];
430
431 try {
432 // read the input stream
433 BufferedInputStream bis = new BufferedInputStream(inputStream);
434 bis.read(content, 0, length);
435 bis.close();
436
437 // create the local file (and parent dir if required)
438 StringBuffer filename = new StringBuffer(40);
439
440 filename.append(RuntimeParameters.get("contentFilesDataDir"));
441
442 java.io.File file = new java.io.File(filename.toString());
443 if (!file.exists()) {
444 RuntimeParameters.logInfo(this, "Creating File data dir : "+filename.toString());
445 file.mkdirs();
446 }
447 filename.append(java.io.File.separator).append(getId()).append(".").append(getFileEnding());
448 file = new java.io.File(filename.toString());
449 if (file.exists())
450 file.delete();
451 file.createNewFile();
452
453 // write the stream's content to the file
454 RuntimeParameters.logInfo(this,"Saving to : "+filename.toString());
455 FileOutputStream os = new FileOutputStream(file);
456 os.write(content);
457 os.flush();
458 os.close();
459
460 } catch (Exception e) {
461 RuntimeParameters.logError(this, "The content dir is " + RuntimeParameters.get("contentFilesDataDir"));
462 RuntimeParameters.logError(this, "Could not read and save the InputStream's content into a File : ",e);
463 throw new RuntimeException("Could not read and save the InputStream's content into a File : "+e);
464 }
465 }
466
467
468 // Duplicable specific
469
470 /**
471 * The File version of this method actually copies the file on the FileSystem.
472 *
473 * @param duplicate, the "raw" duplicate
474 * @return the "complete" duplicate
475 */
476 public Duplicable customiseDuplicate(Duplicable duplicate) {
477
478 // use the parent
479 duplicate = super.customiseDuplicate(duplicate);
480
481 if (duplicate == null) {
482 RuntimeParameters.logDebug(this, " *** File "+getId()+" is passed \"null\" in customiseDuplicate !");
483 return null;
484 }
485
486 if (!(duplicate instanceof File)) {
487 RuntimeParameters.logDebug(this, " *** File "+getId()+" is passed non-File "+duplicate.getId()+" in customiseDuplicate !");
488 return duplicate;
489 }
490
491 // we are in the original object, we have the duplicate in hand
492 // what we want to do is tell the duplicate to use a copy of the file of the original:
493 // 1. get the java.io.File of the original
494 // 2. set the content of the duplicate to that java.io.File
495
496 // 1. get the java.io.File of the original
497 StringBuffer filename = new StringBuffer(40);
498 filename.append(RuntimeParameters.get("contentFilesDataDir"));
499 filename.append(java.io.File.separator).append(getId()).append(".").append(getFileEnding());
500 java.io.File file = new java.io.File(filename.toString());
501
502 // 2. set the content of the duplicate to that java.io.File
503 if (file.exists()) {
504 try {
505 ((File) duplicate).setContent(file);
506 } catch (RuntimeException e) {
507 RuntimeParameters.logDebug(this, " *** File "+getId()+", in customiseDuplicate("+duplicate.getId()+"), could not setContent of the duplicate : "+e);
508 }
509 }
510
511 // no need to save anything
512
513 return duplicate;
514 }
515 }
516
517
518
519