cocoon-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Pascal Davoust" <davou...@yahoo.com>
Subject [Image stored in db] The way I did it (Was: [Upload] File upload cleanup)
Date Tue, 26 Feb 2002 00:01:42 GMT
Hi all,

It seems that it can be interesting for other Cocooners to know how I
achieved to upload files to a database (nothing magic, but can be tricky),
just for the record. So there it is.
Sorry if this message is terribly long, I tried to be as exhaustive as
possible. This small guidelines don't include info about optimization (like
using a timestamp column to allow the reader to tell the remote browser to
use the locally cached resource instead of downloading it again). And I
still don't know how to clean uploaded files up after having them in the
db...

Hope it can help,
Cocoon rules! :o)

				Pascal.

Configuration:
  Platform: Windows
  JDK: 1.3.0 (yeah, I know, I should upgrade :)
  Servlet engine: Tomcat 4.0.1
  Cocoon: 2.0.1

1) Generic DB-related configuration
===================================
(this is needed for any DB-related processing in Cocoon)

1.1) Make the JDBC driver available
Drop the JAR file(s) for your JDBC driver into the
[Tomcat-dir]/webapps/cocoon/WEB-INF/lib directory, so that Cocoon's
classload can find it.

1.2) JDBC driver declaration:
  In the [Tomcat-dir]/webapps/cocoon/WEB-INF/web.xml file, look for the
<init-param> section. Add a line in the <param-value> node, containing the
fully specified classname of the JDBC driver to use to access your DB.
Example (JDBC driver for SQL Svr from iNetSoftware):
---------------------------------------
    <init-param>
      <param-name>load-class</param-name>
      <param-value>
        <!-- ........ -->
        <!-- For SQL Svr: -->
        com.inet.pool.PoolDriver
        <!-- ........ -->
      </param-value>
    </init-param>
---------------------------------------

1.3) Datasource definition:
  In the [Tomcat-dir]/webapps/cocoon/cocoon.xconf file, look for the
<datasources> node. Add your JDBC datasource definition in it.
Example:
---------------------------------------
  <datasources>
    <!-- ........ -->
    <jdbc name="image-library" logger="core.datasources.image-library">
      <auto-commit>false</auto-commit>
      <!-- This URL is driver-dependant, consult the driver docs -->

<dburl>jdbc:inetpool:inetdae7:localhost:1433?database=ImageLibrary</dburl>
      <user>sa</user>
      <password></password>
    </jdbc>
    <!-- ........ -->
  </datasources>
---------------------------------------
Assumption: a database named "ImageLibrary" exists in your RDBMS.

These two steps are very-well documented in the samples and documentation
(though I did find it a bit difficult to find my way around :).

1.4) Table schema for image storage
Create a table in your "ImageLibrary" database, named "Images" with a first
column named "Id" (non nullable, and set as the primary key for the table),
and a second, "Image", that can contain your image. This type is highly
dependant on the RDBMS you're using and the capabilities of your JDBC
driver.
Example (SQL Svr, in SQL Query Analyzer):
---------------------------------------
  create table Image (Id integer not null primary key, Image image null)
---------------------------------------
Example (Oracle, in svrmgrl or sqlplus):
---------------------------------------
  create table Image (Id integer not null primary key, Image blob null);
---------------------------------------


2) HTML form for uploading
==========================

2.1) Writing the HTML form:
Easy enough for HTML-knowledgeable people (have a look at
.../cocoon/docs/xsp/upload.xsp, too, this example is directly derived from
it, though it's plain HTML file):
Example (filename: 'upload-form.html'):
---------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<html>
  <head><title>This form allows you upload files</title></head>
  <body>
    <form method="post"
          enctype="multipart/form-data"
          action="do-upload">
      <p>
        File: <input type="file"
                     name="uploaded-file"
                     size="50" />
      </p>
      <p>
        <input type="submit"
               value="Upload File" />
      </p>
    </form>
  </body>
</html>
----------------------------------------

2.2) HTML form serving:
Make it available to the browsers by declaring a match in your sitemap.
Example (this one serves any HTML page):
Pre-requisite: have a declared HTML serializer
----------------------------------------
    <map:pipeline>
      <!-- ........ -->
      <map:match pattern="**.html">
        <map:generate src="{1}.html"/>
        <map:serialize type="html"/>
      </map:match>
      <!-- ........ -->
    </map:pipeline>
----------------------------------------

2.3) Upload mecanism check:
Now, you can check that your form displays properly by requesting it in your
browser: http://<your-URL-to-your-sitemap>/upload-form.html
You can also check that uploading is already working (yes, it is!). Provide
a (preferably small) file using the form displayed in your browser, and
click the Upload button. Since the targetted URL doesn't exist yet, you'll
get a NullPointerException... but have a look in the
[Tomcat-dir]/work/localhost/cocoon/cocoon-files/upload-dir: your file is in
there!

+++ What happened:
Tomcat/Cocoon recognized that the request contained binary information, of
type "file" (see the <input type="file"> tag of the form). It then creates
and opens a file in the configured directory for uploads (part of Cocoon's
runtime context), and writes all the binary information it receives from the
browser into it. Finally, the server makes the local (uploaded) filename
available as the value for the "uploaded-file" request parameter. It occurs
for every part of the request that's a file (you can upload more than one
file at a time, obviously).
Also have a look at the Tomcat console, it's really helpful to understand
how it decodes the MIME-multipart encoded request.
Easy enough until now, right?


3) Database upload
==================

