Nodetype migration best practices

November 14, 2023

This topic has best practices of what to do if you must modify your nodetypes.

For information on changing existing node types, see 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

From:

[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