Skip to content
OpenCms documentation
OpenCms documentation

Schema versioning

In OpenCms contents are stored as XML documents and for each type of content the structure is defined via an XML schema definition (XSD), short schema. Schema versioning allows to automatically transform already existing contents to an adjusted schema via XSLT.

As projects evolve requirements will change. This also hold for editorial content. Structural changes of existing contents will become necessary. Changes in the order of XML elements, removing elements or adding new elements with default values are handled automatically by the "content correction" mechanism of OpenCms. But more complicated adjustments - like renaming elements, changing element types or nesting existing elements into other elements - can't be handled automatically.

Schema versioning allows to adjust existing XML contents to changed schemata via XSL transformations. All adjustments possible via XSLT can be applied and thus various complicated refactorings of schemata become possible, even if productive content already exists.

In principle, the schema versioning mechanism allows all adjustments that can be expressed with the XML transformation language XSLT. We demonstrate the following three, maybe most interesting, use cases:

  • renaming an existing node
  • changing the type of an existing node
  • moving an exisitng node into a choice element

The "cookbook like" example covers these three cases. We simplify the displayed XML schema and XML file examples in terms of not showing all namespace attributes to get a clearer presentation. We also omit CDATA wrappers for the data (what will make it invalid partly, but easy to read).

We start with a simple article type containing title, text and author information. Here is an existing article and the initial schema. In terms of the schema versioning, this is version 0 of our content and schema.

Initial XML (version 0)

<?xml version="1.0" encoding="UTF-8"?>
<ArticleData>
  <Article language="en">
    <Title>Content section</Title>
    <Author>Some Name</Author>
    <Text name="Text0">
      <links>
        <link name="link0" internal="false" type="A">
          <target>http://www.opencms.org</target>
        </link>
      </links>
      <content><p>Welcome to <a href="%(link0)">OpenCms</a>.</p></content>
    </Text>
  </Article>
</ArticleData>

Initial schema (version 0)

<xsd:schema>
    <xsd:element name="ArticleData" type="OpenCmsArticleData" />
    <xsd:complexType name="OpenCmsArticleData">
        <xsd:sequence>
            <xsd:element name="Article" type="OpenCmsArticle" minOccurs="0" maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="OpenCmsArticle">
        <xsd:sequence>
            <xsd:element name="Title" type="OpenCmsString" />
            <xsd:element name="Author" type="OpenCmsString" />
            <xsd:element name="Text" type="OpenCmsHtml" minOccurs="0" />
        </xsd:sequence>
        <xsd:attribute name="language" type="OpenCmsLocale" use="required" />
    </xsd:complexType>
</xsd:schema>

From the initial schema, we move to an adjusted version, where

  • the "Title" element is renamed to "Headline"
  • the "Author" element now provides a choice between a "Name" (where the content of the initial "Author" nodes should be moved to), or a "LinkToPerson" (where we can link to a content with a persons contact information)
  • the "Text" should be saved as OpenCmsString, not as OpenCmsHtml anymore.

Since we want the existing contents automatically transformed, we can not only exchange the schema, we have to use the versioning mechanism and provide an XSL transformation to get our existing contents to the latest version. In our case, this will be version 1.

The adjusted schema that links to the shown XSL transformation will manage automatic content adjustments to version 1 and our initial article will be accessible directly as it where generated with the adjusted schema already.

The transformed article, adjusted schema and the XSL transformation are displayed below (again with simplifications). The interesting spots concerning the versioning mechanism are commented. 

Transformed XML (version 1)

<?xml version="1.0" encoding="UTF-8"?>
<!-- The version of the data is added -->
<ArticleData version="1">
  <Article language="en">
    <Heading>Content section</Heading>
    <Author>
        <Name>Some Name</Name>
    </Author>
    <Text><p>Welcome to <a href="http://www.opencms.org">OpenCms</a>.</p></Text>
  </Article>
</ArticleData>

Adjusted schema (version 1)

