Implement registerNotificationListeners()
[netconf.git] / plugins / netconf-client-mdsal / src / main / java / org / opendaylight / netconf / client / mdsal / spi / NetconfDeviceNotificationService.java
1 /*
2  * Copyright (c) 2015 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.client.mdsal.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.collect.HashMultimap;
14 import com.google.common.collect.Multimap;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Map;
18 import java.util.Objects;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.mdsal.dom.api.DOMNotification;
21 import org.opendaylight.mdsal.dom.api.DOMNotificationListener;
22 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
23 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
24 import org.opendaylight.yangtools.concepts.AbstractRegistration;
25 import org.opendaylight.yangtools.concepts.ListenerRegistration;
26 import org.opendaylight.yangtools.concepts.Registration;
27 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 public final class NetconfDeviceNotificationService implements DOMNotificationService {
32     private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceNotificationService.class);
33
34     private final Multimap<Absolute, DOMNotificationListener> listeners = HashMultimap.create();
35
36     // Notification publish is very simple and hijacks the thread of the caller
37     // TODO: should we not reuse the implementation for notification router from mdsal-dom-broker ?
38     @SuppressWarnings("checkstyle:IllegalCatch")
39     public synchronized void publishNotification(final DOMNotification notification) {
40         for (var listener : listeners.get(notification.getType())) {
41             try {
42                 listener.onNotification(notification);
43             } catch (Exception e) {
44                 LOG.warn("Listener {} threw an uncaught exception during processing notification {}", listener,
45                     notification, e);
46             }
47         }
48     }
49
50     @Override
51     public <T extends DOMNotificationListener> ListenerRegistration<T> registerNotificationListener(final T listener,
52             final Collection<Absolute> types) {
53         final var lsnr = requireNonNull(listener);
54         final var typesArray = types.stream().map(Objects::requireNonNull).distinct().toArray(Absolute[]::new);
55         return switch (typesArray.length) {
56             case 0 -> new AbstractListenerRegistration<>(lsnr) {
57                 @Override
58                 protected void removeRegistration() {
59                     // No-op
60                 }
61             };
62             case 1 -> registerOne(lsnr, typesArray[0]);
63             default -> registerMultiple(lsnr, typesArray);
64         };
65     }
66
67     @Override
68     public <T extends DOMNotificationListener> ListenerRegistration<T> registerNotificationListener(final T listener,
69             final Absolute... types) {
70         return registerNotificationListener(listener, Arrays.asList(types));
71     }
72
73     @Override
74     public Registration registerNotificationListeners(final Map<Absolute, DOMNotificationListener> typeToListener) {
75         final var copy = Map.copyOf(typeToListener);
76         return switch (copy.size()) {
77             case 0 -> () -> {
78                 // No-op
79             };
80             case 1 -> {
81                 final var entry = copy.entrySet().iterator().next();
82                 yield registerOne(entry.getValue(), entry.getKey());
83             }
84             default -> registerMultiple(copy);
85         };
86     }
87
88     @VisibleForTesting
89     synchronized int size() {
90         return listeners.size();
91     }
92
93     private synchronized <T extends DOMNotificationListener> @NonNull ListenerRegistration<T> registerOne(
94             final @NonNull T listener, final Absolute type) {
95         listeners.put(type, listener);
96         return new AbstractListenerRegistration<>(listener) {
97             @Override
98             protected void removeRegistration() {
99                 synchronized (NetconfDeviceNotificationService.this) {
100                     listeners.remove(type, getInstance());
101                 }
102             }
103         };
104     }
105
106     private synchronized <T extends DOMNotificationListener> @NonNull ListenerRegistration<T> registerMultiple(
107             final @NonNull T listener, final Absolute[] types) {
108         for (var type : types) {
109             listeners.put(type, listener);
110         }
111         return new AbstractListenerRegistration<>(listener) {
112             @Override
113             protected void removeRegistration() {
114                 synchronized (NetconfDeviceNotificationService.this) {
115                     for (var type : types) {
116                         listeners.remove(type, getInstance());
117                     }
118                 }
119             }
120         };
121     }
122
123     private synchronized @NonNull Registration registerMultiple(final Map<Absolute, DOMNotificationListener> toReg) {
124         // we have at least two entries, which we will save as an array of 4 objects
125         int idx = 0;
126         final var array = new Object[toReg.size() * 2];
127         for (var entry : toReg.entrySet()) {
128             final var type = entry.getKey();
129             final var listener = entry.getValue();
130
131             listeners.put(type, listener);
132             array[idx++] = type;
133             array[idx++] = listener;
134         }
135
136         return new AbstractRegistration() {
137             @Override
138             protected void removeRegistration() {
139                 synchronized (NetconfDeviceNotificationService.this) {
140                     for (int i = 0, length = array.length; i < length; ) {
141                         final var type = array[i++];
142                         final var listener = array[i++];
143                         if (!listeners.remove(type, listener)) {
144                             LOG.warn("Failed to remove {} listener {}, very weird", type, listener, new Throwable());
145                         }
146                     }
147                 }
148             }
149         };
150     }
151 }