11/*
2- * Copyright (c) 2024 the Eclipse Milo Authors
2+ * Copyright (c) 2025 the Eclipse Milo Authors
33 *
44 * This program and the accompanying materials are made
55 * available under the terms of the Eclipse Public License 2.0
1616import static org .eclipse .milo .opcua .stack .core .types .builtin .unsigned .Unsigned .ushort ;
1717
1818import java .lang .reflect .Array ;
19+ import java .util .LinkedHashMap ;
1920import java .util .List ;
2021import java .util .Random ;
2122import java .util .UUID ;
2930import org .eclipse .milo .opcua .sdk .core .ValueRank ;
3031import org .eclipse .milo .opcua .sdk .core .ValueRanks ;
3132import org .eclipse .milo .opcua .sdk .core .dtd .BinaryDataTypeCodec ;
33+ import org .eclipse .milo .opcua .sdk .core .types .DynamicEnumType ;
34+ import org .eclipse .milo .opcua .sdk .core .types .DynamicStructType ;
35+ import org .eclipse .milo .opcua .sdk .core .types .codec .DynamicCodecFactory ;
36+ import org .eclipse .milo .opcua .sdk .core .types .codec .DynamicStructCodec ;
37+ import org .eclipse .milo .opcua .sdk .core .typetree .DataType ;
38+ import org .eclipse .milo .opcua .sdk .core .typetree .DataTypeTree ;
3239import org .eclipse .milo .opcua .sdk .server .Lifecycle ;
3340import org .eclipse .milo .opcua .sdk .server .ManagedNamespaceWithLifecycle ;
3441import org .eclipse .milo .opcua .sdk .server .OpcUaServer ;
3744import org .eclipse .milo .opcua .sdk .server .items .DataItem ;
3845import org .eclipse .milo .opcua .sdk .server .items .MonitoredItem ;
3946import org .eclipse .milo .opcua .sdk .server .model .objects .BaseEventTypeNode ;
47+ import org .eclipse .milo .opcua .sdk .server .model .objects .DataTypeEncodingTypeNode ;
4048import org .eclipse .milo .opcua .sdk .server .model .objects .ServerTypeNode ;
4149import org .eclipse .milo .opcua .sdk .server .model .variables .AnalogItemTypeNode ;
4250import org .eclipse .milo .opcua .sdk .server .nodes .UaDataTypeNode ;
5361import org .eclipse .milo .opcua .stack .core .NodeIds ;
5462import org .eclipse .milo .opcua .stack .core .OpcUaDataType ;
5563import org .eclipse .milo .opcua .stack .core .UaException ;
56- import org .eclipse .milo .opcua .stack .core .types .builtin .ByteString ;
57- import org .eclipse .milo .opcua .stack .core .types .builtin .DataValue ;
58- import org .eclipse .milo .opcua .stack .core .types .builtin .DateTime ;
59- import org .eclipse .milo .opcua .stack .core .types .builtin .ExtensionObject ;
60- import org .eclipse .milo .opcua .stack .core .types .builtin .LocalizedText ;
61- import org .eclipse .milo .opcua .stack .core .types .builtin .NodeId ;
62- import org .eclipse .milo .opcua .stack .core .types .builtin .QualifiedName ;
63- import org .eclipse .milo .opcua .stack .core .types .builtin .Variant ;
64- import org .eclipse .milo .opcua .stack .core .types .builtin .XmlElement ;
64+ import org .eclipse .milo .opcua .stack .core .types .builtin .*;
6565import org .eclipse .milo .opcua .stack .core .types .builtin .unsigned .UInteger ;
6666import org .eclipse .milo .opcua .stack .core .types .enumerated .StructureType ;
6767import org .eclipse .milo .opcua .stack .core .types .structured .EnumDefinition ;
@@ -232,6 +232,22 @@ private void createAndAddNodes() {
232232 logger .warn ("Failed to register custom struct type" , e );
233233 }
234234
235+ try {
236+ DataType dataType = registerDynamicStructType ();
237+
238+ addDynamicStructTypeVariable (folderNode , dataType );
239+ } catch (Exception e ) {
240+ logger .warn ("Failed to register dynamic struct type" , e );
241+ }
242+
243+ try {
244+ DataType enumDataType = registerDynamicEnumType ();
245+
246+ addDynamicEnumTypeVariable (folderNode , enumDataType );
247+ } catch (Exception e ) {
248+ logger .warn ("Failed to register dynamic enum type" , e );
249+ }
250+
235251 addCustomObjectTypeAndInstance (folderNode );
236252 }
237253
@@ -932,7 +948,7 @@ private void registerCustomStructType() throws Exception {
932948 // Register Codecs for each supported encoding with DataTypeManager
933949 getNodeContext ()
934950 .getServer ()
935- .getDataTypeManager ()
951+ .getStaticDataTypeManager ()
936952 .registerType (dataTypeId , new CustomStructType .Codec (), binaryEncodingId , null , null );
937953
938954 // Prior to OPC UA 1.04, clients that needed to interpret custom types read the
@@ -1004,7 +1020,7 @@ private void registerCustomUnionType() throws Exception {
10041020 // Register Codecs for each supported encoding with DataTypeManager
10051021 getNodeContext ()
10061022 .getServer ()
1007- .getDataTypeManager ()
1023+ .getStaticDataTypeManager ()
10081024 .registerType (dataTypeId , new CustomUnionType .Codec (), binaryEncodingId , null , null );
10091025
10101026 StructureDescription description =
@@ -1019,6 +1035,184 @@ private void registerCustomUnionType() throws Exception {
10191035 description );
10201036 }
10211037
1038+ private DataType registerDynamicEnumType () throws Exception {
1039+ // Define NodeId for DataType
1040+ NodeId dataTypeId =
1041+ ExpandedNodeId .parse ("nsu=%s;s=DataType.DynamicEnumType" .formatted (NAMESPACE_URI ))
1042+ .toNodeIdOrThrow (getServer ().getNamespaceTable ());
1043+
1044+ // Add a custom DataTypeNode with a SubtypeOf reference to Enumeration
1045+ UaDataTypeNode dataTypeNode =
1046+ new UaDataTypeNode (
1047+ getNodeContext (),
1048+ dataTypeId ,
1049+ newQualifiedName ("DynamicEnumType" ),
1050+ LocalizedText .english ("DynamicEnumType" ),
1051+ LocalizedText .NULL_VALUE ,
1052+ uint (0 ),
1053+ uint (0 ),
1054+ false );
1055+
1056+ dataTypeNode .addReference (
1057+ new Reference (
1058+ dataTypeId ,
1059+ NodeIds .HasSubtype ,
1060+ NodeIds .Enumeration .expanded (),
1061+ Reference .Direction .INVERSE ));
1062+
1063+ getNodeManager ().addNode (dataTypeNode );
1064+
1065+ // Define the enum fields
1066+ EnumField [] fields =
1067+ new EnumField [] {
1068+ new EnumField (
1069+ 0L , LocalizedText .english ("DynField0" ), LocalizedText .NULL_VALUE , "DynField0" ),
1070+ new EnumField (
1071+ 1L , LocalizedText .english ("DynField1" ), LocalizedText .NULL_VALUE , "DynField1" ),
1072+ new EnumField (
1073+ 2L , LocalizedText .english ("DynField2" ), LocalizedText .NULL_VALUE , "DynField2" )
1074+ };
1075+
1076+ EnumDefinition definition = new EnumDefinition (fields );
1077+
1078+ // Set the EnumStrings property
1079+ dataTypeNode .setEnumStrings (
1080+ new LocalizedText [] {
1081+ LocalizedText .english ("DynField0" ),
1082+ LocalizedText .english ("DynField1" ),
1083+ LocalizedText .english ("DynField2" )
1084+ });
1085+
1086+ // Set the DataTypeDefinition attribute
1087+ dataTypeNode .setDataTypeDefinition (definition );
1088+
1089+ DataTypeTree dataTypeTree = getNodeContext ().getServer ().updateDataTypeTree ();
1090+ DataType dataType = dataTypeTree .getDataType (dataTypeId );
1091+ assert dataType != null ;
1092+
1093+ // Register with the dynamic DataTypeManager
1094+ getNodeContext ()
1095+ .getServer ()
1096+ .getDynamicDataTypeManager ()
1097+ .registerType (
1098+ dataTypeId , DynamicCodecFactory .create (dataType , dataTypeTree ), null , null , null );
1099+
1100+ return dataType ;
1101+ }
1102+
1103+ private DataType registerDynamicStructType () throws Exception {
1104+ // Define NodeIds for DataType and encoding Nodes
1105+ NodeId dataTypeId =
1106+ ExpandedNodeId .parse ("nsu=%s;s=DataType.DynamicStructType" .formatted (NAMESPACE_URI ))
1107+ .toNodeIdOrThrow (getServer ().getNamespaceTable ());
1108+
1109+ NodeId binaryEncodingId =
1110+ ExpandedNodeId .parse (
1111+ "nsu=%s;s=DataType.DynamicStructType.BinaryEncoding" .formatted (NAMESPACE_URI ))
1112+ .toNodeIdOrThrow (getServer ().getNamespaceTable ());
1113+
1114+ // Add a custom DataTypeNode with a SubtypeOf reference to Structure
1115+ UaDataTypeNode dataTypeNode =
1116+ new UaDataTypeNode (
1117+ getNodeContext (),
1118+ dataTypeId ,
1119+ newQualifiedName ("DynamicStructType" ),
1120+ LocalizedText .english ("DynamicStructType" ),
1121+ LocalizedText .NULL_VALUE ,
1122+ uint (0 ),
1123+ uint (0 ),
1124+ false );
1125+
1126+ dataTypeNode .addReference (
1127+ new Reference (
1128+ dataTypeId ,
1129+ NodeIds .HasSubtype ,
1130+ NodeIds .Structure .expanded (),
1131+ Reference .Direction .INVERSE ));
1132+
1133+ getNodeManager ().addNode (dataTypeNode );
1134+
1135+ // Add a DataTypeEncodingNode for binary encoding of the new DataType
1136+ DataTypeEncodingTypeNode dataTypeEncodingNode =
1137+ new DataTypeEncodingTypeNode (
1138+ getNodeContext (),
1139+ binaryEncodingId ,
1140+ new QualifiedName (0 , "Default Binary" ),
1141+ LocalizedText .english ("Default Binary" ),
1142+ LocalizedText .NULL_VALUE ,
1143+ uint (0 ),
1144+ uint (0 ),
1145+ null ,
1146+ null ,
1147+ null );
1148+
1149+ dataTypeEncodingNode .addReference (
1150+ new Reference (
1151+ dataTypeEncodingNode .getNodeId (),
1152+ NodeIds .HasTypeDefinition ,
1153+ NodeIds .DataTypeEncodingType .expanded (),
1154+ Reference .Direction .FORWARD ));
1155+
1156+ dataTypeEncodingNode .addReference (
1157+ new Reference (
1158+ dataTypeEncodingNode .getNodeId (),
1159+ NodeIds .HasEncoding ,
1160+ dataTypeId .expanded (),
1161+ Reference .Direction .INVERSE ));
1162+
1163+ getNodeManager ().addNode (dataTypeEncodingNode );
1164+
1165+ // Define the structure
1166+ StructureField [] fields =
1167+ new StructureField [] {
1168+ new StructureField (
1169+ "foo" ,
1170+ LocalizedText .NULL_VALUE ,
1171+ NodeIds .String ,
1172+ ValueRanks .Scalar ,
1173+ null ,
1174+ getServer ().getConfig ().getLimits ().getMaxStringLength (),
1175+ false ),
1176+ new StructureField (
1177+ "bar" ,
1178+ LocalizedText .NULL_VALUE ,
1179+ NodeIds .UInt32 ,
1180+ ValueRanks .Scalar ,
1181+ null ,
1182+ uint (0 ),
1183+ false ),
1184+ new StructureField (
1185+ "baz" ,
1186+ LocalizedText .NULL_VALUE ,
1187+ NodeIds .Boolean ,
1188+ ValueRanks .Scalar ,
1189+ null ,
1190+ uint (0 ),
1191+ false )
1192+ };
1193+
1194+ StructureDefinition definition =
1195+ new StructureDefinition (
1196+ binaryEncodingId , NodeIds .Structure , StructureType .Structure , fields );
1197+
1198+ // Set the DataTypeDefinition attribute
1199+ dataTypeNode .setDataTypeDefinition (definition );
1200+
1201+ DataTypeTree dataTypeTree = getNodeContext ().getServer ().updateDataTypeTree ();
1202+ DataType dataType = dataTypeTree .getDataType (dataTypeId );
1203+ assert dataType != null ;
1204+
1205+ // Register Codecs for each supported encoding with DataTypeManager
1206+ var codec = new DynamicStructCodec (dataType , dataTypeTree );
1207+
1208+ getNodeContext ()
1209+ .getServer ()
1210+ .getDynamicDataTypeManager ()
1211+ .registerType (dataTypeId , codec , binaryEncodingId , null , null );
1212+
1213+ return dataType ;
1214+ }
1215+
10221216 private void addCustomEnumTypeVariable (UaFolderNode rootFolder ) throws Exception {
10231217 NodeId dataTypeId = CustomEnumType .TYPE_ID .toNodeIdOrThrow (getServer ().getNamespaceTable ());
10241218
@@ -1070,7 +1264,7 @@ private void addCustomStructTypeVariable(UaFolderNode rootFolder) throws Excepti
10701264
10711265 ExtensionObject xo =
10721266 ExtensionObject .encodeDefaultBinary (
1073- getServer ().getEncodingContext (), value , binaryEncodingId );
1267+ getServer ().getStaticEncodingContext (), value , binaryEncodingId );
10741268
10751269 customStructTypeVariable .setValue (new DataValue (new Variant (xo )));
10761270
@@ -1107,7 +1301,7 @@ private void addCustomUnionTypeVariable(UaFolderNode rootFolder) throws Exceptio
11071301
11081302 ExtensionObject xo =
11091303 ExtensionObject .encodeDefaultBinary (
1110- getServer ().getEncodingContext (), value , binaryEncodingId );
1304+ getServer ().getStaticEncodingContext (), value , binaryEncodingId );
11111305
11121306 customUnionTypeVariable .setValue (new DataValue (new Variant (xo )));
11131307
@@ -1121,6 +1315,65 @@ private void addCustomUnionTypeVariable(UaFolderNode rootFolder) throws Exceptio
11211315 false ));
11221316 }
11231317
1318+ private void addDynamicEnumTypeVariable (UaFolderNode rootFolder , DataType dataType ) {
1319+ NodeId nodeId = newNodeId ("HelloWorld/DynamicEnumTypeVariable" );
1320+
1321+ UaVariableNode dynamicEnumTypeVariable =
1322+ new UaVariableNode .UaVariableNodeBuilder (getNodeContext ())
1323+ .setNodeId (nodeId )
1324+ .setAccessLevel (AccessLevel .READ_WRITE )
1325+ .setUserAccessLevel (AccessLevel .READ_WRITE )
1326+ .setBrowseName (newQualifiedName ("DynamicEnumTypeVariable" ))
1327+ .setDisplayName (LocalizedText .english ("DynamicEnumTypeVariable" ))
1328+ .setDataType (dataType .getNodeId ())
1329+ .setTypeDefinition (NodeIds .BaseDataVariableType )
1330+ .build ();
1331+
1332+ // Create an instance of DynamicEnumType with value 0 (DynField0)
1333+ DynamicEnumType value = DynamicEnumType .newInstance (dataType , 0 );
1334+ dynamicEnumTypeVariable .setValue (new DataValue (new Variant (value )));
1335+
1336+ getNodeManager ().addNode (dynamicEnumTypeVariable );
1337+ rootFolder .addOrganizes (dynamicEnumTypeVariable );
1338+ }
1339+
1340+ private void addDynamicStructTypeVariable (UaFolderNode rootFolder , DataType dataType ) {
1341+ UaVariableNode dynamicStructTypeVariable =
1342+ UaVariableNode .build (
1343+ getNodeContext (),
1344+ b ->
1345+ b .setNodeId (newNodeId ("HelloWorld/DynamicStructTypeVariable" ))
1346+ .setAccessLevel (AccessLevel .READ_WRITE )
1347+ .setUserAccessLevel (AccessLevel .READ_WRITE )
1348+ .setBrowseName (newQualifiedName ("DynamicStructTypeVariable" ))
1349+ .setDisplayName (LocalizedText .english ("DynamicStructTypeVariable" ))
1350+ .setDataType (dataType .getNodeId ())
1351+ .setTypeDefinition (NodeIds .BaseDataVariableType )
1352+ .build ());
1353+
1354+ LinkedHashMap <String , Object > members = new LinkedHashMap <>();
1355+ members .put ("foo" , "foo" );
1356+ members .put ("bar" , uint (42 ));
1357+ members .put ("baz" , true );
1358+ DynamicStructType value = new DynamicStructType (dataType , members );
1359+
1360+ // Note that we're using getDynamicEncodingContext() here
1361+ ExtensionObject xo =
1362+ ExtensionObject .encodeDefaultBinary (
1363+ getServer ().getDynamicEncodingContext (), value , dataType .getBinaryEncodingId ());
1364+
1365+ dynamicStructTypeVariable .setValue (new DataValue (Variant .ofExtensionObject (xo )));
1366+
1367+ getNodeManager ().addNode (dynamicStructTypeVariable );
1368+
1369+ dynamicStructTypeVariable .addReference (
1370+ new Reference (
1371+ dynamicStructTypeVariable .getNodeId (),
1372+ NodeIds .Organizes ,
1373+ rootFolder .getNodeId ().expanded (),
1374+ false ));
1375+ }
1376+
11241377 @ Override
11251378 public void onDataItemsCreated (List <DataItem > dataItems ) {
11261379 subscriptionModel .onDataItemsCreated (dataItems );
0 commit comments