b0e4c48541db824418aa008c82b2180a54b2aa42
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / netconf / sal / restconf / impl / BrokerFacade.java
1 /**
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.sal.restconf.impl;
9
10 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
11 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
12
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.Lists;
17 import com.google.common.util.concurrent.CheckedFuture;
18 import com.google.common.util.concurrent.FutureCallback;
19 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture;
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.ExecutionException;
26 import javax.annotation.Nullable;
27 import javax.ws.rs.core.Response.Status;
28 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
33 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
34 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
35 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
36 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
37 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
38 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
39 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
40 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
41 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
42 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
43 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
44 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
45 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
46 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
47 import org.opendaylight.yangtools.concepts.ListenerRegistration;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
50 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
53 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
54 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
55 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 public class BrokerFacade {
60     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
61
62     private final static BrokerFacade INSTANCE = new BrokerFacade();
63     private volatile DOMRpcService rpcService;
64     private volatile ConsumerSession context;
65     private DOMDataBroker domDataBroker;
66     private DOMNotificationService domNotification;
67
68     private BrokerFacade() {
69     }
70
71     public void setRpcService(final DOMRpcService router) {
72         this.rpcService = router;
73     }
74
75     public void setDomNotificationService(final DOMNotificationService domNotification) {
76         this.domNotification = domNotification;
77     }
78
79     public void setContext(final ConsumerSession context) {
80         this.context = context;
81     }
82
83     public static BrokerFacade getInstance() {
84         return BrokerFacade.INSTANCE;
85     }
86
87     private void checkPreconditions() {
88         if ((this.context == null) || (this.domDataBroker == null)) {
89             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
90         }
91     }
92
93     // READ configuration
94     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
95         checkPreconditions();
96         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
97     }
98
99     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
100         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
101         if (domDataBrokerService.isPresent()) {
102             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
103         }
104         final String errMsg = "DOM data broker service isn't available for mount point " + path;
105         LOG.warn(errMsg);
106         throw new RestconfDocumentedException(errMsg);
107     }
108
109     // READ operational
110     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
111         checkPreconditions();
112         return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
113     }
114
115     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
116         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
117         if (domDataBrokerService.isPresent()) {
118             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
119         }
120         final String errMsg = "DOM data broker service isn't available for mount point " + path;
121         LOG.warn(errMsg);
122         throw new RestconfDocumentedException(errMsg);
123     }
124
125     // PUT configuration
126     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
127             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
128         checkPreconditions();
129         return putDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
130     }
131
132     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
133             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
134         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
135         if (domDataBrokerService.isPresent()) {
136             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
137                     payload, mountPoint.getSchemaContext());
138         }
139         final String errMsg = "DOM data broker service isn't available for mount point " + path;
140         LOG.warn(errMsg);
141         throw new RestconfDocumentedException(errMsg);
142     }
143
144     public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
145                                                                       final SchemaContext globalSchema)
146             throws InterruptedException {
147         final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
148         final List<PATCHStatusEntity> editCollection = new ArrayList<>();
149
150         List<RestconfError> editErrors;
151         int errorCounter = 0;
152
153         for (final PATCHEntity patchEntity : context.getData()) {
154             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
155
156             switch (operation) {
157                 case CREATE:
158                     if (errorCounter == 0) {
159                         try {
160                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
161                                     patchEntity.getNode(), globalSchema);
162                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
163                         } catch (final RestconfDocumentedException e) {
164                             editErrors = new ArrayList<>();
165                             editErrors.addAll(e.getErrors());
166                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
167                             errorCounter++;
168                         }
169                     }
170                     break;
171                 case REPLACE:
172                     if (errorCounter == 0) {
173                         try {
174                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
175                                     .getTargetNode(), patchEntity.getNode(), globalSchema);
176                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
177                         } catch (final RestconfDocumentedException e) {
178                             editErrors = new ArrayList<>();
179                             editErrors.addAll(e.getErrors());
180                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
181                             errorCounter++;
182                         }
183                     }
184                     break;
185                 case DELETE:
186                     if (errorCounter == 0) {
187                         try {
188                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
189                                     .getTargetNode());
190                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
191                         } catch (final RestconfDocumentedException e) {
192                             editErrors = new ArrayList<>();
193                             editErrors.addAll(e.getErrors());
194                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
195                             errorCounter++;
196                         }
197                     }
198                     break;
199                 case REMOVE:
200                     if (errorCounter == 0) {
201                         try {
202                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
203                                     .getTargetNode());
204                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
205                         } catch (final RestconfDocumentedException e) {
206                             LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
207                                     patchEntity.getEditId(), e);
208                         }
209                     }
210                     break;
211                 case MERGE:
212                     if (errorCounter == 0) {
213                         try {
214                             mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
215                                     patchEntity.getNode(), globalSchema);
216                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
217                         } catch (final RestconfDocumentedException e) {
218                             editErrors = new ArrayList<>();
219                             editErrors.addAll(e.getErrors());
220                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
221                             errorCounter++;
222                         }
223                     }
224                     break;
225             }
226         }
227
228         // if errors then cancel transaction and return error status
229         if (errorCounter != 0) {
230             patchTransaction.cancel();
231             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
232         }
233
234         // if no errors commit transaction
235         final CountDownLatch waiter = new CountDownLatch(1);
236         final CheckedFuture<Void, TransactionCommitFailedException> future = patchTransaction.submit();
237         final PATCHStatusContextHelper status = new PATCHStatusContextHelper();
238
239         Futures.addCallback(future, new FutureCallback<Void>() {
240             @Override
241             public void onSuccess(@Nullable final Void result) {
242                 status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
243                         true, null));
244                 waiter.countDown();
245             }
246
247             @Override
248             public void onFailure(final Throwable t) {
249                 // if commit failed it is global error
250                 status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
251                         false, Lists.newArrayList(
252                         new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
253                 waiter.countDown();
254             }
255         });
256
257         waiter.await();
258         return status.getStatus();
259     }
260
261     // POST configuration
262     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
263             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
264         checkPreconditions();
265         return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
266     }
267
268     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
269             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
270         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
271         if (domDataBrokerService.isPresent()) {
272             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
273                     payload, mountPoint.getSchemaContext());
274         }
275         final String errMsg = "DOM data broker service isn't available for mount point " + path;
276         LOG.warn(errMsg);
277         throw new RestconfDocumentedException(errMsg);
278     }
279
280     // DELETE configuration
281     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
282             final YangInstanceIdentifier path) {
283         checkPreconditions();
284         return deleteDataViaTransaction(this.domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
285     }
286
287     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
288             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
289         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
290         if (domDataBrokerService.isPresent()) {
291             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
292         }
293         final String errMsg = "DOM data broker service isn't available for mount point " + path;
294         LOG.warn(errMsg);
295         throw new RestconfDocumentedException(errMsg);
296     }
297
298     // RPC
299     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
300         checkPreconditions();
301         if (this.rpcService == null) {
302             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
303         }
304         LOG.trace("Invoke RPC {} with input: {}", type, input);
305         return this.rpcService.invokeRpc(type, input);
306     }
307
308     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
309             final ListenerAdapter listener) {
310         checkPreconditions();
311
312         if (listener.isListening()) {
313             return;
314         }
315
316         final YangInstanceIdentifier path = listener.getPath();
317         final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
318                 datastore, path, listener, scope);
319
320         listener.setRegistration(registration);
321     }
322
323     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
324             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
325         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
326         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
327         if (listenableFuture != null) {
328             Optional<NormalizedNode<?, ?>> optional;
329             try {
330                 LOG.debug("Reading result data from transaction.");
331                 optional = listenableFuture.get();
332             } catch (InterruptedException | ExecutionException e) {
333                 LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
334                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
335
336             }
337             if (optional != null) {
338                 if (optional.isPresent()) {
339                     return optional.get();
340                 }
341             }
342         }
343         return null;
344     }
345
346     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
347             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
348             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
349         // FIXME: This is doing correct post for container and list children
350         //        not sure if this will work for choice case
351         if(payload instanceof MapNode) {
352             LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
353             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
354             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
355             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
356             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
357                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
358                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
359                 rWTransaction.put(datastore, childPath, child);
360             }
361         } else {
362             checkItemDoesNotExists(rWTransaction,datastore, path);
363             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
364             rWTransaction.put(datastore, path, payload);
365         }
366         return rWTransaction.submit();
367     }
368
369     private void postDataWithinTransaction(
370             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
371             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
372         // FIXME: This is doing correct post for container and list children
373         //        not sure if this will work for choice case
374         if(payload instanceof MapNode) {
375             LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
376             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
377             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
378             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
379             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
380                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
381                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
382                 rWTransaction.put(datastore, childPath, child);
383             }
384         } else {
385             checkItemDoesNotExists(rWTransaction,datastore, path);
386             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
387             rWTransaction.put(datastore, path, payload);
388         }
389     }
390
391     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
392         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
393         try {
394             if (futureDatastoreData.get()) {
395                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
396                 LOG.trace("{}:{}", errMsg, path);
397                 rWTransaction.cancel();
398                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
399                         ErrorTag.DATA_EXISTS);
400             }
401         } catch (InterruptedException | ExecutionException e) {
402             LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
403         }
404
405     }
406
407     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
408             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
409             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
410         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
411         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
412         writeTransaction.put(datastore, path, payload);
413         return writeTransaction.submit();
414     }
415
416     private void putDataWithinTransaction(
417             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
418             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
419         LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
420         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
421         writeTransaction.put(datastore, path, payload);
422     }
423
424     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
425             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
426             final YangInstanceIdentifier path) {
427         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
428         writeTransaction.delete(datastore, path);
429         return writeTransaction.submit();
430     }
431
432     private void deleteDataWithinTransaction(
433             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
434             final YangInstanceIdentifier path) {
435         LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
436         writeTransaction.delete(datastore, path);
437     }
438
439     private void mergeDataWithinTransaction(
440             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
441             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
442         LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
443         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
444
445         // merging is necessary only for lists otherwise we can call put method
446         if (payload instanceof MapNode) {
447             writeTransaction.merge(datastore, path, payload);
448         } else {
449             writeTransaction.put(datastore, path, payload);
450         }
451     }
452
453     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
454         this.domDataBroker = domDataBroker;
455     }
456
457     private void ensureParentsByMerge(final LogicalDatastoreType store,
458                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
459         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
460         YangInstanceIdentifier rootNormalizedPath = null;
461
462         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
463
464         while(it.hasNext()) {
465             final PathArgument pathArgument = it.next();
466             if(rootNormalizedPath == null) {
467                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
468             }
469
470             // Skip last element, its not a parent
471             if(it.hasNext()) {
472                 normalizedPathWithoutChildArgs.add(pathArgument);
473             }
474         }
475
476         // No parent structure involved, no need to ensure parents
477         if(normalizedPathWithoutChildArgs.isEmpty()) {
478             return;
479         }
480
481         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
482
483         final NormalizedNode<?, ?> parentStructure =
484                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
485         rwTx.merge(store, rootNormalizedPath, parentStructure);
486     }
487
488     public void registerToListenNotification(final NotificationListenerAdapter listener) {
489         checkPreconditions();
490
491         if (listener.isListening()) {
492             return;
493         }
494
495         final SchemaPath path = listener.getSchemaPath();
496         final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
497                 .registerNotificationListener(listener, path);
498
499         listener.setRegistration(registration);
500     }
501
502     private final class PATCHStatusContextHelper {
503         PATCHStatusContext status;
504
505         public PATCHStatusContext getStatus() {
506             return this.status;
507         }
508
509         public void setStatus(PATCHStatusContext status) {
510             this.status = status;
511         }
512     }
513 }