3.1) Declaring the DB uploader action
The trick is here to use an action that allows to insert a record in a
database. First, we declare it (in the <actions> node of the sitemap).
Example:
----------------------------------------
  <map:actions>
    <!-- ........ -->
    <map:action name="add-record" logger="sitemap.action.add-record"
         src="org.apache.cocoon.acting.DatabaseAddAction"/>
    <!-- ........ -->
  </map:actions>
----------------------------------------

3.2) Configuring the DB uploader action
This action needs an XML file as a configuration to tell it what the table
to create records in, the columns it contains, their datatypes and the
parameter it will use to fill each of them.
Ours is quite simple.
Example (filename: 'image-record.xml'):
----------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <connection>image-library</connection>
  <table name="Images">
    <keys>
      <key param="id" dbcol="Id" type="int" mode="manual"/>
    </keys>
    <values>
      <value param="uploaded-file" dbcol="Image" type="binary"/>
    </values>
  </table>
</root>
----------------------------------------
Note: I don't use the "image" type here, because it doesn't allow to use png
files. It uses the Cocoon ImageDirectory facility which complains about the
png file not being a valid Gif or JPEG file (uh, really? :o).
Note 2: the content of this descriptor file is explained a bit further down.

3.3) Defining the target for upload
In the form, we defined the form action URI, remember? We have now to define
it in the sitemap. It obviously uses the previously defined action and the
descriptor file we just created.
Example:
----------------------------------------
    <map:pipeline>
      <!-- ........ -->
      <map:match pattern="do-upload">
        <map:act type="add-record">
          <map:parameter name="descriptor"
                         value="file://image-record.xml"/>
        </map:act>
      </map:match>
      <!-- ........ -->
    </map:pipeline>
----------------------------------------

3.4) Checking DB upload mecanism
Now, you can try to upload again. Before doing so, make sure your table is
empty (select * from <tablename> should return no record), it will prevent
any misleading behaviour due to existing records. Then select a file, click
Upload, and check again in your db: there it is!

+++ What happened:
The action "add-record" uses the 'uploaded-file' parameter (which contains
the name of the local uploaded file) to fill in the 'Image' column.
The 'Id' column is automatically generated, first because we declared it as
the key for the table (it appears in the <keys> node of the image-record.xml
file), and second because it's declared as a manually set key (the
'mode="manual"' attribute). When it's manual, the action detects what's the
maximum value for that column among the existing records (starting at 0 if
none exists), increments it and uses it as the key for the new record.


4) Database download
====================

4.1) Database reader declaration
We got the image in the database. Fine. Now, we probably want to read it
back and serve it as an image file. We'll use the Database Reader from the
standard Cocoon distro, and we have to declare it in the sitemap. Don't
forget to specify the datasource to use (the <use-connection> parameter).
Example:
----------------------------------------
   <map:readers>
      <!-- ........ -->
      <map:reader name="db-img-resource" logger="sitemap.reader.img-db"
                 src="org.apache.cocoon.reading.DatabaseReader">
        <use-connection>image-library</use-connection>
      </map:reader>
      <!-- ........ -->
   </map:readers>
----------------------------------------

4.2) Setting up the image database reader
Then declare a target in your pipeline to use serves that resource. It of
course uses the declared resource reader, and sets the reader's parameters:
it obviously needs to know from which table we need to get the information
from, as well as the column to stream data from, and the key of the record
to look for. It's set using the <map:parameter> element. A common mistake is
to forget to set the "mime-type" attribute of the reader (I did it at least
4 times myself before I got it right in my head :o): you'll get a
NullPointerException, and only the Cocoon error log file will give you a
hint about what happened.
Example:
----------------------------------------
   <map:pipeline>
      <!-- ........ -->
      <map:match pattern="img/*.jpg">
        <map:read type="db-img-resource" src="{1}" mime-type="image/jpeg">
          <map:parameter name="table" value="Images"/>
          <map:parameter name="image" value="Image"/>
          <map:parameter name="key" value="Id"/>
        </map:read>
      </map:match>

      <map:match pattern="img/*.gif">
        <map:read type="db-img-resource" src="{1}" mime-type="image/gif">
          <map:parameter name="table" value="Images"/>
          <map:parameter name="image" value="Image"/>
          <map:parameter name="key" value="Id"/>
        </map:read>
      </map:match>

      <map:match pattern="img/*.png">
        <map:read type="db-img-resource" src="{1}" mime-type="image/x-png">
          <map:parameter name="table" value="Images"/>
          <map:parameter name="image" value="Image"/>
          <map:parameter name="key" value="Id"/>
        </map:read>
      </map:match>
      <!-- ........ -->
   </map:pipeline>
----------------------------------------

4.3) Checking image downloading from database
You're now ready to download any image you may have uploaded to the database
before. The only constraint here is that you have to specify the correct
file extension for the file you uploaded. I'm working on that part to make
it smarter.
Example:
   http://<your-URL-to-your-sitemap>/img/1.jpg
if the first image you uploaded is a JPEG file.


There you go! Now, it's up to you to make it smarter... ;o)


Note for 3.3): after the action has taken place, the newly created record
key value is available in the parameter which name has been specified in the
descriptor entry for it ("Id" here). It works because the identifier has
been computed by the action itself. It allows to do this kind of stuff:
          <map:redirect-to uri="img/{Id}.jpg"/>
(add this in the body of the action, before the </map:act>, and try
uploading a JPEG file...).


_________________________________________________________
Do You Yahoo!?
Get your free @yahoo.com address at http://mail.yahoo.com


---------------------------------------------------------------------
Please check that your question has not already been answered in the
FAQ before posting. <http://xml.apache.org/cocoon/faqs.html>

To unsubscribe, e-mail: <cocoon-users-unsubscribe@xml.apache.org>
For additional commands, e-mail: <cocoon-users-help@xml.apache.org>


Mime
View raw message