Migrating and modifying nodetypes

November 14, 2023

This topic has best practices of what to do if you must modify your nodetypes, information on property flags that do not need migration, and a summary table on modifying node type definitions.

Don’ts

Do not change a property type

For example, do not change a boolean property type to a long.

Don't change:

[jnt:displayLatestNews] > jnt:content, jmix:basicContent
- autoRefresh (boolean)

To:

[jnt:displayLatestNews] > jnt:content, jmix:basicContent
- autoRefresh (long)

Do not change a property name

For example, do not change a property name from desc to description.

Don't change:

[jnt:desc] > jnt:content, jmix:basicContent
- desc (string)

To:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string)

Do not toggle internationalized flag on a property

For example, do not add or remove the internationalized flag.

Don't change:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string)

To:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string) internationalized

Or

Don't change:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string) internationalized

To:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string)

Migrating properties from one to another

To make your migration easier, always add a new property instead of changing an existing property.

For example, change:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string)

To:

[jnt:desc] > jnt:content, jmix:basicContent
- description (string) internationalized

Remove most flags from existing properties and flag them as hidden (this way it won’t show in in edit mode for editors).

[jnt:desc] > jnt:content, jmix:basicContent
- description (string) hidden
- descriptionI18N (string) internationalized

Update your resource bundle to use same label on a new property.

jnt_desc.description = Description field (deprecated)
jnt_desc.descriptionI18N = Description field

Use a Groovy script to migrate existing properties from one to another. You must run the script from Jahia Tools>Administration and Guidance>Groovy console. The script must use a System Session that is run as the root user. Here is a skeleton example of how to achieve that.

JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(ServicesRegistry.getInstance().getJahiaUserManagerService().lookupRootUser().getJahiaUser(), "default", null, new JCRCallback<Object>() {
   public Object doInJCR(JCRSessionWrapper session) {
       // Session is now a system session not hindered by locks, or protected properties
       return null;
   }
});

Sample Groovy script

This script runs and copies the value of description properties for all declared languages on the site associated with the node being updated.

import javax.jcr.NodeIterator
import javax.jcr.RepositoryException
import javax.jcr.query.Query

import org.jahia.registries.ServicesRegistry
import org.jahia.services.content.JCRCallback
import org.jahia.services.content.JCRNodeWrapper
import org.jahia.services.content.JCRSessionWrapper
import org.jahia.services.content.JCRTemplate
import org.jahia.services.query.QueryWrapper

def log = log;

JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(ServicesRegistry.getInstance().getJahiaUserManagerService().lookupRootUser().getJahiaUser(), "default", null, new JCRCallback<Object>() {
   public Object doInJCR(JCRSessionWrapper session) {
       log.info("Copying description into descriptionI18N")
       final String stmt = "SELECT * FROM [jnt:desc] WHERE ISDESCENDANTNODE('/sites') AND [description] is not null";
       try {
           QueryWrapper queryInit = session.getWorkspace().getQueryManager().createQuery(stmt, Query.JCR_SQL2);
           NodeIterator iteratorSitesInit = queryInit.execute().getNodes();
           def i = 0;
           while (iteratorSitesInit.hasNext()) {
               JCRNodeWrapper node = (JCRNodeWrapper) iteratorSitesInit.nextNode();
               // Check if nose is locked
               if (node.isLocked()) {
                   log.warn("Node " + node.getPath() + " is locked from " + node.getPropertyAsString("jcr:lockOwner") + " for reason " + node.getPropertyAsString("j:lockTypes"));
               }
               def description = node.getProperty("description").getString();
               // let's copy the value of description in all available languages
               node.getResolveSite().getActiveLiveLanguagesAsLocales().forEach({locale ->
                   node.getOrCreateI18N(locale).setProperty("descriptionI18N", description)
               })

               i++;
               if (i % 500 == 0) {
                   session.save();
               }
           }
           session.save()
       } catch (RepositoryException ex) {
           log.info("Exception while executing migration script {}",ex.message)
       }

       return null;
   }
});

After validation, you can delete the property description using a script and then remove it from the definition to avoid keeping deprecated properties longer than needed.

import javax.jcr.NodeIterator
import javax.jcr.RepositoryException
import javax.jcr.query.Query

import org.jahia.registries.ServicesRegistry
import org.jahia.services.content.JCRCallback
import org.jahia.services.content.JCRNodeWrapper
import org.jahia.services.content.JCRSessionWrapper
import org.jahia.services.content.JCRTemplate
import org.jahia.services.query.QueryWrapper

def log = log;


JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(ServicesRegistry.getInstance().getJahiaUserManagerService().lookupRootUser().getJahiaUser(), "default", null, new JCRCallback<Object>() {
   public Object doInJCR(JCRSessionWrapper session) {
       log.info("Removing description from existing node")
       final String stmt = "SELECT * FROM [jnt:desc] WHERE ISDESCENDANTNODE('/sites') AND [description] is not null";
       try {
           QueryWrapper queryInit = session.getWorkspace().getQueryManager().createQuery(stmt, Query.JCR_SQL2);
           NodeIterator iteratorSitesInit = queryInit.execute().getNodes();
           def i = 0;
           while (iteratorSitesInit.hasNext()) {
               JCRNodeWrapper node = (JCRNodeWrapper) iteratorSitesInit.nextNode();
               // Check if nose is locked
               if (node.isLocked()) {
                   log.warn("Node " + node.getPath() + " is locked from " + node.getPropertyAsString("jcr:lockOwner") + " for reason " + node.getPropertyAsString("j:lockTypes"));
               }
               node.getProperty("description").remove();
               i++;
               if (i % 500 == 0) {
                   session.save();
               }
           }
           session.save()
       } catch (RepositoryException ex) {
           log.info("Exception while executing migration script {}",ex.message)
       }

       return null;
   }
});

Property flags that do not need migration

Flag Operation Update
mandatory, autocreated added Existing nodes need to be updated with a value if property doesn't exist, either by editing or by a script
mandatory, autocreated removed No impact
indexed=no, fulltextsearchable=no, etc. (all index relatable flag) added/removed Requires a reindexation from Jahia Tools>Administration and Guidance>Search engine management
hidden, protected, primary added/removed No impact

Modifying node type definitions

Modify the definitions.cnd file with caution. Content integrity issues can occur if content has already been created with existing node types definitions.

Type of modification Operation Comment
Namespace Creation Will not create a problem
Namespace Deletion Should never be done. Instead of a deletion, stop using the previous namespace
Namespace Modification Should never be done. Instead of a modification, create a new namespace and stop using the previous one
Node type Creation Will not create a problem
Node type Deletion Should never be done before having deleted all the instantiated nodes of this type from the template/site. Find and delete instances of this node using Jahia Tools>JCR Data>JCR console. You can also script this operation using Groovy scripts.
Node type Modification Renaming a node type is similar to perform a deletion of the previous node type, and creating of a new one
Property of a node type Creation Will not create problem
Property of a node type Deletion Should never be done before having set the property to null on all the instantiated nodes. Otherwise, it will lead to publication issues. An alternative option is to declare this property as hidden and then stop using it.
Property of a node type Modification Should never be done if there is node instantiated with this property. If necessary, create a new property and refer to "Deletion" section above