<!-- The schema version has to be added here -->
<xsd:schema version="1">
    <xsd:element name="ArticleData" type="OpenCmsArticleData" />
    <xsd:complexType name="OpenCmsArticleData">
        <xsd:sequence>
            <xsd:element name="Article" type="OpenCmsArticle" minOccurs="0" maxOccurs="unbounded" />
        </xsd:sequence>
         <xsd:attribute name="version" type="xsd:int" use="optional" />
    </xsd:complexType>
    <xsd:complexType name="OpenCmsArticle">
        <xsd:sequence>
            <xsd:element name="Heading" type="OpenCmsString" />
            <xsd:element name="Author" type="OpenCmsAuthorData" />
            <xsd:element name="Text" type="OpenCmsString" minOccurs="0" />
        </xsd:sequence>
        <xsd:attribute name="language" type="OpenCmsLocale" use="required" />
    </xsd:complexType>

    <xsd:annotation>
        <xsd:appinfo>
            <!-- We link to the XSLT file that is used to transform the existing contents of version 0 -->
            <versiontransformation>
                /system/modules/name.of.module/schemas/transformations/article.xslt
            </versiontransformation>
        </xsd:appinfo>
    </xsd:annotation>

    <!-- For simplicity, the sub-schema is shown here directly instead of a referenced include file. -->
    <xsd:complexType name="OpenCmsAuthorData">
        <xsd:sequence>
            <xsd:element name="Author" type="OpenCmsAuthor" minOccurs="0" maxOccurs="unbounded"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="OpenCmsAuthor">
        <xsd:choice>
            <xsd:element name="Name" type="OpenCmsString" minOccurs="0" />
            <xsd:element name="LinkToPerson" type="OpenCmsVfsFile" minOccurs="0" />
        </xsd:choice>
        <xsd:attribute name="language" type="OpenCmsLocale" use="optional"/>
    </xsd:complexType>

</xsd:schema>

XSL transformation stylesheet

<!-- This is the file linked in the article's schema. -->
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:opencms="xalan://org.opencms.xml.content.CmsXsltContext"  
  exclude-result-prefixes="opencms" >

 <xsl:output indent="yes" encoding="UTF-8"/>
 <xsl:param name="context" />

<!-- Rename Title to Heading -->
<xsl:template match="/ArticleData/Article/Title">
    <Heading>
        <xsl:value-of select="." />
    </Heading>
</xsl:template>

<!-- Move Author to Author/Name -->
<xsl:template match="/ArticleData/Article/Author">
    <Author>
        <Name>
            <xsl:value-of select="." />
        </Name>
    </Author>
</xsl:template>

<!-- Convert the type of Text from OpenCmsHtml to OpenCmsString -->
<!-- To convert between different OpenCms value types,
     we use a custom conversion function written in Java,
     which can then be used as an extension in Xalan
     (see declaration in the xsl:stylesheet tag above).
     Currently, this is the only transformation function that comes with OpenCms.
-->
<xsl:template match="/ArticleData/Article/Text">
    <xsl:copy-of select="opencms:convertType($context, ., 'OpenCmsHtml', 'OpenCmsString', 'Text')" />
</xsl:template>

<!-- The 'just copy everything else' rule. -->
 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*" />
     </xsl:copy>
 </xsl:template>

</xsl:stylesheet>

All relevant parts for the using the schema versioning mechanism in your template are present in the above example. Here's a short summary of the steps to perform for the first version update:

  1. Rewrite your schema to the new version and add the version="1" as property to the <xsd:schema> node.
  2. Add the node <versiontransformation>/system/modules/my.module/schemas/transformations/my-type.xslt</versiontransformation> under <xsd:appinfo>, where the content should be the path to your XSL transformation file. By convention, the file should reside in your module in a folder schemas/transformations and be named like the content type it transforms contents of.
  3. Add the XSL transformation file that you link to from the schema.

When you again change your schema and need another version of your content you have to:

  1. Rewrite you schema to the new version and increment the version property at the <xsd:schema> node.
  2. Adjust your XSL tranformation file, such that it handles the tranformation from each initial version to the latest version correctly. So for version 2, the transformation of version 0 and version 1 contents must yield correct version 2 contents.
Make sure that your XSL transformation covers all potential transformation cases. This means all contents that conform to any prior schema version must be converted correctly.

The versioning mechanism triggers the same way as the "content correction" mechanism. This means:

  • The transformation is applied automatically to an XML content whenever it is read.
  • When a content is written, it is written in the most recent schema version.

Furthermore, for contents that already have the most recent schema version (checked by the version number), the transformation is not applied.

In essence, this means that you can work with all contents as if they have the most recent schema version immediately. You do not have to touch and rewrite any existing content. This is important especially for developers, e.g. JSP code can rely on the most recent schema version and does not have to handle other versions.

To round up, here are some more details about the mechanism:

  • If the XML content file does not contain a version information, it is assumed to be version 0.
  • If the XSD schema file does not contain a version information, it is assumed to be version 0.
  • The existing "content correction" mechanism is kept "as is".
    • This means that changes that are possible using this mechanism will not require a new version.
    • This is even true in case the content has a versions number, so in the example above you could switch the order of Title and Text without incrementing the version number.