Frank,

I don't really like your approach and don't want to see such an Action added to a Framework like Struts. Here's a few comments why:

   * Struts is database-independent. People don't necessarily use plain
     JDBC.
   * You are getting anything you need for accessing your DB from the
     request, including SQL, username, password, i. e. it would
     probably have to be included plain text in some JSP. Bad thing...
   * I don't like JDBC in Actions. What about separating controller
     from business logic and database access?
   * Your approach is not very flexible. You may not always want to
     read the data completely into memory before writing it to the
     response.
   * BTW, it's a good idea to call response.reset() and, if possible,
     to set the content length before writing to the OutputStream.

Streaming files back to the reponse is really nothing special, and it's not exactly a Struts-specific thing. A simple Booch utitlity class would serve the purpose.

Reinhard


Frank W. Zammetti (MLists) wrote:

One of the things I see asked very frequently on the Users mailing list is
how to return PDF's and other BLOB fields from a database (or from a file
system).  People either have trouble figuring out how to do it and require
help, or have trouble making it work.

Please find attached source for a new Action called BLOBAction that I
submit to you all for comments and, perhaps eventually, inclusion in
Struts as a built-in Action like ForwardAction and the like.

Consider this proof-of-concept code, quality-wise... You can read my
rather verbose comments on this, but in short... Right now it works by
accepting a bunch of parameters submitted with the request.  This I feel
is NOT the right approach.  I was hoping that Struts would read in unknown
attributes for <action> mappings from struts-config.xml, but that does not
seem to be the case.  I believe most, if not all, of these parameters
should become attributes of the action mappings themselves, but this is
one of the things I'm looking for feedback on.

I am using this code (essentially this code... this is more generic
though) in a production app here at work, so I know it works (aside from
my tests here on this particular code which of course work).  I'm sure
it's not ready as-is, but I don't think it's too bad either.

I look forward to any comments you may have (first and foremost: is this
even worth it?  I think it is, but I may be wrong on even that basic
point!)



------------------------------------------------------------------------

