Installation and Upgrade > Upgrading ThingWorx > Migrating Java Extensions from 8.x to 9.x
Migrating Java Extensions from 8.x to 9.x
Only Java backed extensions may require migration. The migration of Java extensions is only required if you are upgrading from ThingWorx 8.x to 9.x (not 9.x to 9.x), and if you have the following:
Implemented lifecycle events such as initializeEntity and cleanupEntity
Maintain local state within your implementation
Have local copies (or hard references) of other entities
In ThingWorx High Availability Clustering, entities exist on multiple servers. Their information has to stay in sync across servers. Their lifecycle is a little different on the server where the change happens (change server) and the server where changes are synchronized (sync server). There is only one change server, and all other servers in the cluster are sync servers, which reload the dirty entities. The change server is simply the server where the request lands; it has no special configuration or status.
A new extension metadata attribute, haCompatible, was added to identify if an extension is compatible with ThingWorx High Availability Clustering. For more information, see Best Practices for Packaging and Deploying ThingWorx Solutions and Platform Settings for ThingWorx HA.
Lifecycle API Changes 
With ThingWorx High Availability Clustering, the following lifecycle API methods now require an additional parameter. The reason for the change is the behavior difference in lifecycle on the change server versus on the sync servers. On the change server, all logic has to be done for the lifecycle event. However, on the sync servers, you may only want to do a subset of this logic. For example, during initialize, there is no reason to set all of the states on the sync servers, since the state is shared.
Using the ContextType parameter, you can decide if some of the logic should happen. A ContextType of NONE indicates that it is not a secondary operation and all logic should execute. ContextType can also differentiate between STARTUP, SHUTDOWN, INSERT, UPDATE, or DELETE, which trigger different behaviors.
These changes will create compile-time errors. For a single-server fix, you can add the parameter to methods, and no other changes are required. For compatibility with Active-Active Clustering, make the following changes:
initializeEntity() to initializeEntity(contextType)
cleanupEntity() to cleanupEntity(contextType)
initializeThing() to initializeThing(contextType)
startThing() to startThing(contextType)
processStartNotification() to processStartNotification(contextType)
stopThing() to stopThing(contextType)
cleanupThing() to cleanupThing(contextType)
In the example below from the Thing entity, when the change server is initialized, a full configuration table update from its hierarchy is done and the changes are persisted. On the sync server, the configuration table is updated when the entity is initialized. Since it has already been persisted to the database, it can be loaded. There is no need to walk the hierarchy or do other logic.
public void initializeEntity(ContextType contextType) throws Exception {
_logger.trace("initializeEntity called on Thing {} with patch operation {} ", getName(), contextType);
if (!contextType.isSecondaryOperation()) {
updateConfigurationTableStructure(contextType, null,
ThingShapeUtilities.getConfigurationTableDataForInheritance(getThingTemplate(), getImplementedThingShapes()));
} else {
updateConfigurationTableStructure(contextType);
}
super.initializeEntity(contextType);
}
In the cleanup operation, the behavior is similar. The cache information on the change server is cleared but not on the sync server since that state is shared. In general, the change server does all of the work, and the sync server does less work.
public void cleanupEntity(ContextType contextType) throws Exception {
super.cleanupEntity(contextType);

getInstanceShape().cleanup(contextType);

// reset cache
if (!contextType.isSecondaryOperation()) {
clearNonPersistentPropertiesFromCache();
clearLastPropertyValueCache();
}

if (isEnabled()) {
cleanupThing(contextType);
}
}
Maintaining State 
Local state is any state not in properties, configuration tables, or data tables. Local state needs to be changed to shared state to be available between servers. Shared state is accomplished with the Ignite caching layer. For extensions, we recommend that all states should be stored in either properties or in configuration tables:
Configuration Tables
Configuration tables are stored in the database and synced across the servers. Therefore, there is a slight delay in the update across servers. Since configuration tables are part of the model sync, entities on the sync server are destroyed and recreated with the model changed when synced. Therefore, it may be cleaner and more efficient to use properties than whole model updates. Also, persistent properties can be used to store state values between restarts.
Properties
All property values are now stored as shared state. Updates to these values take place immediately across servers by writing to the cache. If a property is persistent, it will still be cached for performance and written to the database to preserve state between restarts. You can create a Data Thing, which can hold state properties if they need to be common across multiple Thing instances.
Local Properties
In some cases, share state for properties should not be used. For example, ConnectableThing has the property isConnected for which the connection status is trackedper server. It does not make sense to have each server overwrite isConnected when its ConnectableThings connection status changes. This local state can be achieved by adding the aspect isLocalProperty to the property. This aspect only applies to properties that arenot persistent; persistent properties with this aspect are shared as normal.
@ThingworxPropertyDefinition(
name ="isConnected",
description ="Flag indicating if connected or not",
baseType ="BOOLEAN",
aspects = {"isPersistent:false","isReadOnly:false","defaultValue:false","isLocalProperty:true"})
Checking for Hard References 
Decoupling removes hard references to other entities. For the patching process in model sync in ThingWorx High Availability Clustering, the system needs to swap changed entities; therefore, there can be no entities encapsulating hard references to other entities. For example, if you have a Thing template cached at the Thing level, and the template changed, it would have to be patched on all servers. Any Thing having a hard reference to this template will break. Therefore, all entities must be changed from hard references to soft references. So, as references are needed, the platform walks the tree to get them.
For example, below is the ThingTemplate class having a reference to a base Thing template.
With Hard Reference
// Parent thing template
privateThingTemplate _baseThingTemplate =null

...
@ThingworxExtensionApiMethod(since = {6,6})
publicThingTemplate getBaseThingTemplate() {
if(_baseThingTemplate ==null&& StringUtilities.isNonEmpty(getBaseThingTemplateName())) {
_baseThingTemplate = ThingTemplateManager.getInstance().getEntityDirect(getBaseThingTemplateName());
}

return_baseThingTemplate;
}
Because we are storing the hard reference in the _baseThingTemplate, this will cause issues with the sync process patching. In the example below, the hard reference is decoupled to a soft reference and just the name is stored and template is retrieved when needed.
//Note the hard reference is removed (local stored template _baseThingTemplate)

@ThingworxExtensionApiMethod(since = {6,6})
publicThingTemplate getBaseThingTemplate() {
ThingTemplate baseThingTemplate =null

if(StringUtilities.isNonEmpty(getBaseThingTemplateName())) {
baseThingTemplate = ThingTemplateManager.getInstance().getEntityDirect(getBaseThingTemplateName());
}

returnbaseThingTemplate;
}
Was this helpful?