« September 2007 | Main | December 2007 »

November 2007 Archives

November 1, 2007

Specifying null content in your XML Document using nillable and xsi:nil

At times, it is very important to distinguish between empty content and null content in your XML documents. XML Parsers consider text values of empty elements as an empty string, and not null.
For instance, <itemId></itemId> and <itemId/> are both nodes whose text content is an empty string. When an XML Parser
parsers the document containing this element, the parser treats both variants of the itemId element as one with empty string value.
If you wish to explicitly mark the element to have a value of null as opposed to an empty content, you should declare the element in the schema
to be "nillable", signalling that the value for the element can come in as null at runtime.


For instance <xsd:element name="itemId" nillable="true"/>



At runtime, the element has to be explicitly marked with the "xsi:nil="true" to indicate the null content.
For e.g.
<itemId xsi:nil="true"/> or <itemId xsi:nil="true"></itemId>. Any schema aware XML parser will then be able to interpret the element content to have a null value.


But you cannot have xsi:nil="true" for elements that have non-null content. For e.g.
<itemId xsi:nil="true">hello</itemId> is illegal.
But you are free to have attributes on elements that have xsi:nil="true".


Note that "xsi" is  just a prefix - the "nil" attribute comes from the XML Schema instance namespace - http://www.w3.org/2001/XMLSchema-instance. You could define any arbitrary namespace prefix to refer to the "nil" attribute.


Another important point to note is that you cannot define "xsi:nil" on attributes. They can be defined only on elements.
Moreover, it is also illegal to define nillable on element references."ref" and "nillable" are mutually exclusive attributes.
For e.g.


<element ref="ItemId" nillable="true"/> is invalid.


Nillable should be specified on the actual element definition.

November 14, 2007

Removing additional whitespaces in your Elements and Attributes

In case your XML elements contain multiple whitespaces within the text, or leading and trailing the text, you could use normalize-space() function to remove the leading and trailing whitespaces, as well as to combine all adjacent intermediary whitespaces into one single whitespace.


For e.g


Input


<input>   1sp 2sp  3sp   4sp     5sp     3sp   end</input>


XSLT


 <ns1:result>
   <xsl:value-of select="normalize-space(/input)"/>
</ns1:result>


Result


<result>1sp 2sp 3sp 4sp 5sp 3sp end</result>

November 22, 2007

Customizing any BPEL Project Artifact using <customizeDocument>

Tokenization of artifacts in a BPEL Project


This note describes a simple ant task that I had come up with that provides an easy and flexible method to enable migration of Process artifacts while deploying to multiple target environments.


Problem Statement


Most often, the BPEL process projects that you develop contain XSDs and WSDLs that fall into one or more of the following categories.
a) The artifacts themselves are hosted on a dedicated server for the specific environment. [For instance, in the OHS htdocs within the development server] The artifacts are referred from the project using absolute URLs, containing the host, port, and other server specific information.
b) The artifacts include or import other artifacts that are hosted on a dedicated server for the specific environment.
c) The WSDL contains a JNDI location that changes for each environment.
d) The WSDL defines inline schemas that contain imported remote XSDs that again are hosted on the dedicated development server.
If your process exhibits one or more of the above characteristics, then more often that not, you will encounter the challenge of automating the migration of these artifacts at deployment time, especially when you have a medium to large number of projects, and/or if you have multiple target environments to deploy to.
There are multiple approaches that are in prevelance today to tackle this.
a) The laborious error-prone way of manually replacing the URLs/JNDI names for the new environments.
b) Usage of &lt;customize> and &lt;customizeWSDL> ant tasks.


But what will be really useful is a generic customization infrastructure that enables users to tokenize any arbitrary XML based artifact within the BPEL Project that includes, but is not limited to
a) The BPEL file
b) The WSDL file(s)
c) XSDs
d) XSLT
e) Toplink Mappings
The list goes on.


Overview


The above issues are addressed through a new customization ant task, namely customizeDocument. This task is very similar in usage and behaviour to the existing customization tasks shipped with the product.
The task allows token replacements of contents of any element or attribute that can be reached through an XPath expression. The only limitation of the task is within the tokenization of Processing Instructions within your XML based documents. For instance, the Jdeveloper generated XSL Map file contains a PI indicating the URLs of the source and target XSDs for the map. These URLs cannot be tokenized using this task, nor is this supported through any of the existing customization task.



