RTD change to document migration to Spring Boot 3.0
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021-2022 Bell Canada
6  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
7  *  ================================================================================
8  *  Licensed under the Apache License, Version 2.0 (the "License");
9  *  you may not use this file except in compliance with the License.
10  *  You may obtain a copy of the License at
11  *
12  *        http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *  Unless required by applicable law or agreed to in writing, software
15  *  distributed under the License is distributed on an "AS IS" BASIS,
16  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  *  See the License for the specific language governing permissions and
18  *  limitations under the License.
19  *
20  *  SPDX-License-Identifier: Apache-2.0
21  *  ============LICENSE_END=========================================================
22  */
23
24 package org.onap.cps.ncmp.api.impl
25
26 import com.hazelcast.map.IMap
27 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
28 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
29 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
30 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
31 import org.onap.cps.ncmp.api.inventory.CmHandleState
32 import org.onap.cps.ncmp.api.inventory.CompositeState
33 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
34 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
35 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
36 import org.onap.cps.ncmp.api.models.DataOperationDefinition
37 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
38 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
39 import org.onap.cps.ncmp.api.models.ConditionApiProperties
40 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
41 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
42 import org.onap.cps.ncmp.api.models.DataOperationRequest
43 import org.onap.cps.spi.exceptions.CpsException
44 import org.onap.cps.spi.model.ConditionProperties
45 import spock.lang.Shared
46 import java.util.stream.Collectors
47 import org.onap.cps.utils.JsonObjectMapper
48 import com.fasterxml.jackson.databind.ObjectMapper
49 import org.onap.cps.api.CpsDataService
50 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
51 import org.onap.cps.spi.FetchDescendantsOption
52 import org.onap.cps.spi.model.DataNode
53 import org.springframework.http.HttpStatus
54 import org.springframework.http.ResponseEntity
55 import spock.lang.Specification
56
57 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
58 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
59 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
60 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
61 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
62
63 class NetworkCmProxyDataServiceImplSpec extends Specification {
64
65     def mockCpsDataService = Mock(CpsDataService)
66     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
67     def mockDmiDataOperations = Mock(DmiDataOperations)
68     def nullNetworkCmProxyDataServicePropertyHandler = null
69     def mockInventoryPersistence = Mock(InventoryPersistence)
70     def mockCmHandleQueries = Mock(CmHandleQueries)
71     def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
72     def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
73     def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
74     def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
75
76     def NO_TOPIC = null
77     def NO_REQUEST_ID = null
78     @Shared
79     def OPTIONS_PARAM = '(a=1,b=2)'
80     @Shared
81     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
82
83     def objectUnderTest = new NetworkCmProxyDataServiceImpl(
84             spiedJsonObjectMapper,
85             mockDmiDataOperations,
86             nullNetworkCmProxyDataServicePropertyHandler,
87             mockInventoryPersistence,
88             mockCmHandleQueries,
89             mockCpsCmHandlerQueryService,
90             mockLcmEventsCmHandleStateHandler,
91             mockCpsDataService,
92             stubModuleSyncStartedOnCmHandles)
93
94     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
95
96     def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
97
98     def 'Write resource data for pass-through running from DMI using POST.'() {
99         given: 'cpsDataService returns valid datanode'
100             mockDataNode()
101         when: 'write resource data is called'
102             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
103                 'testResourceId', CREATE,
104                 '{some-json}', 'application/json')
105         then: 'DMI called with correct data'
106             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
107                     CREATE, '{some-json}', 'application/json')
108                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
109     }
110
111     def 'Get resource data for pass-through operational from DMI.'() {
112         given: 'cpsDataService returns valid data node'
113             mockDataNode()
114         and: 'get resource data from DMI is called'
115             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
116                     new ResponseEntity<>('dmi-response', HttpStatus.OK)
117         when: 'get resource data operational for cm-handle is called'
118             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
119         then: 'DMI returns a json response'
120             assert response == 'dmi-response'
121     }
122
123     def 'Get resource data for pass-through running from DMI.'() {
124         given: 'cpsDataService returns valid data node'
125             mockDataNode()
126         and: 'DMI returns valid response and data'
127             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
128                     new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
129         when: 'get resource data is called'
130             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
131         then: 'get resource data returns expected response'
132             assert response == '{dmi-response}'
133     }
134
135     def 'Get resource data for operational (cached) data.'() {
136         given: 'CPS Data service returns some object(s)'
137             mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
138         when: 'get resource data is called'
139             def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
140         then: 'get resource data returns teh first object from the data service'
141             assert response == 'First Object'
142     }
143
144     def 'Execute (async) data operation for #datastoreName from DMI.'() {
145         given: 'cpsDataService returns valid data node'
146             def dataOperationRequest = getDataOperationRequest(datastoreName)
147         when: 'request resource data for data operation is called'
148             objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId')
149         then: 'request resource data for data operation returns expected response'
150             1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId')
151         where: 'the following data stores are used'
152             datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
153     }
154
155     def 'Getting Yang Resources.'() {
156         when: 'yang resources is called'
157             objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
158         then: 'CPS module services is invoked for the correct dataspace and cm handle'
159             1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
160     }
161
162     def 'Get a cm handle.'() {
163         given: 'the system returns a yang modelled cm handle'
164             def dmiServiceName = 'some service name'
165             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
166                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
167                 lastUpdateTime: 'some-timestamp',
168                 dataSyncEnabled: false,
169                 dataStores: dataStores())
170             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
171             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
172             def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
173                 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
174             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
175         when: 'getting cm handle details for a given cm handle id from ncmp service'
176             def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
177         then: 'the result is a ncmpServiceCmHandle'
178             result.class == NcmpServiceCmHandle.class
179         and: 'the cm handle contains the cm handle id'
180             result.cmHandleId == 'some-cm-handle'
181         and: 'the cm handle contains the DMI Properties'
182             result.dmiProperties ==[ Book:'Romance Novel' ]
183         and: 'the cm handle contains the public Properties'
184             result.publicProperties == [ "Public Book":'Public Romance Novel' ]
185         and: 'the cm handle contains the cm handle composite state'
186             result.compositeState == compositeState
187
188     }
189
190     def 'Get cm handle public properties'() {
191         given: 'a yang modelled cm handle'
192             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
193             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
194             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
195         and: 'the system returns this yang modelled cm handle'
196             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
197         when: 'getting cm handle public properties for a given cm handle id from ncmp service'
198             def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
199         then: 'the result returns the correct data'
200             result == [ 'public prop' : 'some public prop' ]
201     }
202
203     def 'Execute cm handle id search for inventory'() {
204         given: 'a ConditionApiProperties object'
205             def conditionProperties = new ConditionProperties()
206             conditionProperties.conditionName = 'hasAllProperties'
207             conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
208             def conditionServiceProps = new CmHandleQueryServiceParameters()
209             conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
210         and: 'the system returns an set of cmHandle ids'
211             mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
212         when: 'getting cm handle id set for a given dmi property'
213             def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
214         then: 'the result returns the correct 2 elements'
215             assert result.size() == 2
216             assert result.contains('cmHandle1')
217             assert result.contains('cmHandle2')
218     }
219
220     def 'Get cm handle composite state'() {
221         given: 'a yang modelled cm handle'
222             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
223                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
224                 lastUpdateTime: 'some-timestamp',
225                 dataSyncEnabled: false,
226                 dataStores: dataStores())
227             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
228             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
229             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
230         and: 'the system returns this yang modelled cm handle'
231             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
232         when: 'getting cm handle composite state for a given cm handle id from ncmp service'
233             def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
234         then: 'the result returns the correct data'
235             result == compositeState
236     }
237
238     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
239         given: 'cpsDataService returns valid datanode'
240             mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
241                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
242         when: 'get resource data is called'
243             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
244                 'testResourceId', UPDATE,
245                 '{some-json}', 'application/json')
246         then: 'DMI called with correct data'
247             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
248                     UPDATE, '{some-json}', 'application/json')
249                 >> { new ResponseEntity<>(HttpStatus.OK) }
250     }
251
252     def 'Verify modules and create anchor params.'() {
253         given: 'dmi plugin registration return created cm handles'
254             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
255                     dmiDataPlugin: 'service2')
256             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
257             mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
258         when: 'parse and create cm handle in dmi registration then sync module'
259             objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration)
260         then: 'system persists the cm handle state'
261             1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> {
262                 args -> {
263                           def cmHandleStatePerCmHandle = (args[0] as Map)
264                           cmHandleStatePerCmHandle.each {
265                             assert it.key.id == 'test-cm-handle-id' && it.value == CmHandleState.ADVISED
266                           }
267                     }
268             }
269     }
270     
271     def 'Execute cm handle id search'() {
272         given: 'valid CmHandleQueryApiParameters input'
273             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
274             def conditionApiProperties = new ConditionApiProperties()
275             conditionApiProperties.conditionName = 'hasAllModules'
276             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
277             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
278         and: 'query cm handle method return with a data node list'
279             mockCpsCmHandlerQueryService.queryCmHandleIds(
280                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
281                     >> ['cm-handle-id-1']
282         when: 'execute cm handle search is called'
283             def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
284         then: 'result is the same collection as returned by the CPS Data Service'
285             assert result == ['cm-handle-id-1']
286     }
287
288     def 'Getting module definitions.'() {
289         when: 'get module definitions method is called with a valid cm handle ID'
290             objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
291         then: 'CPS module services is invoked once'
292             1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
293     }
294
295     def 'Execute cm handle search'() {
296         given: 'valid CmHandleQueryApiParameters input'
297             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
298             def conditionApiProperties = new ConditionApiProperties()
299             conditionApiProperties.conditionName = 'hasAllModules'
300             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
301             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
302         and: 'query cm handle method return with a data node list'
303             mockCpsCmHandlerQueryService.queryCmHandles(
304                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
305                     >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
306         when: 'execute cm handle search is called'
307             def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
308         then: 'result is the same collection as returned by the CPS Data Service'
309             assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
310     }
311
312     def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
313         given: 'an existing cm handle composite state'
314             def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
315                 dataStores: CompositeState.DataStores.builder()
316                     .operationalDataStore(CompositeState.Operational.builder()
317                         .dataStoreSyncState(initialDataSyncState)
318                         .build()).build())
319         and: 'get cm handle state returns the composite state for the given cm handle id'
320             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
321         when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
322             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
323         then: 'the data sync enabled flag is set to #dataSyncEnabled'
324             compositeState.dataSyncEnabled == dataSyncEnabledFlag
325         and: 'the data store sync state is set to #expectedDataStoreSyncState'
326             compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
327         and: 'the cps data service to delete data nodes is invoked the expected number of times'
328             deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode('NFP-Operational', 'some-cm-handle-id', '/netconf-state', _)
329         and: 'the inventory persistence service to update node leaves is called with the correct values'
330             saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
331         where: 'the following data sync enabled flag is used'
332             scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
333             'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 1
334             'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
335             'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
336             'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
337     }
338
339     def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
340         given: 'a cm handle composite state'
341             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
342         and: 'get cm handle state returns the composite state for the given cm handle id'
343             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
344         when: 'set data sync enabled is called with the data sync enabled flag set to true'
345             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
346         then: 'the expected exception is thrown'
347             thrown(CpsException)
348         and: 'the inventory persistence service to update node leaves is not invoked'
349             0 * mockInventoryPersistence.saveCmHandleState(_, _)
350     }
351
352     def 'Get all cm handle IDs by DMI plugin identifier.' () {
353         given: 'cm handle queries service returns cm handles'
354             1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
355         when: 'cm handle Ids are requested with dmi plugin identifier'
356             def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
357         then: 'the result size is correct'
358             assert result.size() == 2
359         and: 'the result returns the correct details'
360             assert result.containsAll('cm-handle-1','cm-handle-2')
361     }
362
363     def dataStores() {
364         CompositeState.DataStores.builder()
365             .operationalDataStore(CompositeState.Operational.builder()
366                 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
367                 .lastSyncTime('some-timestamp').build()).build()
368     }
369
370     def mockDataNode() {
371         mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
372                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
373     }
374
375     def getDataOperationRequest(datastore) {
376         def dataOperationRequest = new DataOperationRequest()
377         def dataOperationDefinitions = new ArrayList()
378         dataOperationDefinitions.add(getDataOperationDefinition(datastore))
379         dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
380         return dataOperationRequest
381     }
382
383     def getDataOperationDefinition(datastore) {
384         def dataOperationDefinition = new DataOperationDefinition()
385         dataOperationDefinition.setOperation("read")
386         dataOperationDefinition.setOperationId("operational-12")
387         dataOperationDefinition.setDatastore(datastore)
388         def targetIds = new ArrayList()
389         targetIds.add("some-cm-handle")
390         dataOperationDefinition.setCmHandleIds(targetIds)
391         return dataOperationDefinition
392     }
393 }