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: <[EMAIL PROTECTED]> For additional commands, e-mail: <[EMAIL PROTECTED]>