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!)

-- 
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com

/*
 * ====================================================================
 *
 * 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]

Reply via email to