Setup and Usage


To setup this task, perform the following steps.
a) Copy the customtasks.jar available at http://blogs.oracle.com/ramkumarMenon/gems/customtasks.jar into $ORACLE_HOME\integration\lib directory.
b) Open up ant-orabpel.xml in $ORACLE_HOME\bpel\utilities directory and paste the following piece of code into the file within the &lt;project> element.



&lt;path id="custom.tasks.class.path">
    &lt;pathelement location="&lt;absolute path to location of customtasks.jar>"/>
 &lt;/path>
 
  &lt;property name="custom.tasks.class.path" refid="custom.tasks.class.path"/>


&lt;taskdef name="customizeDocument" classname="com.collaxa.cube.ant.taskdefs.customize.document.CustomizeDocument">
    &lt;classpath>
      &lt;pathelement path="${custom.tasks.class.path}"/>
    &lt;/classpath>
&lt;/taskdef> 


The usage of this task is best illustrated through a simple running example.


You have a BPEL process project named �CreatePurchaseOrder� that you have deployed to the �test� environment. After you have performed all the necessary tests, you wish to promote this process to the production environment.
The project contains amongst others, the process WSDL named CreatePurchaseOrderService.wsdl that imports the local Process Schema named PurchaseOrder.xsd.


The WSDL also imports another WSDL named GeneralPOHeader.wsdl that is a shared WSDL amongst multiple BPEL Processes. This WSDL is hosted on the OHS on the �test� environment. The CreatePurchaseOrderService.wsdl also imports an XSD named Addressing.xsd that is again hosted on the OHS on the test environment.
Moreover, each environment is associated with a specific support group � that receives notification on faults and errors within processes deployed to that specific environment. The support team email is specified as a preference witihn the deployment descriptor of the BPEL process. [bpel.xml].


This is how a portion of CreatePurchaseOrderService.wsdl looks like.


&lt;?xml version = '1.0' encoding = 'UTF-8'?>
&lt;definitions name="PurchaseOrderService" targetNamespace="http://xmlns.oracle.com/PurchaseOrderService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:client="http://xmlns.oracle.com/PurchaseOrderService" xmlns:plnk="http://schemas.xmlsoap.org/ws/2003/05/partner-link/">


        &lt;import namespace="http://xmlns.po.com/general" location="http://testsoa.server.com:7780/services/GeneralPOHeader.wsdl" />


        &lt;types>
  &lt;schema xmlns="http://www.w3.org/2001/XMLSchema">
   &lt;import namespace="http://xmlns.oracle.com/CreatePurchaseOrderService" schemaLocation="CreatePurchaseOrder.xsd"/>
   &lt;import namespace="http://xmlns.po.com/addressing" schemaLocation="http://testsoa.server.com:7780/schemas/Addressing.xsd"/>
  &lt;/schema>
 &lt;/types>


         . . . . .       
&lt;/definitions>


And the following is a snippet of bpel.xml.


&lt;?xml version = '1.0' encoding = 'UTF-8'?>
&lt;BPELSuitcase>
   &lt;BPELProcess id="CreatePurchaseOrderService" src="CreatePurchaseOrderService.bpel">
      &lt;partnerLinkBindings>
         &lt;partnerLinkBinding name="client">
            &lt;property name="wsdlLocation">CreatePurchaseOrderService.wsdl&lt;/property>
         &lt;/partnerLinkBinding>
      &lt;/partnerLinkBindings>
      &lt;preferences>
         &lt;property name="supportEmail" encryption="plaintext">test_support_group@clientcompany.com&lt;/property>
      &lt;/preferences>
   &lt;/BPELProcess>
&lt;/BPELSuitcase>


Note the lines highlighted in blue. The intent is to migrate these URLs and preferences to the values applicable for production. The corresponding values in the prod environment are
a) http://prodsoa.server.com:9876/services/GeneralPOHeader.wsdl
b) http://prodsoa.server.com:9876/schemas/Addressing.xsd
c) prod_support_group@clientcompany.com


