6363
6464import java .nio .file .Paths ;
6565import java .util .ArrayList ;
66+ import java .util .Arrays ;
6667import java .util .HashSet ;
6768import java .util .List ;
6869import java .util .Map ;
7374import static org .apache .iotdb .db .pipe .sink .protocol .opcua .server .OpcUaNameSpace .convertToOpcDataType ;
7475import static org .apache .iotdb .db .pipe .sink .protocol .opcua .server .OpcUaNameSpace .timestampToUtc ;
7576import static org .eclipse .milo .opcua .stack .core .StatusCodes .Bad_Timeout ;
77+ import static org .eclipse .milo .opcua .stack .core .types .enumerated .TimestampsToReturn .Neither ;
7678
7779public class IoTDBOpcUaClient {
7880 private static final Logger LOGGER = LoggerFactory .getLogger (OpcUaNameSpace .class );
7981
82+ private static final int DEFAULT_MAX_NODES_PER_WRITE = 10_000 ;
83+ private static final int DEFAULT_MAX_NODES_PER_NODE_MANAGEMENT = 250 ;
84+
8085 // Customized nodes
8186 private static final int NAME_SPACE_INDEX = 2 ;
8287
@@ -90,6 +95,9 @@ public class IoTDBOpcUaClient {
9095 private final boolean historizing ;
9196 private ClientRunner runner ;
9297
98+ private int maxNodesPerWrite = DEFAULT_MAX_NODES_PER_WRITE ;
99+ private int maxNodesPerNodeManagement = DEFAULT_MAX_NODES_PER_NODE_MANAGEMENT ;
100+
93101 public IoTDBOpcUaClient (
94102 final String nodeUrl ,
95103 final SecurityPolicy securityPolicy ,
@@ -118,6 +126,62 @@ public void run(final OpcUaClient client) throws Exception {
118126 }
119127 break ;
120128 }
129+ updateOperationLimits ();
130+ }
131+
132+ private void updateOperationLimits () {
133+ try {
134+ final List <DataValue > operationLimits =
135+ client
136+ .readValues (
137+ 0.0 ,
138+ Neither ,
139+ Arrays .asList (
140+ Identifiers .Server_ServerCapabilities_OperationLimits_MaxNodesPerWrite ,
141+ Identifiers
142+ .Server_ServerCapabilities_OperationLimits_MaxNodesPerNodeManagement ))
143+ .get ();
144+ maxNodesPerWrite = getOperationLimit (operationLimits , 0 , DEFAULT_MAX_NODES_PER_WRITE );
145+ maxNodesPerNodeManagement =
146+ getOperationLimit (operationLimits , 1 , DEFAULT_MAX_NODES_PER_NODE_MANAGEMENT );
147+ LOGGER .info (
148+ "OPC UA server operation limits: maxNodesPerWrite={}, maxNodesPerNodeManagement={}" ,
149+ maxNodesPerWrite ,
150+ maxNodesPerNodeManagement );
151+ } catch (final InterruptedException e ) {
152+ Thread .currentThread ().interrupt ();
153+ LOGGER .warn (
154+ "Interrupted while reading OPC UA server operation limits, use defaults: "
155+ + "maxNodesPerWrite={}, maxNodesPerNodeManagement={}" ,
156+ DEFAULT_MAX_NODES_PER_WRITE ,
157+ DEFAULT_MAX_NODES_PER_NODE_MANAGEMENT );
158+ } catch (final Exception e ) {
159+ LOGGER .warn (
160+ "Failed to read OPC UA server operation limits, use defaults: "
161+ + "maxNodesPerWrite={}, maxNodesPerNodeManagement={}" ,
162+ DEFAULT_MAX_NODES_PER_WRITE ,
163+ DEFAULT_MAX_NODES_PER_NODE_MANAGEMENT ,
164+ e );
165+ }
166+ }
167+
168+ private static int getOperationLimit (
169+ final List <DataValue > operationLimits , final int index , final int defaultValue ) {
170+ if (Objects .isNull (operationLimits ) || operationLimits .size () <= index ) {
171+ return defaultValue ;
172+ }
173+
174+ final DataValue dataValue = operationLimits .get (index );
175+ if (Objects .isNull (dataValue )
176+ || Objects .isNull (dataValue .getStatusCode ())
177+ || !dataValue .getStatusCode ().isGood ()
178+ || Objects .isNull (dataValue .getValue ())
179+ || !(dataValue .getValue ().getValue () instanceof Number )) {
180+ return defaultValue ;
181+ }
182+
183+ final long limit = ((Number ) dataValue .getValue ().getValue ()).longValue ();
184+ return limit == 0 ? Integer .MAX_VALUE : (int ) Math .min (limit , Integer .MAX_VALUE );
121185 }
122186
123187 // Only support tree model & client-server
@@ -185,7 +249,8 @@ private void collectWriteRequests(
185249 if (Objects .nonNull (sink .getValueName ()) && !sink .getValueName ().equals (name )) {
186250 PipeLogger .log (
187251 LOGGER ::warn ,
188- "When the 'with-quality' mode is enabled, the measurement must be either \" value-name\" or \" quality-name\" " );
252+ "When the 'with-quality' mode is enabled, the measurement must be either \" value-name\" "
253+ + " or \" quality-name\" " );
189254 continue ;
190255 }
191256
@@ -263,17 +328,23 @@ private void addMissingNodes(final List<OpcUaWriteRequest> writeRequests) throws
263328 }
264329 }
265330
266- final AddNodesResponse addStatus = client .addNodes (nodesToAdd ).get ();
267- for (final AddNodesResult result : addStatus .getResults ()) {
268- if (!result .getStatusCode ().equals (StatusCode .GOOD )
269- && result .getStatusCode ().getValue () != StatusCodes .Bad_NodeIdExists ) {
270- throw new PipeException (
271- "Failed to create nodes after transfer data value, creation status: "
272- + addStatus
273- + writeRequests
274- .get (0 )
275- .getErrorString (new StatusCode (StatusCodes .Bad_NodeIdUnknown )));
331+ for (int startIndex = 0 ; startIndex < nodesToAdd .size (); ) {
332+ final int endIndex =
333+ getBatchEndIndex (startIndex , nodesToAdd .size (), maxNodesPerNodeManagement );
334+ final AddNodesResponse addStatus =
335+ client .addNodes (nodesToAdd .subList (startIndex , endIndex )).get ();
336+ for (final AddNodesResult result : addStatus .getResults ()) {
337+ if (!result .getStatusCode ().equals (StatusCode .GOOD )
338+ && result .getStatusCode ().getValue () != StatusCodes .Bad_NodeIdExists ) {
339+ throw new PipeException (
340+ "Failed to create nodes after transfer data value, creation status: "
341+ + addStatus
342+ + writeRequests
343+ .get (0 )
344+ .getErrorString (new StatusCode (StatusCodes .Bad_NodeIdUnknown )));
345+ }
276346 }
347+ startIndex = endIndex ;
277348 }
278349 }
279350
@@ -290,13 +361,24 @@ private void validateRetriedWrites(
290361
291362 private List <StatusCode > writeValuesOnce (final List <OpcUaWriteRequest > writeRequests )
292363 throws Exception {
293- final List <NodeId > nodeIds = new ArrayList <>(writeRequests .size ());
294- final List <DataValue > dataValues = new ArrayList <>(writeRequests .size ());
295- for (final OpcUaWriteRequest writeRequest : writeRequests ) {
296- nodeIds .add (writeRequest .nodeId );
297- dataValues .add (writeRequest .dataValue );
364+ final List <StatusCode > writeStatuses = new ArrayList <>(writeRequests .size ());
365+ for (int startIndex = 0 ; startIndex < writeRequests .size (); ) {
366+ final int endIndex = getBatchEndIndex (startIndex , writeRequests .size (), maxNodesPerWrite );
367+ final List <NodeId > nodeIds = new ArrayList <>(endIndex - startIndex );
368+ final List <DataValue > dataValues = new ArrayList <>(endIndex - startIndex );
369+ for (int i = startIndex ; i < endIndex ; ++i ) {
370+ nodeIds .add (writeRequests .get (i ).nodeId );
371+ dataValues .add (writeRequests .get (i ).dataValue );
372+ }
373+ writeStatuses .addAll (client .writeValues (nodeIds , dataValues ).get ());
374+ startIndex = endIndex ;
298375 }
299- return client .writeValues (nodeIds , dataValues ).get ();
376+ return writeStatuses ;
377+ }
378+
379+ private static int getBatchEndIndex (
380+ final int startIndex , final int totalSize , final int batchSize ) {
381+ return (int ) Math .min ((long ) totalSize , (long ) startIndex + batchSize );
300382 }
301383
302384 private static final class OpcUaWriteRequest {
0 commit comments