001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.services.dbobj;
066:
067: import com.jcorporate.expresso.core.dataobjects.DataField;
068: import com.jcorporate.expresso.core.dataobjects.jdbc.LobField;
069: import com.jcorporate.expresso.core.db.DBConnection;
070: import com.jcorporate.expresso.core.db.DBException;
071: import com.jcorporate.expresso.core.dbobj.RequestContext;
072: import com.jcorporate.expresso.core.dbobj.SecuredDBObject;
073: import com.jcorporate.expresso.core.misc.ConfigJdbc;
074: import com.jcorporate.expresso.core.misc.ConfigManager;
075: import com.jcorporate.expresso.core.misc.ConfigurationException;
076: import org.apache.log4j.Logger;
077:
078: import java.io.File;
079: import java.io.FileInputStream;
080: import java.io.FileNotFoundException;
081: import java.io.IOException;
082: import java.io.InputStream;
083: import java.sql.SQLException;
084:
085: /**
086: * A MediaDBObject is a DBObject intended to be used for storage of media objects
087: * directly in the database. It facilitates generic storage by automatically
088: * creating filename and MimeType fields that map to the MimeTypes table for
089: * easy downloading. This allows for the ability to store more than one media
090: * type in the same table while still allowing the Application to appropriately
091: * deal with each MimeType.
092: * <p/>
093: * <p/>
094: * This data object conveniently stores and retrieves Binary Large OBjects (BLOBS) or
095: * Character Large OBjects (CLOBS).
096: * <p/>
097: * <p/>
098: * Performance note: Each blob field access requires one more round trip to the
099: * database since BLOBs cannot be effectively stored in memory. Thus the lazy
100: * loading.
101: * </p>
102: * <p>Typical Usage:<code><pre>
103: * myMediaDBObject = new myMediaDBObject();
104: * File f = new File("/temp/output.tmp");
105: * FileOutputStream fos = new FileOutputStream(f);
106: * myMediaDBObject.setField("id", 2)
107: * InputStream is = myMediaDBObject.retrieveBlob("picture");
108: * // Copy the input stream to the file output stream
109: * myMediaDBObject.release();
110: * </pre></code></p>
111: *
112: * @author Michael Rimov, Peter Pilgrim
113: * @version $Revision: 1.9 $ on $Date: 2004/11/17 20:48:18 $
114: * @since Expresso 5.1
115: */
116: public class MediaDBObject extends SecuredDBObject {
117:
118: /**
119: * The LOBField object for streaming data into and out of the Database
120: */
121: private LobField lf = null;
122:
123: /**
124: * The Log4j Logger
125: */
126: private static final Logger log = Logger
127: .getLogger(MediaDBObject.class);
128:
129: /**
130: * Suffix for the filename field.
131: * <p>Each blob field has several additional supporting fields. The supporting
132: * fields have the name of the original BLOB field plus a suffix that is appended
133: * to the BLOB field name to get the supporting field</p>
134: */
135: public static final String FLD_FILE_SUFFIX = "_fileName";
136:
137: /**
138: * Suffix for the mime number field. This field contains a pointer into the
139: * MimeTypes table.
140: * <p>Each blob field has several additional supporting fields. The supporting
141: * fields have the name of the original BLOB field plus a suffix that is appended
142: * to the BLOB field name to get the supporting field</p>
143: */
144: public static final String FLD_MIME_SUFFIX = "_mimeType";
145:
146: /**
147: * Suffix for the file size field. This field contains the size of the blob
148: * that was saved to the database.
149: * <p>Each blob field has several additional supporting fields. The supporting
150: * fields have the name of the original BLOB field plus a suffix that is appended
151: * to the BLOB field name to get the supporting field</p>
152: */
153: public static final String FLD_SIZE_SUFFIX = "_fileSize";
154:
155: /**
156: * Default Constructor.
157: *
158: * @throws DBException upon initialization error
159: */
160: public MediaDBObject() throws DBException {
161: super ();
162: }
163:
164: /**
165: * Constructor that sets the connection on create
166: *
167: * @param newConnection The dbConnection object to associate with this
168: * object
169: */
170: public MediaDBObject(DBConnection newConnection) throws DBException {
171: super (newConnection);
172: } /* SecuredDBObject(DBConnection) */
173:
174: /**
175: * <p/>
176: * Constructor that sets a connection as the object is created - typically
177: * this is used when a particular DBConnection is required for the purposes of
178: * maintaining a database transaction. If a specific connection is not used,
179: * there is no way to use commit() and rollback() in the event of failure, as a
180: * different DBConnection might be used for each phase of the transaction.
181: * Critial sections should therefore explicity request a DBConnection from the
182: * connection pool and pass it to each of the DB objects in that section.
183: * </p>
184: * <p>This constructor is neceesary to work with otherDBMap and transaction
185: * capabilities</p>
186: *
187: * @param newConnection The DBConnection to utilize
188: * @param setupTablesContext The data context that contains the setup (and
189: * security) tables for this object
190: */
191: public MediaDBObject(DBConnection newConnection,
192: String setupTablesContext) throws DBException {
193: super (newConnection, setupTablesContext);
194: } /* DBObject(DBConnection) */
195:
196: /**
197: * Constructor: Specify a DB connection AND user
198: *
199: * @param newUid User ID attempting to use this object.
200: * If this is SecuredDBObject.SYSTEM_ACCOUNT, then
201: * full permissions are granted. Note that you cannot log in
202: * as SecuredDBObject.SYSTEM_ACCOUNT,
203: * t can only be used from within a method.
204: * @throws DBException If the object cannot be created
205: */
206: public MediaDBObject(int newUid) throws DBException {
207: super (newUid);
208: }
209:
210: /**
211: * For using DBObjects within Controllers. Initializes based upon the current
212: * user and the requested db. [Of course this can be modified later]
213: *
214: * @param request - The controller request handed to you by the framework.
215: * @throws DBException upon construction error
216: */
217: public MediaDBObject(RequestContext request) throws DBException {
218: super (request);
219: }
220:
221: /**
222: * Convenience method to get the LOB attachment filename
223: *
224: * @throws DBException if a database error occurs
225: * @parameter fieldName the field name of the attachment
226: */
227: public String getBlobFilename(String fieldName) throws DBException {
228: return getField(fieldName + FLD_FILE_SUFFIX);
229: }
230:
231: /**
232: * Convenience method to get the LOB attachment mime content type
233: *
234: * @throws DBException if a database error occurs
235: * @parameter fieldName the field name of the attachment
236: */
237: public String getBlobMimeType(String fieldName) throws DBException {
238: return getField(fieldName + FLD_MIME_SUFFIX);
239: }
240:
241: /**
242: * Convenience method to get the LOB attachment content length
243: *
244: * @throws DBException if a database error occurs
245: * @parameter fieldName the field name of the attachment
246: */
247: public String getBlobFileSize(String fieldName) throws DBException {
248: return getField(fieldName + FLD_SIZE_SUFFIX);
249: }
250:
251: /**
252: * Convenience method to get the LOB attachment content length as
253: * an integer
254: *
255: * @throws DBException if a database error occurs
256: * @parameter fieldName the field name of the attachment
257: */
258: public int getBlobFileSizeInt(String fieldName) throws DBException {
259: return getFieldInt(fieldName + FLD_SIZE_SUFFIX);
260: }
261:
262: /**
263: * Adds a BLOB field definition to this DBObject.
264: * <p>Using this method creates several supporting fields.
265: * <ul>
266: * <li>BlobFieldName + '_mimeType': An integer value that points to the mime type
267: * in the MimeTypes table. When the BLOB is retrieved, the content stream
268: * mime type can be set allowing for the web browser to properly interpret the
269: * incoming stream.</li>
270: * <li>BlobFieldName + '_fileName': A file name for the BLOB field. While this is
271: * optional, it allows the WebBrowser to assign a default file name when it
272: * saves the BLOB locally </li>
273: * <li>BlobFieldName + '_fileSize': This field gets set when a file is uploaded
274: * into the database table. It allows for the browser to determine what percentage
275: * of the BLOB is transferred to the browser</li>
276: * </ul>
277: * </p>
278: *
279: * @param fieldName The name of the field to create.
280: * @param fieldDescription The 'friendly name' of the field to create
281: * @throws DBException upon creation error.
282: */
283: protected void addBlobField(String fieldName,
284: String fieldDescription) throws DBException {
285: this .addField(fieldName, "blob", 0, true, fieldDescription);
286: this .addField(fieldName + FLD_MIME_SUFFIX, "int", 0, true,
287: fieldDescription + " Mime Type");
288: this .addField(fieldName + FLD_FILE_SUFFIX, "varchar", 128,
289: true, fieldDescription + " File Name");
290: this .setLookupObject(fieldName + FLD_MIME_SUFFIX,
291: com.jcorporate.expresso.services.dbobj.MimeTypes.class
292: .getName());
293:
294: this .addField(fieldName + FLD_SIZE_SUFFIX, "long", 0, true,
295: fieldDescription + " File Size");
296:
297: this .setReadOnly(fieldName + FLD_FILE_SUFFIX);
298: this .setReadOnly(fieldName + FLD_MIME_SUFFIX);
299: this .setReadOnly(fieldName + FLD_SIZE_SUFFIX);
300: }
301:
302: /**
303: * This method is for saving a BLOB field that has been processed by the
304: * DefaultAutoElement object. We have all the information we need because
305: * the file to save, mime types, etc, have all been saved as attributes of
306: * the fieldName specified. Do not call this method if you have not either
307: * set the appropriate attributes yourself or have called
308: * DefaultAutoElement.parseSingleInput().
309: * <p/>
310: * This method also deletes the uploaded temp file upon completion. Create the
311: * File object and call one of the other saveBlob objects yourself if you do
312: * not want this behavior.
313: * </p>
314: *
315: * @param fieldName The field Name to save
316: * @throws DBException If unable to save the file to the database
317: * @throws IllegalStateException if unable to retrieve the appropriate attributes.
318: * @throws IllegalArgumentException if fieldName doesn't exist or is null.
319: * @see com.jcorporate.expresso.services.controller.ui.DefaultAutoElement#parseSingleInput
320: */
321: public void saveBlob(String fieldName) throws DBException {
322: if (fieldName == null) {
323: throw new IllegalArgumentException(
324: "Argument fieldName must not be null");
325: }
326:
327: DataField myField = this .getDataField(fieldName);
328: if (myField == null) {
329: throw new IllegalArgumentException("Field " + fieldName
330: + " is not defined");
331: }
332:
333: String origFileName = (String) myField
334: .getAttribute("origFileName");
335: String localFileName = (String) myField
336: .getAttribute("fileName");
337:
338: if (origFileName == null || localFileName == null) {
339: log.error("Unable to retrieve attributes: origFileName = "
340: + origFileName + " localFileName = "
341: + localFileName);
342:
343: throw new IllegalStateException(
344: "Unable to DataObject attribtues");
345: }
346:
347: File f = new File(localFileName);
348: if (f == null) {
349: log.error("File: " + localFileName
350: + " doesn't appear to exist.");
351: throw new IllegalStateException(
352: "Unable to retrieve uploaded file!");
353: }
354:
355: int fileSize = (int) f.length();
356: InputStream is;
357: try {
358: is = new FileInputStream(f);
359: } catch (FileNotFoundException ex) {
360: log.error("File: " + localFileName
361: + " doesn't appear to exist.", ex);
362: throw new IllegalStateException(
363: "Unable to retrieve uploaded file!");
364: }
365:
366: this .saveBlob(fieldName, is, origFileName, fileSize);
367:
368: try {
369: is.close();
370: f.delete();
371: } catch (IOException ex) {
372: log.error("Error closing and deleting file "
373: + localFileName, ex);
374: }
375: }
376:
377: /**
378: * Saves a BLOB field to the database
379: *
380: * @param fieldName the field Name to save
381: * @param value A binary stream to become the value of the new strea,
382: * @param fileName The name of the 'file' to store
383: * @param fileSize The size of the file to save.
384: */
385: public void saveBlob(String fieldName, java.io.InputStream value,
386: String fileName, int fileSize) throws DBException {
387: if (lf == null) {
388: lf = new LobField();
389: }
390:
391: lf.setCriteria(this );
392: try {
393: lf.saveBlob(fieldName, value, fileSize);
394:
395: } finally {
396: lf.close();
397: }
398:
399: this .setField(fieldName + "_fileName", fileName);
400:
401: this .setField(fieldName + "_mimeType", MimeTypes.getMimeType(
402: fileName, this .getDataContext()).getField(
403: MimeTypes.FLD_MIMENUMBER));
404: this .setField(fieldName + "_fileSize", fileSize);
405: this .update();
406: }
407:
408: /**
409: * Saves a file to the blob field,
410: *
411: * @param fieldName the field name to save to the database
412: * @param value A java.io.InputStream to save into the database
413: * @param fileSize the size of the stream that is getting saved to the database
414: * @throws DBException upon communication error
415: */
416: public void saveBlob(String fieldName, java.io.InputStream value,
417: int fileSize) throws DBException {
418: if (lf == null) {
419: lf = new LobField();
420: }
421:
422: lf.setCriteria(this );
423: lf.saveBlob(fieldName, value, fileSize);
424: try {
425: lf.saveBlob(fieldName, value, fileSize);
426: } finally {
427: lf.close();
428: }
429: this .setField(fieldName + "_fileName", "unknown");
430: this .setField(fieldName + "_mimeType", "application/x-unknown");
431: this .setField(fieldName + "_fileSize", fileSize);
432: this .update();
433: }
434:
435: /**
436: * Retrieves a BLOB from the database. This functiona allocates a DBConnection
437: * object and all the corresponding input streams. You must process the BLOB
438: * before doing anything else to this particular DBObject, and then call
439: * the release() function to release the DBConnection and close the appropriate
440: * InputStreams.
441: *
442: * @param fieldName The name of the field to retrieve
443: * @return java.io.InputStream the contents of the BLOB field. [May be null]
444: * @throws DBException upon retrieval error
445: */
446: public java.io.InputStream retrieveBlob(String fieldName)
447: throws DBException {
448: if (lf == null) {
449: lf = new LobField();
450: }
451:
452: lf.setCriteria(this );
453:
454: try {
455: ConfigJdbc jdbcConfig = ConfigManager.getContext(
456: this .getDataContext()).getJdbc();
457: boolean nativeBlob = jdbcConfig.isNativeBlob();
458: if (!nativeBlob) {
459: return lf.getBlobStream(fieldName);
460: } else {
461: try {
462: java.sql.Blob blob = lf.getBlob(fieldName);
463: if (blob == null) {
464: throw new DBException(
465: "Error getting BLOB object from field "
466: + fieldName);
467: }
468: return blob.getBinaryStream();
469: } catch (SQLException ex) {
470: throw new DBException(
471: "Error getting binary stream from Blob object",
472: ex);
473: }
474: }
475: } catch (ConfigurationException ex) {
476: throw new DBException(
477: "Unable to get 'nativeBlob' setting.", ex);
478: }
479: }
480:
481: /**
482: * Release the associated data with the blob fields. Very important that this
483: * is called once you've retrieved/set the BLOB fields.
484: */
485: public void release() {
486: if (lf != null) {
487: lf.close();
488: }
489: }
490:
491: }
|