/*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution, if
*    any, must include the following acknowlegement:
*       "This product includes software developed by the
*        Apache Software Foundation (http://www.apache.org/)."
*    Alternately, this acknowlegement may appear in the software itself,
*    if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Struts", and "Apache Software
*    Foundation" must not be used to endorse or promote products derived
*    from this software without prior written permission. For written
*    permission, please contact [EMAIL PROTECTED]
*
* 5. Products derived from this software may not be called "Apache"
*    nor may "Apache" appear in their names without prior written
*    permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/


package org.apache.struts.action;



import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletOutputStream;
import javax.sql.DataSource;


/** * A BLOBAction can be used generically to return a BLOB, i.e., an image or a * PDF or anything else. It can use a database or the file system as the * source of the BLOB. Note that this version, which is basically a more * generic proof-of-concept of an Action I use in a productiona app already, * gets all its parameters from request. This, I think, is *NOT* how it * should work, at least not exclusively. I believe most, if not all, of the * parameters used here should actually be attributes of the ActionMapping. * Ideally, if the parameters are found in request them they would override * what's in the mapping, but having them JUST be request parameters I don't * think is the right answer. I was hoping Struts would grab any attributes * it found for mappings whether it recognized them or not, but that doesn't * seem to be the case, hence I can't fully and properly implement this without * input from others. So, for now, everything is request parameters. * * The following parameters are accepted (remember, most or maybe even all of * these I think should be optional attributes of <action>... * * contentType .. This is the content type of the returned response. Values * like "image/gif", "image/jpg" or "application/pdf" are typical. * * dataSource .......... Either the value "database" or "file". Determines * whether the BLOB is served from a database or the file * system (probably the local file system, but could be a * mapped drive or remotely mounted volume too.) * dbConnectionSource .. Only applicable when dataSource="database". Either * the value "create" or "jndi". Use "create" when you * want the database connection to be created with each * invocation of this Action. Use "jndi" to get the * connection from JNDI (probably a connection pool). * dbDriverClass ....... Only applicable when dataSource="database". This is * the JDBC driver class to use to access the database. * dbURL ............... Only applicable when dataSource="database". This is * the connection string used to connect to the database. * This is vendor-specific! * dbUsername .......... Only applicable when dataSource="database". Username * used to connect to the database. * dbPassword .......... Only applicable when dataSource="database". Password * for the user specified by username, used to access the * database. * dbTable ............. Only applicable when dataSource="database". This is * the name of the table in the database that the BLOB is * coming from. * dbField ............. Only applicable when dataSource="database". This is * the field in the table specified by dbTable that the * BLOB is coming from. * dbQuery ............. Only applicable when dataSource="database". This is * the SQL in the WHERE clause used to retrieve the BLOB * from the database. * flFullPath .......... Only applicable when dataSource="file". This is a * fully-qualified filename, including path. Note that * this is NOT relative to the container, which might be * a security concern (although I view it as a powerful * capability... I think it's definitely a security * concern when all these parameters are passed on the * URL, but it shouldn't be an issue when they are * included in the <action> mappings). * * Sample usages: * * >> Serve from a database: * Assume you have an app server running on the local machine on port 8181, * and a test app installed called "blobtest" using an extension of .bt. * Further assume you have an action mapping in struts-config.xml pointing to * this action under the name BLOBServer.bt. Further still, assume * you have an Oracle database on a remote machine called * "dbsrvr" on listening on port 1521 with a schema named "schm1". Even * further assume you have a user "theusr" set up in the database with a * password of "mypw123". Lastly, assume you have a table named "logos" with * two fields, "logo" and "logoid" and you want to return the image stored in * the BLOB field "logo" where logoid=153. You are NOT using JNDI to get a * connection to the database, you want to create a connection on each * invocation of this Action. All that assumed, invoke this * action with the following URL to test it (i.e., as the src of an <img> * tag perhaps)... * * http://localhost:8181/toa/BLOBServer.bt?dataSource=database&; * dbConnectionSource=create&contentType=image/gif&dbTable=logos&dbField=logo& * dbQuery=logoid=153&dbDriverClass=oracle.jdbc.driver.OracleDriver& * dbURL=jdbc:oracle:thin:@cdp03d:1521:utoa&dbUsername=utoa&dbPassword=utoatest * * * >> Serve from the file system: * Assume you have an image named test.gif in the directory c:\temp (on a * Windows system obviously)... * * http://localhost:8181/toa/BLOBServer.toa?dataSource=file&; * flFullPath=c:\temp\test.gif * * * @author <a href="mailto:[EMAIL PROTECTED]">Frank W. Zammetti</a> * @version .1 * @date September 17, 2004 */ public class BLOBAction extends Action {


/** * This usual execute method * * @param mapping Action mapping * @param form ActionForm * @param request HTTP Request as passed in to execute * @param response HTTP Response as passed in to execute * @return null No return, response is rendered fully from here * @throws Exception Exception */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Get the parameter telling us the source of our BLOB as well as what
   // the content type should be
   String dataSource  = (String)request.getParameter("dataSource");
   String contentType = (String)request.getParameter("contentType");

   // Determine whether the BLOB is coming from a database or the file
   // system and call the appropriate method, which returns an array of
   // bytes that we'll then render to the output stream
   byte[] bytes = null;
   if (dataSource.equalsIgnoreCase("database")) {
     bytes = serveFromDatabase(request);
   }
   if (dataSource.equalsIgnoreCase("file")) {
     bytes = serveFromFileSystem(request);
   }

   // Get ready to output
   ServletOutputStream out = response.getOutputStream();

   // Set content type and output to stream
   response.setContentType(contentType);
   ByteArrayOutputStream ba = new ByteArrayOutputStream();
   ba.write(bytes, 0, bytes.length);
   ba.writeTo(out);
   out.flush();

   // Not forwarding anywhere
   return null;

 } // End process()


