Uploading Binary Files (BLOBS) to An Oracle Table Using a Servlet
The purpose of this entry is to provide the reader with the actual code required to upload a binary file (image, excel spreadsheet, Word Doc, PDF, etc) from a web browser and store it in the BLOB column of database table. After searching high and low for a specific example (I did find one that showed how to upload the file and save it to disk, which helped quite a bit), I vowed to publish my code for anyone else searching for a similar example. Some of this code comes from an actual application, and may not seem to 'flow', as I have omitted non-relevant portions. The goal here is to impart the method for the upload and storage of BLOB data from the browser to the database.
Multipart Form-Data ... A brief explanation
It is important at the start to realize that binary data is uploaded as multipart/form-data data from a form on a web page. This means that the parameter values passed up from the form cannot be retrieved from the receiving servlet using the traditional request.getParameter method. The form tag will look something like:
<FORM action="/uploadservlet/imageUpload.do"
enctype="multipart/form-data"
method="POST" >
The file upload component of the form uses an input tag of the format:
<INPUT type="file" name="my_image">
The rest of the form input parameter are just like any other:
<INPUT type="text" name="p_user">
All of the data is sent up in a specific format. The passed parameters are strings of the form:
Content-Disposition: form-data; name="p_user" Joe smith
--------------AaB03x
In the example above, p_user is a form parameter name and Joe Smith is the value. NOTICE that there is a blank line between the parameter string and the actual value. Each of the parameter string value pairs is separated by a delimiter or boundary. The boundary line separating each individual data element looks something like:
--------------AaB03x
The value of this boundary varies. The first line in the stream will contain this value. It is important to grab and store this value, as the code will later demonstrate.
The binary content, or file, will be the last parameter string value pair in the input stream (basically the rest of the input stream). The parameter string for the file looks like this:
content-disposition: form-data; name="my_image"; filename="test.jpeg"
Content-Type: image/pjpeg
... FILE CONTENT ...
The next string or line after the file parameter string is the content indicator string. The actual content of the file follows content type string. NOTICE that the file data is separated from the content string by a blank line. For more info see
(http://www.faqs.org/rfcs/rfc1867.html).
The Code .....
The code for uploading the image into the data consists of a PL/SQL function on the data base side that takes the BLOB passed from the servlet and stores it in a new table, and the Java Servlet and related classes that handle the upload request. First, lets look at the PL/SQL. The following code comes from an application used to upload juvenile mugshots into a database in a juvenile arrest system:
FUNCTION pre_load_image_table(p_juvmast_id JUVENILE_MASTERS.ID%TYPE := NULL,
p_label JUVENILE_PHOTOS.label%TYPE := NULL,
p_user JUVENILE_PHOTOS.created_by%TYPE := NULL,
p_mime_type JUVENILE_PHOTOS.MIME_TYPE%TYPE := NULL) RETURN NUMBER
IS
v_return NUMBER := NULL;
BEGIN
EXECUTE IMMEDIATE 'SELECT jpo_seq.nextval FROM dual' INTO v_return;
EXECUTE IMMEDIATE 'INSERT INTO juvenile_photos(id,juvmast_id,label,lineup_order,mime_type,juv_image,created_by,date_created) '||
' values (:id,:p_juvmast_id,:p_label,:p_lineup,:p_mime_type,empty_blob(),:created_by,:date_created)'
USING
v_return,
p_juvmast_id,
p_label,
getNextLineupOrder(p_juvmast_id),
p_mime_type,
p_user,
SYSDATE;
RETURN v_return;
END;
FUNCTION externalSaveImage(p_juvmast_id JUVENILE_MASTERS.ID%TYPE := NULL,
p_label JUVENILE_PHOTOS.label%TYPE := NULL,
p_user JUVENILE_PHOTOS.created_by%TYPE := NULL,
p_mimetype JUVENILE_PHOTOS.MIME_TYPE%TYPE := NULL) RETURN t_refcursor
IS
v_refcur kbcJuvenileImages.t_refcursor;
v_pk NUMBER(12) := 0;
BEGIN
--Creates a new row in the image table with an empty_blob() and returns new PK.
v_pk := pre_load_image_table(p_juvmast_id,UPPER(p_label),p_user,p_mimetype);
OPEN v_refcur FOR 'SELECT juv_image FROM juvenile_photos WHERE id = :id FOR UPDATE' USING v_pk;
RETURN v_refcur;
END;
The first function is called by the second to create a new row in the image table containing the data passed from the webpage describing the image. Please note that an EMPTY BLOB is inserted into the blob column of this table. The second function selects the BLOB column of the new record for update and passes this value back to the calling Java application as a refcursor. This creates an input stream that allows the blob data to be streamed back to the database.
The image upload Java class is our next piece of code. The class extends another class that I have not included. The base class is merely a JDBC helper class containing code to obtain a connection object from a connection factory. The important code here is the code that illustrates how to stream the binary data back to the database:
package imageupload;
import java.io.*;
import java.sql.*;
import javax.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;
import javax.servlet.ServletInputStream;
/**
* The purpose of this class is to take the remaining portion
* of the input stream containing the body of the uploaded file
* and pass it to a database stored proc that saves it to a
* BLOB column in a table.
* This class extends a JDBC helper class called
* JDBC connection base.
*/
public class ImageLoader extends JDBCConnectionBase
{
public void insertImageData(ServletInputStream p_image_stream,
int p_id,
String p_label,
String p_user,
String p_mime_type,
String p_delimiter){
try{
BLOB v_image_blob = null;
String lineCheck;
setPooledConnection();
//Using the stored procedure removes some of the complexity from
//the Java application.
CallableStatement cs =
conn.prepareCall("begin ? := kbcJuvenileImages.externalSaveImage(?,?,?,?); end;");
cs.registerOutParameter(1,OracleTypes.CURSOR);
cs.setInt(2,p_id);
cs.setString(3,p_label);
cs.setString(4,p_user);
cs.setString(5,p_mime_type);
cs.execute();
rset = (ResultSet)cs.getObject(1);
if (rset.next()){
v_image_blob = ((OracleResultSet)rset).getBLOB(1);
}
OutputStream outstream = v_image_blob.getBinaryOutputStream();
byte[] buffer = new byte[256];
int length = -1;
while ((length = p_image_stream.readLine(buffer,0,256)) != -1)
{
lineCheck = new String(buffer,0,length);
if (lineCheck.indexOf(p_delimiter)== -1) {
outstream.write(buffer,0,length);
}
}
outstream.close();
cs.close();
conn.commit();
}catch(SQLException e)
{
System.err.println("Error creating image record: "+e.getMessage());
v_error.append("Error creating image record: ").append(e.getMessage());
}catch(Exception e)
{
System.err.println("Error creating image record: "+e.getMessage());
v_error.append("Error creating image record.").append(e.getMessage());
e.printStackTrace();
}finally
{
closeConnections();
}
}
}
The next bit of Java code is the actual servlet that handles the request for upload. I think I have sufficiently commented the code, but if you have questions just shoot me an email.
package imageupload;
import oracle.sql.BLOB;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.ServletInputStream;
import java.io.PrintWriter;
import java.io.IOException;
public class uploadServlet extends HttpServlet
{
//The following six variables represent tokens
//used to parse the content submitted in the
//multi-part form.
private static final String CONTENT_TYPE
= "text/html; charset=windows-1252";
private static final String DATA_INDICATOR
= "Content-Disposition: form-data; name=\"";
private static final String FIELD_INDICATOR = "name=\"";
private static final String FILE_INDICATOR = "filename=\"";
private static final String FILE_MIME_INDICATOR = "Content-Type:";
private String REQUEST_DELIMITER = null;
//HashMap to store field names and values
//passed from the calling form.
private HashMap fields = new HashMap();
/**
* Check to see if the line passed in
* contains a field type indicator.
*/
private boolean checkField(String p_value)
{
boolean v_return = false;
if(p_value.indexOf(FIELD_INDICATOR) != -1)
{
v_return = true;
}
return v_return;
}
/**
* Check to see if the line passed in
* contains a file type indicator.
*/
private boolean checkFile(String p_value)
{
boolean v_return = false;
if(p_value.indexOf(FILE_INDICATOR) != -1)
{
v_return = true;
}
return v_return;
}
/**
* Check to see if the line passed in
* is a delimiter line. Delimiters
* separate the different sections
* of multipart form.
*/
private boolean checkDelimiter(String p_value)
{
boolean v_return = false;
if(p_value.indexOf(REQUEST_DELIMITER) != -1)
{
v_return = true;
}
return v_return;
}
/**
* Check to see if the line passed in
* contains the file content type indicator.
*/
private boolean checkContentIdicator(String p_value)
{
boolean v_return = false;
if(p_value.indexOf(FILE_MIME_INDICATOR) != -1)
{
v_return = true;
}
return v_return;
}
/**
* Check to see if the line passed in
* contains a data value type indicator.
*/
private boolean checkDataIndicator(String p_value)
{
boolean v_return = false;
if(p_value.indexOf(DATA_INDICATOR) != -1)
{
v_return = true;
}
return v_return;
}
/**
* Returns the value of field stored in the
* 'fields' HashMap based upon the key passed
* in to the method. The key relates directly
* to the parameter names in the passing form.
*/
private String getFieldValue(String p_name)
{
String v_value = new String();
try{
v_value =(String)fields.get(p_name);
}catch(Exception e)
{
v_value = "";
}
return v_value.trim();
}
/**
* Parse the value of the parameter passed in
* on line containing parameter/field data.
*/
private String parseFieldValue(ServletInputStream in) throws Exception
{
byte[] line = new byte[128];
int i = 0;
StringBuffer fieldValue = new StringBuffer(128);
String newline;
newline = new String();
while (i != -1 && !checkDelimiter(newline)) {
i = in.readLine(line, 0, 128);
newline = new String(line, 0, i);
if (!checkDelimiter(newline)){
fieldValue.append(newline);
}
}
return fieldValue.toString();
}
/**
* Parse the value of the parameter name in
* on line containing parameter/field data.
*/
private String parseFieldName(String p_name)
{
int pos = 0;
String v_content = new String();
pos = p_name.indexOf(FIELD_INDICATOR);
if (pos > 0){
v_content = p_name.substring(pos+6, p_name.length()-3);
}
return v_content;
}
/**
* Parse the value of the content type passed in
* on line containing file content data.
*/
private String getContentType(String p_name)
{
int pos = 0;
String v_content = new String();
pos = p_name.indexOf(":");
if (pos != -1){
v_content = p_name.substring(pos+1,p_name.length());
}
return v_content;
}
/**
* Place field name and value in the "fields" HashMap
* for easy retrieval later. The field (or parameter)
* name becomes the hashmap key.
*/
private void setFieldValue(String p_name,
String p_value)
{
fields.put(p_name,p_value);
}
/**
* This method takes the input stream containing the
* multipart form data and processes it. Processing
* means separating out the different items such as
* parameter values and passes off the binary data
* to a handler object that loads the data into a
* database field. The method returns a success or
* failure value in the form of a String.
*/
private String processInputStream(ServletInputStream in) throws Exception
{
String v_return_value = new String();
String v_content = new String();
String newline = new String();
ImageLoader imageLoader = new ImageLoader();
try{
byte[] line = new byte[256];
int i = in.readLine(line, 0, 256);
//First line is delimiter/boundary
REQUEST_DELIMITER = new String(line, 0, i);
while (i != -1) {
newline = new String(line, 0, i);
//System.out.println(newline);
if (checkDataIndicator(newline)) {
if (checkFile(newline)) {
i = in.readLine(line, 0, 256);
newline = new String(line, 0, i);
//System.out.println(newline);
while (!checkContentIdicator(newline))
{
i = in.readLine(line, 0, 256);
newline = new String(line, 0, i);
}
if (checkContentIdicator(newline)){
v_content = getContentType(newline);
}
//Consume extra line
i = in.readLine(line, 0, 256);
//newline = new String(line, 0, i);
//System.out.println(newline);
if (getFieldValue(ApplicationConstants.PARAM_NAME).length() > 0){
imageLoader.insertImageData(in,
Integer.parseInt(getFieldValue("p_id")),
getFieldValue("p_label"),
getFieldValue("p_user"),
v_content,
REQUEST_DELIMITER);
}else
{
v_return_value="Photo Upload failed: ID is required.";
break;
}
}else{
String fieldName =parseFieldName(newline);
//System.out.println("fieldName:" + fieldName);
// blank line
i = in.readLine(line, 0, 128);
setFieldValue(fieldName,parseFieldValue(in));
}
}
//read the next line.
i = in.readLine(line, 0, 128);
}
if (imageLoader.existErrors())
{
v_return_value = "Photo Upload failed: "+imageLoader.getErrors();
}
}catch(Exception e)
{
v_return_value = "Photo Upload failed: "+e.getMessage();
}finally
{
in.close();
}
if (v_return_value.length() < 1)
{
v_return_value = "Photo Uploaded Successfully.";
}
return v_return_value;
}
private void displayResponse(PrintWriter out,
String p_text)
{
int v_error = p_text.indexOf("failed");
out.println("<HTML>");
out.println("<BODY BGCOLOR=\"C0C0C0">");
out.println("<STYLE>");
out.println(".errorText {color: red;");
out.println(" font-style: italic;");
out.println(" font-weight: bold;}");
out.println(" .greenText {color:#006600;}");
out.println("</STYLE>");
out.println("<big>Upload Juvenile Master Photo</big><br>");
if (v_error >= 0)
{
out.println("<div class=\"errorText">");
}else
{
out.println("<div class=\"greenText">");
}
out.println(p_text);
out.println("</div>");
out.println("</BODY>");
out.println("</HTML>");
}
public void init(ServletConfig config) throws ServletException
{
super.init(config);
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String v_upload_result = new String();
PrintWriter out = response.getWriter();
ServletInputStream in = null;
response.setContentType(CONTENT_TYPE);
try{
in = request.getInputStream();
v_upload_result = processInputStream(in);
}catch(Exception e)
{
v_upload_result="Image Upload failed: "+e.getMessage();
}finally
{
in.close();
}
displayResponse(out,v_upload_result);
out.close();
}
}
4:08:45 PM
|