Note: Steps 1 through 3 are only one of the different ways to manage multiple environments. You could have alternative mechanisms to manage multi-server deployments.


Step 1: Define multiple copies of ant-orabpel.properties, one for each environment - lets say as ant-orabpel_dev.properties, ant-orabpel_test.properties and ant-orabpel_prod.properties. Specify env specific parameters in each of these files.
Step 2: Define the following properties in the ant-orabpel_test.properties.
a) service_host=testsoa.server.com
b) service_ port = 7780
c) support_group_email=test_support_group@clientcompany.com
Define the following properties in the ant-orabpel_prod.properties.
a) service_ host=prodsoa.server.com
b) service_ port = 9876
c) support_group_email=prod_support_group@clientcompany.com
Step 3: Open the build.xml. Change the following line within it.
             &lt;property file="${bpel.home}/utilities/ant-orabpel.properties"/>
             So that it looks like this.
             &lt;property file="${bpel.home}/utilities/ant-orabpel_${deploy_env}.properties"/>
This enables us to pass in a command line argument named deploy_env to the build script and have it import the appropriate ant-orabpel.properties.
For e.g., When you invoke the build for the test environment by typing in ant �Ddeploy_env=test, the build.xml automatically imports the ant-orabpel_test.properties.


Step 4: Open up the compile target block. We will now define the customizations on the process WSDL and the bpel.xml.


&lt;target name="compile">
        &lt;bpelc input="${process.dir}/bpel/bpel.xml" out="${process.dir}/output"
               rev="${rev}" home="${bpel.home}">
        &lt;/bpelc>
    &lt;/target>


Before the bpelc node, add the customizeDocument task.


&lt;target name=�compile�>
   &lt;customizeDocument inFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl� outFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl�>
&lt;/customizeDocument>
   . . . .
&lt;/target>
The above step indicates the intent to customize the CreatePurchaseOrderService.wsdl.
Next, we need to add the nested �setTextContent� task that allows us to set the text of any attribute or element within the WSDL. Use the setTextContent task to specify the element or attribute to be updated, together with the value that it should be updated with.


&lt;target name=�compile�>
   &lt;customizeDocument inFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl� outFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl�>
   &lt;setTextContent select=�/wsdl:definitions/wsdl:import/@location� textContent=�http://${service_host}:${service_port}/services/GeneralPOHeader.wsdl�/>
&lt;/customizeDocument>
   . . . .
&lt;/target>


The �select� attribute on the customizeDocument indicates a qualified XPath expression that points to the node within the process WSDL that needs to be customized. In this example, it points to the location attribute of the WSDL import. The �textContent� attribute will hold the text that will be set within the node. In this example, it points to the absolute URL of the imported WSDL. You can also specify the tokens in the URL � The build script will automatically subsitute them with the actual values when it is executed. For instance, if the build were executed with the �Ddeploy_env=test or prod, the appropriate values of service_host and service_port will be substituted from the ant-orabpel_test/prod.properties.


Next, we shall customize the XSD import within the WSDL. For this, add one more setTextContent task within the customizeDocument task. The semantics of this is identical to the earlier step.


&lt;target name=�compile�>
   &lt;customizeDocument inFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl� outFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl�>
   &lt;setTextContent select=�/wsdl:definitions/wsdl:import/@location� textContent=�http://${service_host}:${service_port}/services/GeneralPOHeader.wsdl�/>
   &lt;setTextContent select=�/wsdl:definitions/wsdl:types/xsd:schema/xsd:import/@schemaLocation� textContent=�http://${service_host}:${service_port}/schemas/Addressing.xsd�/>
&lt;/customizeDocument>


   . . . .
&lt;/target>