/** * This method is called to return the BLOB as a byte array from a database * * @param request HTTP Request as passed in to execute * @return byte[] Byte array of the BLOB data * @throws Exception Exception */ private byte[] serveFromDatabase(HttpServletRequest request) throws Exception {

  // Get the parameters from request specific to a database source
   String dbTable = (String)request.getParameter("dbTable");
   String dbField = (String)request.getParameter("dbField");
   String dbQuery = (String)request.getParameter("dbQuery");

   // Set up for the query
   String     sql  = "select " + dbField + " from " + dbTable +
                     " where " + dbQuery;
   Connection conn = getDBConnection(request);

   // Do the query and get results
   Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                                         ResultSet.CONCUR_UPDATABLE);
   ResultSet rs   = stmt.executeQuery(sql);
   Blob      blob = null;
   while (rs.next()) {
     blob = rs.getBlob(dbField);
   }

  // Clean up after ourselves
   rs.close();
   conn.close();

   // Return our BLOB
   return blob.getBytes(1, (int)(blob.length()));

 } // End serveFromDatabase


/** * This method will return a Connection object to a database when a * database is the BLOB data source * * @param request HTTP Request as passed in to execute * @return Connection Database connection * @throws Exception Exception */ private Connection getDBConnection(HttpServletRequest request) throws Exception {

   // Determine whether we're creating a connection manually or getting one
   // from a JNDI lookup
   String dbConnSource = (String)request.getParameter("dbConnectionSource");

   // Create the connection, or get it from JNDI
   Connection conn = null;

   if (dbConnSource.equalsIgnoreCase("create")) {

     // We're creating the database connection here, so get the required
     // parameters from request and do it
     String dbDriverClass = (String)request.getParameter("dbDriverClass");
     String dbURL         = (String)request.getParameter("dbURL");
     String dbUsername    = (String)request.getParameter("dbUsername");
     String dbPassword    = (String)request.getParameter("dbPassword");
     Class.forName(dbDriverClass);
     conn = DriverManager.getConnection(dbURL, dbUsername, dbPassword);

   } else if (dbConnSource.equalsIgnoreCase("jndi")) {

     // We're getting the database connection from JNDI, easy enough!  The
     // only extra thing we need is the JNDI context to use.. I found that
     // under WebSphere you have to look up in InitialContext, while under
     // Tomcat it's under java:comp/env.  I very much assume I either don't
     // know something about JNDI or am doing something wrong, so I look
     // forward to anyone that can give me the right answer!  For now though,
     // this code seems to do the trick.
     String jndiContext = (String)request.getParameter("jndiContext");
     String jndiIdentifier = (String)request.getParameter("jndiIdentifier");
     InitialContext jndiCntx = new InitialContext();
     Context ctx = null;
     if (jndiContext == null || jndiContext.equalsIgnoreCase("")) {
       ctx = jndiCntx;
     } else {
       ctx = (Context)jndiCntx.lookup(jndiContext);
     }
     DataSource ds = (DataSource)ctx.lookup(jndiIdentifier);
     conn =  ds.getConnection();

   } // End dbConnSource if

   return conn;

 } // End getDBConnection()


/** * This method is called to return the BLOB as a byte array from the * file system * * @param request HTTP Request as passed in to execute * @return byte[] Byte array of the BLOB data * @throws Exception Exception */ private byte[] serveFromFileSystem(HttpServletRequest request) throws Exception {

   // Get the parameters from request specific to a database source
   String flFullPath = (String)request.getParameter("flFullPath");

   // Read in the file to a byte array
   File                f    = new File(flFullPath);
   FileInputStream     istr = new FileInputStream(f);
   BufferedInputStream bstr = new BufferedInputStream(istr);
   int                 size = (int)f.length();
   byte[]              data = new byte[size];
   bstr.read(data, 0, size);
   bstr.close();

   // Return the BLOB data
   return data;

 } // End serveFromDatabase


} // End class


------------------------------------------------------------------------

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]


--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]



Reply via email to