DBPrism Servlet Engine
How DBPrism CMS is integrated with Cocoon Cache System for accelerating dynamic content generation
Integration flow
  1. A client browser sent a request to DBPrism CMS like this http://www.dbprism.com.ar/Documentation/index.html, this request is routed to an Apache WebServer, for example, which is listening into the default port (80).
  2. The Apache WebServer is configured with mod_proxy to route every url of the site www.dbprism.com.ar starting at / to a Servlet container listening on the port 8888. mod_proxy rewrites the previous one url to a new url like this http://localhost:8888/dbprism/doc/Documentation/index.html.
  3. In this example an OC4J is configured with the dbprism.ear application file which defines the mount point /dbprism/ with an instance of Cocoon.
  4. OC4J pass the url to zone interpreted by Cocoon, Cocoon receives an url without the mount point (/dbprism/), that is /doc/Home.html. At this point Cocoon check for this page in his own cache and if the page is valid return the cached version, if not executes the steps 5 and 6.
  5. Based on the sitemap.xmap definition Cocoon resolves the url /doc/Documentation/index.html as a content generated by DBPrism, then DBPrism receives a request for an stored CMS page named /Documentation/index.html, note that doc was extracted because is used as Cocoon sub-sitemap information (information for mounting a sub directory into the main sitemap).
  6. A CMS stored procedure is executed to retrieve the specific page (/Documentation/index.html) and the page is returned to DBPrism engine, then the page returns in the inverted path passing to Cocoon (which formats the page and stores it in his Cache System), OC4J and Apache, finishing in the client browser.
  7. At this point a Content Writer (like me) writes a new version of the document /Documentation/index.xml (CMS version of /Documentation/index.html), and upload it using Ftp or with JDeveloper using the WebDAV explorer.
  8. The table CMS_DOCS which stores the XML document has a trigger after update which sends an http message to the Cocoon engine invalidating the cached content. The invalidation message is sent to the Container directly (not through Apache) and Cocoon routes the url /dbprism/x-dbprism-cache-invalidate to an XSP page which parses this message and contacts the DBPrism External Invalidator Server to mark the page /Documentation/index.html as no longer valid. Note that the post message includes an username and password encode in a Base64 form. An example of invalidation message is showed below. After this step a new request comming from the client browser will be routed following the steps 4,5 and 6.
CMS Triggers

The triggers below are examples of invalidation triggers and are implemented on createTriggers.sql file, the purpose of these triggers are to control the cache coherence betwen Cocoon External Cache Invalidator Server and the content of the CMS tables .

CMS_DOCS$ESI$INSERT - CMS_DOCS$ESI$UPDATE - CMS_DOCS$ESI$DELETE

These triggers are fired when a Content Writer update, insert or delete information on the table CMS_DOCS, content assets. These triggers call to the stored procedures wich sent the invalidation messages, the code on the stored procedure do not sent the HTTP message directly to the Cocoon Cache System, they post a messsage in a Queue which have a PLSQL code to consume the message and send the ESI invalidation message using UTL_HTTP package.

-- Detects update on cms_docs
create or replace TRIGGER CMS_DOCS$ESI$INSERT AFTER INSERT ON CMS_DOCS 
REFERENCING OLD AS OLD NEW AS NEW 
FOR EACH ROW 
CALL cmsSchemaUtils.invalidateOnInsert(:new.object_id)
/
show errors;

create or replace TRIGGER CMS_DOCS$ESI$UPDATE AFTER UPDATE ON CMS_DOCS 
REFERENCING OLD AS OLD NEW AS NEW 
FOR EACH ROW 
CALL cmsSchemaUtils.invalidateOnUpdate(:new.object_id)
/
show errors;

-- Detects delete on cms_docs
create or replace TRIGGER CMS_DOCS$ESI$DELETE BEFORE DELETE ON CMS_DOCS 
REFERENCING OLD AS OLD NEW AS NEW 
FOR EACH ROW
CALL cmsSchemaUtils.invalidateOnDelete(:old.object_id)
/
show errors;

