7a3b80b9f574c337c019e3864260eec0652992e9
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / sal / dom / broker / BackwardsCompatibleMountPoint.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.controller.sal.dom.broker;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Function;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Iterables;
15 import com.google.common.util.concurrent.CheckedFuture;
16 import com.google.common.util.concurrent.Futures;
17 import com.google.common.util.concurrent.JdkFutureAdapters;
18 import com.google.common.util.concurrent.ListenableFuture;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ExecutionException;
22 import javax.annotation.Nullable;
23 import org.opendaylight.controller.md.sal.common.api.RegistrationListener;
24 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
25 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler;
26 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandlerRegistration;
27 import org.opendaylight.controller.md.sal.common.api.data.DataReader;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
30 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
31 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
32 import org.opendaylight.controller.md.sal.common.api.routing.RouteChangeListener;
33 import org.opendaylight.controller.md.sal.common.impl.ListenerRegistry;
34 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
35 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
36 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
37 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
38 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
39 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
40 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
41 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
42 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
43 import org.opendaylight.controller.md.sal.dom.api.DOMService;
44 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
45 import org.opendaylight.controller.md.sal.dom.broker.impl.compat.BackwardsCompatibleDataBroker;
46 import org.opendaylight.controller.sal.common.DataStoreIdentifier;
47 import org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration;
48 import org.opendaylight.controller.sal.core.api.Broker.RpcRegistration;
49 import org.opendaylight.controller.sal.core.api.RoutedRpcDefaultImplementation;
50 import org.opendaylight.controller.sal.core.api.RpcImplementation;
51 import org.opendaylight.controller.sal.core.api.RpcProvisionRegistry;
52 import org.opendaylight.controller.sal.core.api.RpcRegistrationListener;
53 import org.opendaylight.controller.sal.core.api.RpcRoutingContext;
54 import org.opendaylight.controller.sal.core.api.data.DataChangeListener;
55 import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction;
56 import org.opendaylight.controller.sal.core.api.data.DataProviderService;
57 import org.opendaylight.controller.sal.core.api.data.DataValidator;
58 import org.opendaylight.controller.sal.core.api.model.SchemaService;
59 import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance;
60 import org.opendaylight.controller.sal.core.api.notify.NotificationListener;
61 import org.opendaylight.controller.sal.core.api.notify.NotificationPublishService;
62 import org.opendaylight.controller.sal.dom.broker.impl.NotificationRouterImpl;
63 import org.opendaylight.controller.sal.dom.broker.impl.SchemaAwareRpcBroker;
64 import org.opendaylight.controller.sal.dom.broker.impl.SchemaContextProvider;
65 import org.opendaylight.controller.sal.dom.broker.spi.NotificationRouter;
66 import org.opendaylight.controller.sal.dom.broker.util.ProxySchemaContext;
67 import org.opendaylight.yangtools.concepts.ListenerRegistration;
68 import org.opendaylight.yangtools.concepts.Registration;
69 import org.opendaylight.yangtools.yang.common.QName;
70 import org.opendaylight.yangtools.yang.common.RpcResult;
71 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
72 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
73 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
74 import org.opendaylight.yangtools.yang.model.api.Module;
75 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
76 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
77
78 public class BackwardsCompatibleMountPoint implements MountProvisionInstance, SchemaContextProvider, SchemaService {
79
80     private final DataProviderService dataReader;
81     private final DataReader<YangInstanceIdentifier,CompositeNode> readWrapper;
82
83     private final YangInstanceIdentifier mountPath;
84     private final NotificationPublishService notificationPublishService;
85     private final RpcProvisionRegistry rpcs;
86
87     private final ListenerRegistry<SchemaContextListener> schemaListenerRegistry = new ListenerRegistry<>();
88
89     private SchemaContext schemaContext;
90
91     public BackwardsCompatibleMountPoint(final YangInstanceIdentifier path, final DOMMountPointService.DOMMountPointBuilder mountPointBuilder) {
92         this.mountPath = Preconditions.checkNotNull(path);
93         Preconditions.checkNotNull(mountPointBuilder);
94
95         dataReader = new DataBrokerImpl();
96         readWrapper = new ReadWrapper();
97         notificationPublishService = new DelgatingNotificationPublishService();
98         rpcs = new SchemaAwareRpcBroker(path.toString(), this);
99
100         mountPointBuilder.addService(DOMDataBroker.class, new BackwardsCompatibleDomStore(dataReader, this));
101         mountPointBuilder.addService(NotificationPublishService.class, notificationPublishService);
102         mountPointBuilder.addService(RpcProvisionRegistry.class, rpcs);
103
104         mountPointBuilder.addInitialSchemaContext(new ProxySchemaContext(this));
105
106         mountPointBuilder.register();
107     }
108
109     public BackwardsCompatibleMountPoint(final YangInstanceIdentifier path, final DOMMountPoint mount) {
110         this.mountPath = Preconditions.checkNotNull(path);
111         Preconditions.checkNotNull(mount);
112
113         final DOMDataBroker domBroker = getServiceWithCheck(mount, DOMDataBroker.class);
114
115         this.schemaContext = mount.getSchemaContext();
116
117         dataReader = new BackwardsCompatibleDataBroker(domBroker, this);
118
119         // Set schema context to provide it for BackwardsCompatibleDataBroker
120         if(schemaContext != null) {
121             setSchemaContext(schemaContext);
122         }
123
124         readWrapper = new ReadWrapper();
125
126         notificationPublishService = getServiceWithCheck(mount, NotificationPublishService.class);
127         rpcs = getServiceWithCheck(mount, RpcProvisionRegistry.class);
128     }
129
130     private <T extends DOMService> T getServiceWithCheck(final DOMMountPoint mount, final Class<T> type) {
131         final Optional<T> serviceOptional = mount.getService(type);
132         Preconditions.checkArgument(serviceOptional.isPresent(), "Service {} has to be set in {}. " +
133                 "Cannot construct backwards compatible mount wrapper without it", type, mount);
134         return serviceOptional.get();
135     }
136
137     @Override
138     public void addModule(final Module module) {
139         throw new UnsupportedOperationException();
140     }
141
142     @Override
143     public void removeModule(final Module module) {
144         throw new UnsupportedOperationException();
145     }
146
147     @Override
148     public SchemaContext getSessionContext() {
149         return getSchemaContext();
150     }
151
152     @Override
153     public SchemaContext getGlobalContext() {
154         return getSchemaContext();
155     }
156
157     @Override
158     public ListenerRegistration<SchemaContextListener> registerSchemaContextListener(final SchemaContextListener listener) {
159         return schemaListenerRegistry.register(listener);
160     }
161
162     @Override
163     public void publish(final CompositeNode notification) {
164         notificationPublishService.publish(notification);
165     }
166
167     @Override
168     public ListenerRegistration<NotificationListener> addNotificationListener(final QName notification, final NotificationListener listener) {
169         return notificationPublishService.addNotificationListener(notification, listener);
170     }
171
172     // TODO Read wrapper is never used ... same in org.opendaylight.controller.sal.dom.broker.MountPointImpl
173     public DataReader<YangInstanceIdentifier, CompositeNode> getReadWrapper() {
174         return readWrapper;
175     }
176
177     @Override
178     public CompositeNode readConfigurationData(final YangInstanceIdentifier path) {
179         return dataReader.readConfigurationData(path);
180     }
181
182     @Override
183     public CompositeNode readOperationalData(final YangInstanceIdentifier path) {
184         return dataReader.readOperationalData(path);
185     }
186
187     @Override
188     public Registration registerOperationalReader(
189             final YangInstanceIdentifier path, final DataReader<YangInstanceIdentifier, CompositeNode> reader) {
190         return dataReader.registerOperationalReader(path, reader);
191     }
192
193     @Override
194     public Registration registerConfigurationReader(
195             final YangInstanceIdentifier path, final DataReader<YangInstanceIdentifier, CompositeNode> reader) {
196         return dataReader.registerConfigurationReader(path, reader);
197     }
198
199     @Override
200     public RoutedRpcRegistration addRoutedRpcImplementation(final QName rpcType, final RpcImplementation implementation) {
201         return rpcs.addRoutedRpcImplementation(rpcType, implementation);
202     }
203
204     @Override
205     public void setRoutedRpcDefaultDelegate(final RoutedRpcDefaultImplementation defaultImplementation) {
206         rpcs.setRoutedRpcDefaultDelegate(defaultImplementation);
207     }
208
209     @Override
210     public RpcRegistration addRpcImplementation(final QName rpcType, final RpcImplementation implementation)
211             throws IllegalArgumentException {
212         return rpcs.addRpcImplementation(rpcType, implementation);
213     }
214
215     @Override
216     public Set<QName> getSupportedRpcs() {
217         return rpcs.getSupportedRpcs();
218     }
219
220     @Override
221     public ListenableFuture<RpcResult<CompositeNode>> invokeRpc(final QName rpc, final CompositeNode input) {
222         return rpcs.invokeRpc(rpc, input);
223     }
224
225     @Override
226     public ListenerRegistration<RpcRegistrationListener> addRpcRegistrationListener(final RpcRegistrationListener listener) {
227         return rpcs.addRpcRegistrationListener(listener);
228     }
229
230     @Override
231     public ListenableFuture<RpcResult<CompositeNode>> rpc(final QName type, final CompositeNode input) {
232         return rpcs.invokeRpc(type, input);
233     }
234
235     @Override
236     public DataModificationTransaction beginTransaction() {
237         return dataReader.beginTransaction();
238     }
239
240     @Override
241     public ListenerRegistration<DataChangeListener> registerDataChangeListener(final YangInstanceIdentifier path,
242             final DataChangeListener listener) {
243         return dataReader.registerDataChangeListener(path, listener);
244     }
245
246     @Override
247     public Registration registerCommitHandler(
248             final YangInstanceIdentifier path, final DataCommitHandler<YangInstanceIdentifier, CompositeNode> commitHandler) {
249         return dataReader.registerCommitHandler(path, commitHandler);
250     }
251
252     @Override
253     public void removeRefresher(final DataStoreIdentifier store, final DataRefresher refresher) {
254         // NOOP
255     }
256
257     @Override
258     public void addRefresher(final DataStoreIdentifier store, final DataRefresher refresher) {
259         // NOOP
260     }
261
262     @Override
263     public void addValidator(final DataStoreIdentifier store, final DataValidator validator) {
264         // NOOP
265     }
266     @Override
267     public void removeValidator(final DataStoreIdentifier store, final DataValidator validator) {
268         // NOOP
269     }
270
271     @Override
272     public SchemaContext getSchemaContext() {
273         return schemaContext;
274     }
275
276     @Override
277     public void setSchemaContext(final SchemaContext schemaContext) {
278         this.schemaContext = schemaContext;
279         for (ListenerRegistration<SchemaContextListener> schemaServiceListenerListenerRegistration : schemaListenerRegistry.getListeners()) {
280             schemaServiceListenerListenerRegistration.getInstance().onGlobalContextUpdated(schemaContext);
281         }
282     }
283
284     class ReadWrapper implements DataReader<YangInstanceIdentifier, CompositeNode> {
285         private YangInstanceIdentifier shortenPath(final YangInstanceIdentifier path) {
286             if (!mountPath.contains(path)) {
287                 return null;
288             }
289             return YangInstanceIdentifier.create(Iterables.skip(path.getPathArguments(), Iterables.size(mountPath.getPathArguments())));
290         }
291
292         @Override
293         public CompositeNode readConfigurationData(final YangInstanceIdentifier path) {
294             final YangInstanceIdentifier newPath = shortenPath(path);
295             if(newPath == null) {
296                 return null;
297             }
298             return BackwardsCompatibleMountPoint.this.readConfigurationData(newPath);
299         }
300
301         @Override
302         public CompositeNode readOperationalData(final YangInstanceIdentifier path) {
303             final YangInstanceIdentifier newPath = shortenPath(path);
304             if(newPath == null) {
305                 return null;
306             }
307             return BackwardsCompatibleMountPoint.this.readOperationalData(newPath);
308         }
309     }
310
311     @Override
312     public ListenerRegistration<RegistrationListener<DataCommitHandlerRegistration<YangInstanceIdentifier, CompositeNode>>> registerCommitHandlerListener(
313             final RegistrationListener<DataCommitHandlerRegistration<YangInstanceIdentifier, CompositeNode>> commitHandlerListener) {
314         return dataReader.registerCommitHandlerListener(commitHandlerListener);
315     }
316
317     @Override
318     public <L extends RouteChangeListener<RpcRoutingContext, YangInstanceIdentifier>> ListenerRegistration<L> registerRouteChangeListener(
319             final L listener) {
320         return rpcs.registerRouteChangeListener(listener);
321     }
322
323     @VisibleForTesting
324     static final class BackwardsCompatibleDomStore implements DOMDataBroker {
325         private final DataProviderService dataReader;
326         private final SchemaContextProvider schemaContextProvider;
327
328         public BackwardsCompatibleDomStore(final DataProviderService dataReader, final SchemaContextProvider schemaContextProvider) {
329             this.dataReader = dataReader;
330             this.schemaContextProvider = schemaContextProvider;
331         }
332
333         @Override
334         public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
335             final DataNormalizer dataNormalizer = new DataNormalizer(schemaContextProvider.getSchemaContext());
336             return new BackwardsCompatibleReadTransaction(dataReader, dataNormalizer);
337         }
338
339         @Override
340         public DOMDataWriteTransaction newWriteOnlyTransaction() {
341             final DataNormalizer dataNormalizer = new DataNormalizer(schemaContextProvider.getSchemaContext());
342             return new BackwardsCompatibleWriteTransaction(dataReader, dataNormalizer);
343         }
344
345         @Override
346         public ListenerRegistration<DOMDataChangeListener> registerDataChangeListener(final LogicalDatastoreType store, final YangInstanceIdentifier path, final DOMDataChangeListener listener, final DataChangeScope triggeringScope) {
347             throw new UnsupportedOperationException("Register data listener not supported for mount point");
348         }
349
350         @Override
351         public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
352             throw new UnsupportedOperationException("Transaction chain not supported for mount point");
353         }
354
355         @Override
356         public DOMDataReadWriteTransaction newReadWriteTransaction() {
357             final DataNormalizer dataNormalizer = new DataNormalizer(schemaContextProvider.getSchemaContext());
358             return new BackwardsCompatibleReadWriteTransaction(dataReader, dataNormalizer);
359         }
360
361         @VisibleForTesting
362         static final class BackwardsCompatibleReadTransaction implements DOMDataReadOnlyTransaction {
363             private final DataProviderService dataReader;
364             private final DataNormalizer normalizer;
365
366             public BackwardsCompatibleReadTransaction(final DataProviderService dataReader, final DataNormalizer normalizer) {
367                 this.dataReader = dataReader;
368                 this.normalizer = normalizer;
369             }
370
371             @Override
372             public Object getIdentifier() {
373                 return this;
374             }
375
376             @Override
377             public void close() {
378                 // NOOP
379             }
380
381             @Override
382             public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
383                     final LogicalDatastoreType store, final YangInstanceIdentifier path) {
384
385                 CompositeNode rawData = null;
386
387                 switch (store) {
388                     case CONFIGURATION: {
389                         rawData = dataReader.readConfigurationData(path);
390                         break;
391                     }
392                     case OPERATIONAL: {
393                         rawData = dataReader.readOperationalData(path);
394                         break;
395                     }
396                 }
397                 Preconditions.checkNotNull(rawData, "Unable to read %s data on path %s", store, path);
398
399                 final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalized = normalizer.toNormalized(path, rawData);
400                 final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = Optional.<NormalizedNode<?, ?>>fromNullable(normalized.getValue());
401                 return Futures.immediateCheckedFuture(normalizedNodeOptional);
402             }
403
404             @Override public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store,
405                 final YangInstanceIdentifier path) {
406
407                 try {
408                     return Futures.immediateCheckedFuture(read(store, path).get().isPresent());
409                 } catch (InterruptedException | ExecutionException e) {
410                     return Futures.immediateFailedCheckedFuture(new ReadFailedException("Exists failed",e));
411                 }
412             }
413         }
414
415         @VisibleForTesting
416         static final class BackwardsCompatibleWriteTransaction implements DOMDataWriteTransaction {
417             private DataModificationTransaction oldTx;
418             private final DataNormalizer dataNormalizer;
419
420             public BackwardsCompatibleWriteTransaction(final DataProviderService dataReader, final DataNormalizer dataNormalizer) {
421                 this.oldTx = dataReader.beginTransaction();
422                 this.dataNormalizer = dataNormalizer;
423             }
424
425             @Override
426             public Object getIdentifier() {
427                 return this;
428             }
429
430             @Override
431             public boolean cancel() {
432                 oldTx = null;
433                 return true;
434             }
435
436             @Override
437             public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
438                 final CompositeNode legacyData = dataNormalizer.toLegacy(path, data);
439                 try {
440                     final YangInstanceIdentifier legacyPath = dataNormalizer.toLegacy(path);
441
442                     switch (store) {
443                         case CONFIGURATION: {
444                             oldTx.putConfigurationData(legacyPath, legacyData);
445                             return;
446                         }
447                     }
448
449                     throw new IllegalArgumentException("Cannot put data " + path + " to datastore " + store);
450                 } catch (final DataNormalizationException e) {
451                     throw new IllegalArgumentException(String.format("Cannot transform path %s to legacy format", path), e);
452                 }
453             }
454
455             @Override
456             public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
457                 // TODO not supported
458                 throw new UnsupportedOperationException("Merge not supported for mount point");
459             }
460
461             @Override
462             public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
463                 try {
464                     final YangInstanceIdentifier legacyPath = dataNormalizer.toLegacy(path);
465
466                     switch (store) {
467                         case CONFIGURATION: {
468                             oldTx.removeConfigurationData(legacyPath);
469                             return;
470                         }
471                     }
472                     throw new IllegalArgumentException("Cannot delete data " + path + " from datastore " + store);
473                 } catch (final DataNormalizationException e) {
474                     throw new IllegalArgumentException(String.format("Cannot transform path %s to legacy format", path), e);
475                 }
476             }
477
478             @Override
479             public CheckedFuture<Void, TransactionCommitFailedException> submit() {
480                 final ListenableFuture<Void> commitAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
481                     @Override
482                     public Void apply(@Nullable final RpcResult<TransactionStatus> input) {
483                         return null;
484                     }
485                 });
486
487                 return Futures.makeChecked(commitAsVoid, new Function<Exception, TransactionCommitFailedException>() {
488                     @Override
489                     public TransactionCommitFailedException apply(@Nullable final Exception input) {
490                         return new TransactionCommitFailedException("Commit failed", input);
491                     }
492                 });
493             }
494
495             @Override
496             public ListenableFuture<RpcResult<TransactionStatus>> commit() {
497                 return JdkFutureAdapters.listenInPoolThread(oldTx.commit());
498             }
499         }
500
501
502         @VisibleForTesting
503         static class BackwardsCompatibleReadWriteTransaction implements DOMDataReadWriteTransaction {
504
505             private final DataProviderService dataReader;
506             private final DataNormalizer dataNormalizer;
507             private final BackwardsCompatibleWriteTransaction delegateWriteTx;
508
509             public BackwardsCompatibleReadWriteTransaction(final DataProviderService dataReader, final DataNormalizer dataNormalizer) {
510                 this.dataReader = dataReader;
511                 this.dataNormalizer = dataNormalizer;
512                 this.delegateWriteTx = new BackwardsCompatibleWriteTransaction(dataReader, dataNormalizer);
513             }
514
515             @Override
516             public Object getIdentifier() {
517                 return this;
518             }
519
520             @Override
521             public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
522                     final LogicalDatastoreType store, final YangInstanceIdentifier path) {
523                 return new BackwardsCompatibleReadTransaction(dataReader, dataNormalizer).read(store, path);
524             }
525
526             @Override public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store,
527                 final YangInstanceIdentifier path) {
528
529                 try {
530                     return Futures.immediateCheckedFuture(read(store, path).get().isPresent());
531                 } catch (InterruptedException | ExecutionException e) {
532                     return Futures.immediateFailedCheckedFuture(new ReadFailedException("Exists failed",e));
533                 }
534             }
535
536             @Override
537             public boolean cancel() {
538                 return delegateWriteTx.cancel();
539             }
540
541             @Override
542             public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
543                 delegateWriteTx.put(store, path, data);
544             }
545
546             @Override
547             public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
548                 delegateWriteTx.merge(store, path, data);
549             }
550
551             @Override
552             public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
553                 delegateWriteTx.delete(store, path);
554             }
555
556             @Override
557             public CheckedFuture<Void, TransactionCommitFailedException> submit() {
558                 return delegateWriteTx.submit();
559             }
560
561             @Override
562             public ListenableFuture<RpcResult<TransactionStatus>> commit() {
563                 return delegateWriteTx.commit();
564             }
565         }
566     }
567
568     private class DelgatingNotificationPublishService implements NotificationPublishService {
569         private final NotificationRouter notificationRouter;
570
571         public DelgatingNotificationPublishService(final NotificationRouter notificationRouter) {
572             this.notificationRouter = notificationRouter;
573         }
574
575         private DelgatingNotificationPublishService() {
576             this(new NotificationRouterImpl());
577         }
578
579         @Override
580         public void publish(final CompositeNode notification) {
581             notificationRouter.publish(notification);
582         }
583
584         @Override
585         public ListenerRegistration<NotificationListener> addNotificationListener(final QName notification, final NotificationListener listener) {
586             return notificationRouter.addNotificationListener(notification, listener);
587         }
588     }
589 }