The definition of the customization for the WSDL and XSD is almost complete. The missing step is the namespace URI mappings for the prefixes used within the select expressions. In this case, there are two prefixes that need to be mapped to a namespace URI.
a) The �xsd� namespace prefix
b)  The �wsdl� namespace prefix.
These prefixes are declared as properties within the same build file.
Declare two properties, one for each of the namespace URI, as below at the global level within the build.xml [i.e. as a child element of the &lt;project> element. The name of the properties should be  �prefix.� Followed by the actual prefix. The value attribute contains the namespace URI for the prefix.


    &lt;property name="prefix.wsdl" value="http://schemas.xmlsoap.org/wsdl/"/>
    &lt;property name="prefix.xsd" value="http://www.w3.org/2001/XMLSchema"/>



Next, we specify the customization required for the bpel.xml.
Add another &lt;customizeDocument> task within the compile block. We need to add another one since we are customizing a different document.


&lt;project   �..>
     . . . . .
     &lt;property name="prefix.wsdl" value="http://schemas.xmlsoap.org/wsdl/"/>
     &lt;property name="prefix.xsd" value="http://www.w3.org/2001/XMLSchema"/>


     &lt;target name=�compile�>
       &lt;customizeDocument inFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl� outFile=�${process.dir}/bpel/CreatePurchaseOrderService.wsdl�>
      &lt;setTextContent select=�/wsdl:definitions/wsdl:import/@location� textContent=�http://${service_host}:${service_port}/services/GeneralPOHeader.wsdl�/>
     &lt;setTextContent select=�/wsdl:definitions/wsdl:types/xsd:schema/xsd:import/@schemaLocation� textContent=�http://${service_host}:${service_port}/schemas/Addressing.xsd�/>
&lt;/customizeDocument>


   &lt;customizeDocument inFile=�${process.dir}/bpel/bpel.xml� outFile=�${process.dir}/bpel/bpel.xml�>
     &lt;setTextContent select=�/BPELSuitcase/BPELProcess/preferences/property[@name=�supportEmail
�]� textContent=�${support_group_email}�/>
&lt;/customizeDocument>
   . . . .
&lt;/target>


Note that the select expression used for this customization does not need any namespace prefixes. This is attributed to the fact that bpel.xml by itself is an unqualified document.


This completes the customization features provided by this task.


Note that the customizeDocument task can be used to customize any bpel process project artifact, although not illustrated in this document.

November 26, 2007

Miscellaneous Ant tasks for Managing the BPEL Server

The serverAdmin ANT task

This news item describes a simple ant task that I had come up named serverAdmin that allows you to
  1. Selectively undeploy BPEL Processes
  2. Selectively purge BPEL process instances.
  3. Cancel and Recover Invocation Messages
  4. Cancel and Recover Callback Messages
  5. Get one or more domain configuration properties
  6. Set one or more domain configuration properties
  7. Fetch the latest domain log from the Server
  8. Create/delete a domain on the BPEL server

Setup and Usage

To setup this task, perform the following steps.
  1. Copy the admintasks.jar from http://blogs.oracle.com/ramkumarMenon/gems/admintasks.jar into $ORACLE_HOMEintegrationlib directory.
  2. Open up ant-orabpel.xml in $ORACLE_HOMEbpelutilities directory and paste the following piece of code into the file within the &lt;project> element.
&lt;path id="admin.tasks.class.path">
      &lt;pathelement location="${bpel.home}/../lib/admintasks.jar"/>
  &lt;/path>
  &lt;property name="admin.tasks.class.path" refid="admin.tasks.class.path"/>
  &lt;taskdef name="serverAdmin" classname="com.collaxa.cube.ant.taskdefs.ServerAdmin">
    &lt;classpath>
      &lt;pathelement path="${admin.tasks.class.path}"/>
    &lt;/classpath>
  &lt;/taskdef>
Illustrative Command Syntax
      &lt;serverAdmin  providerURL="opmn:ormi://&lt;hostName>:&lt;opmnRequestPort>:&lt;oc4jInstanceName>/orabpel" userName="oc4jadmin" password="&lt;pwdforoc4jadmin>">
       &lt;createDomain domainName="testDomain"/>
       &lt;deleteDomain domainName="testDomain"/>
       &lt;manageDomain domain="default">
              &lt;undeployProcesses select="BPELProcess.*"  type="all" revision="all"/>
              &lt;undeployProcesses select="TestXPath"  type="retired" revision="all"/>
             &lt;undeployProcesses select="Sample.*" type="all" revision="2.0"/>
             &lt;undeployProcesses select="SampleProcess" type="active" revision="1.0"/>
         &lt;!--example dateTime format "yyyy-MM-dd'T'HH:mm:ss Z"   "1994-11-05T08:15:30 -0800-->
         &lt;!--example date format "yyyy-MM-dd"   "1994-11-05"-->
          &lt;purgeInstances select="TestXPath" type="all" fromDateTime="2007-10-29T11:21:03 -0800" toDateTime="2007-10-31T13:21:03 -0800"/>
          &lt;purgeInstances select="TestJMS" type="closed.stale" fromDateTime="2007-10-29T11:21:03 -0800" toDateTime="2007-10-31T13:21:03 -0800"/>
             &lt;getProperties propertyNames="dspMinThreads,dspMaxThreads,syncMaxWaitTime"/>
              &lt;setProperty name="dspMinThreads" value="5"/>
              &lt;cancelInvocations select="PurchaseOrderProcess" revision="1.0" fromDateTime="2007-11-05T17:45:56 -0800" toDateTime="2007-11-05T17:45:58 -0800"/>
              &lt;recoverInvocations select="OrderCreationProcess" revision="1.0" fromDateTime="2007-11-05T17:21:10 -0800" toDateTime="2007-11-05T17:21:15 -0800"/>
              &lt;cancelCallbacks select="OrderCreationProcess" revision="2.0" fromDate="2007-11-05" toDate="2007-11-06"/>
              &lt;recoverCallbacks select="OrdeUpdateProcess" revision="2.0" fromDate="2007-11-05" toDate="2007-11-06"/>
              &lt;printLog outFile="D:\\temp\\latest_domain.log" lineCount="2000"/>
          &lt;purgeInstances select="all" type="closed.stale" fromDate="2007-10-29" toDate="2007-10-31"/>
          &lt;purgeInstances fromDate="2007-10-29" toDate="2007-10-31"/>
          &lt;purgeInstances select="SampleProcess" type="closed.completed"  toDateTime="2007-10-31"/>
          &lt;purgeInstances select="SampleProcess2" fromDateTime="2007-10-29T11:21:03 -0800"/>
          &lt;purgeInstances select="TestXPath" type="closed.stale" fromDate="2007-10-29" />
         &lt;purgeInstances/>
        &lt;/manageDomain>
      &lt;/serverAdmin>
   

Short Description

UnDeployProcesses subtask:-
The select argument for "undeployProcesses" subtask accepts both exact process names, as well as java regex expressions. For instance, the above example shows undeployment for all processes whose name starts with the string "BPELProcess". All arguments are mandatory.If you wish to specify "all" values for one or more arguments, specify them in the format &lt;argName>="all". For instance, you can specify type="all" or revision="all" or select="all" to indicate selection of all process states, all revisions or all processes.
PurgeInstances subtask :-
If fromDateTime/fromDate is omitted, it purges every matching instance upto and including the "toDateTime/toDate". Note that in case you specify a date as opposed to a dateTime, the time is defaulted to the beginning of the date. [i.e. 12 AM] If the "toDateTime/toDate" is omitted, it purges every matching instance till the present. If both are specified, the task purges all matching instances including and between the given timespan. If both are omitted, the task purges all matching instances irrespective of the time. The dateTime has to be specified in the format given in the above example. The "type" attribute, if omitted, is equivalent to "all". The other values are
  1. closed.aborted
  2. closed.cancelled
  3. closed.completed
  4. closed.faulted
  5. closed.stale
  6. closed.pendingCancel
  7. initiated
  8. open.running
  9. open.faulted
  10. open.suspended
Each serverAdmin task can be used to administer a different BPEL runtime. Within each serverAdmin, you can provide multiple &lt;manageDomain> tasks, one for each domain to purge and undeploy processes and instances in each domain.
Print Log Subtask
Prints the domain log
SetProperty subtask
Sets a domain property. e.g. dspMaxThreads
GetProperties subtask
Gets the property values for the given set of domain properties.[comma separated]
Recovery/Cancellation/Deletion of Invocation or callback subtasks
Recovers, deletes or cancels invocations/callback. Arguments to this task are the processName, revision, the from/to date or dateTimes in the given format as specified in the example. [yyyy-MM-dd'T'HH:mm:ss Z or yyyy-MM-dd]
Create Domain subtask
Creates a domain with the given name.
Delete Domain subtask
Deletes the domain with the given name. Notes: Ensure that BPEL_HOME and ORACLE_HOME are properly set. &lt;p/> Sample Build File
&lt;?xml version="1.0" encoding="iso-8859-1"?>
&lt;project name="ServerAdminBuild" default="deploy" basedir=".">
    &lt;!--=============================-->
    &lt;!-- Process deployment targets  -->
    &lt;!--=============================-->
    &lt;!-- Set bpel.home from developer prompt's environment variable BPEL_HOME -->
    &lt;condition property="bpel.home" value="${env.BPEL_HOME}">
        &lt;available file="${env.BPEL_HOME}/utilities/ant-orabpel.xml"/>
    &lt;/condition>
    &lt;!-- If bpel.home is not yet using env.BPEL_HOME, set it for JDev -->
    &lt;property name="bpel.home" value="${oracle.home}/integration/bpel"/>
    &lt;!-- First override from build.properties in process.dir, if available -->
    &lt;property file="${process.dir}/build.properties"/>
    &lt;!-- import custom ant tasks for the BPEL PM -->
    &lt;import file="${bpel.home}/utilities/ant-orabpel.xml"/>
    &lt;target name="adminTasks">
      &lt;serverAdmin  providerURL="opmn:ormi://server.host.com:6004:oc4j_soa/orabpel" userName="oc4jadmin" password="welcome1">
        &lt;manageDomain domain="default">
              &lt;undeployProcesses select="BPELProcess.*"  type="all" revision="all"/>
             &lt;getProperties propertyNames="dspMinThreads"/>
              &lt;setProperty name="dspMinThreads" value="5"/>
              &lt;cancelInvocations select="PurchaseOrderProcess" revision="1.0" fromDateTime="2007-11-05T17:45:56 -0800" toDateTime="2007-11-05T17:45:58 -0800"/>
              &lt;recoverlInvocations select="OrderCreationProcess" revision="1.0" fromDateTime="2007-11-05T17:21:10 -0800" toDateTime="2007-11-05T17:21:15 -0800"/>
              &lt;cancelCallbacks select="OrderCreationProcess" revision="2.0" fromDate="2007-11-05" toDate="2007-11-06"/>
              &lt;recoverCallbacks select="OrdeUpdateProcess" revision="2.0" fromDate="2007-11-05" toDate="2007-11-06"/>
              &lt;printLog outFile="D:\\temp\\latest_domain.log" lineCount="2000"/>
              &lt;undeployProcesses select="TestXPath"  type="retired" revision="all"/>
             &lt;undeployProcesses select="Sample.*" type="all" revision="2.0"/>
             &lt;undeployProcesses select="SampleProcess" type="active" revision="1.0"/>
         &lt;!--example dateTime format "yyyy-MM-dd'T'HH:mm:ss Z"   "1994-11-05T08:15:30 -0800-->
         &lt;!--example date format "yyyy-MM-dd"   "1994-11-05"-->
          &lt;purgeInstances select="TestXPath" type="all" fromDateTime="2007-10-29T11:21:03 -0800" toDateTime="2007-10-31T13:21:03 -0800"/>
          &lt;purgeInstances select="TestJMS" type="closed.stale" fromDateTime="2007-10-29T11:21:03 -0800" toDateTime="2007-10-31T13:21:03 -0800"/>
          &lt;purgeInstances select="all" type="closed.stale" fromDate="2007-10-29" toDate="2007-10-31"/>
          &lt;purgeInstances fromDate="2007-10-29" toDate="2007-10-31"/>
          &lt;purgeInstances select="SampleProcess" type="closed.completed"  toDateTime="2007-10-31"/>
          &lt;purgeInstances select="SampleProcess2" fromDateTime="2007-10-29T11:21:03 -0800"/>
          &lt;purgeInstances select="TestXPath" type="closed.stale" fromDate="2007-10-29" />
         &lt;purgeInstances/>
        &lt;/manageDomain>
      &lt;/serverAdmin>
    &lt;/target>
&lt;/project>

About November 2007

This page contains all entries posted to Ramkumar Menon's Blog in November 2007. They are listed from oldest to newest.

September 2007 is the previous archive.

December 2007 is the next archive.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type and Oracle