CREATE OR REPLACE PACKAGE BODY cmsSchemaUtils AS
  -- ....
  PROCEDURE invalidateOnInsert(oid RAW) is
    enqueue_options     dbms_aq.enqueue_options_t;
    message_properties  dbms_aq.message_properties_t;
    message_handle      RAW(16);
    message             esi_msg_typ;
  begin
    -- reserved for future use, see bug 3376491 at metalink
    message := esi_msg_typ(oid,'insert');
    dbms_aq.enqueue(queue_name         => '&admin_user..esi_msg_queue',
                    enqueue_options    => enqueue_options,
                    message_properties => message_properties,
                    payload            => message,
                    msgid              => message_handle);
  end invalidateOnInsert;

  PROCEDURE invalidateOnDelete(oid RAW) is
    enqueue_options     dbms_aq.enqueue_options_t;
    message_properties  dbms_aq.message_properties_t;
    message_handle      RAW(16);
    message             esi_msg_typ;
  begin
    message := esi_msg_typ(oid,'delete');
    dbms_aq.enqueue(queue_name         => '&admin_user..esi_msg_queue',
                    enqueue_options    => enqueue_options,
                    message_properties => message_properties,
                    payload            => message,
                    msgid              => message_handle);
  end invalidateOnDelete;

  PROCEDURE invalidateOnUpdate(oid RAW) is
    enqueue_options     dbms_aq.enqueue_options_t;
    message_properties  dbms_aq.message_properties_t;
    message_handle      RAW(16);
    message             esi_msg_typ;
  begin
    message := esi_msg_typ(oid,'update');
    dbms_aq.enqueue(queue_name         => '&admin_user..esi_msg_queue',
                    enqueue_options    => enqueue_options,
                    message_properties => message_properties,
                    payload            => message,
                    msgid              => message_handle);
  end invalidateOnUpdate;

  PROCEDURE esiMsgAction(message esi_msg_typ) is
    t_path              varchar2(4000);
    BEGIN
      if (message.operation='delete') then
        select anypath into t_path from archived_cms_docs
               where oid=message.oid;
          -- invalidates all the pages under this directory because have incorrect information about the header.
        --DBMS_OUTPUT.PUT_LINE ('esiMsgAction: ' || message.oid ||
        --                                     ' ... ' || message.operation || ' path: '||t_path );
        cache.cms_invalidate_dir(t_path);
      else
        if (message.operation='update') then
          for c in (select r.path tpath from path_view r where
                            sys_op_r2o(extractValue(r.res,'/Resource/XMLRef'))=message.oid) loop
             --DBMS_OUTPUT.PUT_LINE ('esiMsgAction: ' || message.oid ||
             --                                     ' ... ' || message.operation || ' path: '||c.tpath );
             cache.cms_invalidate_page(c.tpath);
          end loop;
        else
          -- insert
          select any_path into t_path from resource_view r where
                 sys_op_r2o(extractValue(res,'/Resource/XMLRef'))=message.oid;
          --DBMS_OUTPUT.PUT_LINE ('esiMsgAction: ' || message.oid ||
          --                                     ' ... ' || message.operation || ' path: '||t_path );
          cache.cms_invalidate_dir(t_path);
        end if;
      end if;
    EXCEPTION
      WHEN others THEN
        --dbms_output.put_line (' ---- Exception on esiMsgAction ---- ');
        null;
  end esiMsgAction;

  PROCEDURE esiMsgCallBack(context  IN  RAW,
                           reginfo  IN  SYS.AQ$_REG_INFO,
                           descr    IN  SYS.AQ$_DESCRIPTOR,
                           payload  IN  RAW,
                           payloadl IN  NUMBER) is
    dequeue_options     dbms_aq.dequeue_options_t;
    message_properties  dbms_aq.message_properties_t;
    message_handle      RAW(16);
    message             esi_msg_typ;
    t_path              varchar2(4000);
    BEGIN
      -- get the consumer name and msg_id from the descriptor
      dequeue_options.msgid := descr.msg_id;
      dequeue_options.consumer_name := descr.consumer_name;
      DBMS_AQ.DEQUEUE(queue_name          =>     descr.queue_name,
                      dequeue_options      =>    dequeue_options,
                      message_properties  =>     message_properties,
                      payload             =>     message,
                      msgid               =>     message_handle);
      commit;
      esiMsgAction(message);
  end esiMsgCallBack;
  -- ....
END cmsSchemaUtils;
A queue is necessary because at the time of inserting a new pages on the CMS_DOCS table, RESOURCE_VIEW do not see the changes (they are not commited yet) so its not possible to know the path (any_path column) of the resource for sending the ESI invalidation message.
This work is licensed under a Creative Commons License . Creative Commons License
(C) 1999-2008 - DBPrism ~ DBPrism CMS | Marcelo F. Ochoa | TANDIL ~ Argentina | 2008-10-07T20:16:39
DBPrism at SourceForgeBuilt with Cocoon2