Bug 8153: Enforce check-style rules for netconf - sal-netconf-connector
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / listener / NetconfSessionPreferences.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
9 package org.opendaylight.netconf.sal.connect.netconf.listener;
10
11 import com.google.common.base.MoreObjects;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Predicate;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.Iterables;
19 import com.google.common.collect.Maps;
20 import java.net.URI;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26 import org.opendaylight.netconf.client.NetconfClientSession;
27 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 public final class NetconfSessionPreferences {
34
35     private static final class ParameterMatcher {
36         private final Predicate<String> predicate;
37         private final int skipLength;
38
39         ParameterMatcher(final String name) {
40             predicate = new Predicate<String>() {
41                 @Override
42                 public boolean apply(final String input) {
43                     return input.startsWith(name);
44                 }
45             };
46
47             this.skipLength = name.length();
48         }
49
50         private String from(final Iterable<String> params) {
51             final Optional<String> o = Iterables.tryFind(params, predicate);
52             if (!o.isPresent()) {
53                 return null;
54             }
55
56             return o.get().substring(skipLength);
57         }
58     }
59
60     private static final Logger LOG = LoggerFactory.getLogger(NetconfSessionPreferences.class);
61     private static final ParameterMatcher MODULE_PARAM = new ParameterMatcher("module=");
62     private static final ParameterMatcher REVISION_PARAM = new ParameterMatcher("revision=");
63     private static final ParameterMatcher BROKEN_REVISON_PARAM = new ParameterMatcher("amp;revision=");
64     private static final Splitter AMP_SPLITTER = Splitter.on('&');
65     private static final Predicate<String> CONTAINS_REVISION = new Predicate<String>() {
66         @Override
67         public boolean apply(final String input) {
68             return input.contains("revision=");
69         }
70     };
71
72     private final Map<QName, CapabilityOrigin> moduleBasedCaps;
73     private final Map<String, CapabilityOrigin> nonModuleCaps;
74
75     NetconfSessionPreferences(final Map<String, CapabilityOrigin> nonModuleCaps,
76                               final Map<QName, CapabilityOrigin> moduleBasedCaps) {
77         this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps);
78         this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps);
79     }
80
81     public Set<QName> getModuleBasedCaps() {
82         return moduleBasedCaps.keySet();
83     }
84
85     public Map<QName, CapabilityOrigin> getModuleBasedCapsOrigin() {
86         return moduleBasedCaps;
87     }
88
89     public Set<String> getNonModuleCaps() {
90         return nonModuleCaps.keySet();
91     }
92
93     public Map<String, CapabilityOrigin> getNonModuleBasedCapsOrigin() {
94         return nonModuleCaps;
95     }
96
97     // allows partial matches - assuming parameters are in the same order
98     public boolean containsPartialNonModuleCapability(final String capability) {
99         final Iterator<String> iterator = getNonModuleCaps().iterator();
100         while (iterator.hasNext()) {
101             if (iterator.next().startsWith(capability)) {
102                 LOG.trace("capability {} partially matches {}", capability, nonModuleCaps);
103                 return true;
104             }
105         }
106         return false;
107     }
108
109     public boolean containsNonModuleCapability(final String capability) {
110         return nonModuleCaps.containsKey(capability);
111     }
112
113     public boolean containsModuleCapability(final QName capability) {
114         return moduleBasedCaps.containsKey(capability);
115     }
116
117     @Override
118     public String toString() {
119         return MoreObjects.toStringHelper(this)
120                 .add("capabilities", nonModuleCaps)
121                 .add("moduleBasedCapabilities", moduleBasedCaps)
122                 .add("rollback", isRollbackSupported())
123                 .add("monitoring", isMonitoringSupported())
124                 .add("candidate", isCandidateSupported())
125                 .add("writableRunning", isRunningWritable())
126                 .toString();
127     }
128
129     public boolean isRollbackSupported() {
130         return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString());
131     }
132
133     public boolean isCandidateSupported() {
134         return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
135     }
136
137     public boolean isRunningWritable() {
138         return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString());
139     }
140
141     public boolean isNotificationsSupported() {
142         return containsPartialNonModuleCapability(NetconfMessageTransformUtil.NETCONF_NOTIFICATONS_URI.toString())
143                 || containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS);
144     }
145
146     public boolean isMonitoringSupported() {
147         return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)
148                 || containsPartialNonModuleCapability(
149                         NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
150     }
151
152     /**
153      * Merge module-based list of capabilities with current list of module-based capabilities.
154      *
155      * @param netconfSessionModuleCapabilities capabilities to merge into this
156      *
157      * @return new instance of preferences with merged module-based capabilities
158      */
159     public NetconfSessionPreferences addModuleCaps(final NetconfSessionPreferences netconfSessionModuleCapabilities) {
160         final Map<QName, CapabilityOrigin> mergedCaps = Maps.newHashMapWithExpectedSize(moduleBasedCaps.size()
161                 + netconfSessionModuleCapabilities.getModuleBasedCaps().size());
162         mergedCaps.putAll(moduleBasedCaps);
163         mergedCaps.putAll(netconfSessionModuleCapabilities.getModuleBasedCapsOrigin());
164         return new NetconfSessionPreferences(getNonModuleBasedCapsOrigin(), mergedCaps);
165     }
166
167     /**
168      * Override current list of module-based capabilities.
169      *
170      * @param netconfSessionPreferences capabilities to override in this
171      *
172      * @return new instance of preferences with replaced module-based capabilities
173      */
174     public NetconfSessionPreferences replaceModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
175         return new NetconfSessionPreferences(
176                 getNonModuleBasedCapsOrigin(), netconfSessionPreferences.getModuleBasedCapsOrigin());
177     }
178
179     public NetconfSessionPreferences replaceModuleCaps(final Map<QName, CapabilityOrigin> newModuleBasedCaps) {
180         return new NetconfSessionPreferences(getNonModuleBasedCapsOrigin(), newModuleBasedCaps);
181     }
182
183
184     /**
185      * Merge list of non-module based capabilities with current list of non-module based capabilities.
186      *
187      * @param netconfSessionNonModuleCapabilities capabilities to merge into this
188      *
189      * @return new instance of preferences with merged non-module based capabilities
190      */
191     public NetconfSessionPreferences addNonModuleCaps(
192             final NetconfSessionPreferences netconfSessionNonModuleCapabilities) {
193         final Map<String, CapabilityOrigin> mergedCaps = Maps.newHashMapWithExpectedSize(
194                 nonModuleCaps.size() + netconfSessionNonModuleCapabilities.getNonModuleCaps().size());
195         mergedCaps.putAll(getNonModuleBasedCapsOrigin());
196         mergedCaps.putAll(netconfSessionNonModuleCapabilities.getNonModuleBasedCapsOrigin());
197         return new NetconfSessionPreferences(mergedCaps, getModuleBasedCapsOrigin());
198     }
199
200     /**
201      * Override current list of non-module based capabilities.
202      *
203      * @param netconfSessionPreferences capabilities to override in this
204      *
205      * @return new instance of preferences with replaced non-module based capabilities
206      */
207     public NetconfSessionPreferences replaceNonModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
208         return new NetconfSessionPreferences(
209                 netconfSessionPreferences.getNonModuleBasedCapsOrigin(), getModuleBasedCapsOrigin());
210     }
211
212     public static NetconfSessionPreferences fromNetconfSession(final NetconfClientSession session) {
213         return fromStrings(session.getServerCapabilities());
214     }
215
216     private static QName cachedQName(final String namespace, final String revision, final String moduleName) {
217         return QName.create(namespace, revision, moduleName).intern();
218     }
219
220     private static QName cachedQName(final String namespace, final String moduleName) {
221         return QName.create(URI.create(namespace), null, moduleName).withoutRevision().intern();
222     }
223
224     public static NetconfSessionPreferences fromStrings(final Collection<String> capabilities) {
225         // we do not know origin of capabilities from only Strings, so we set it to default value
226         return fromStrings(capabilities, CapabilityOrigin.DeviceAdvertised);
227     }
228
229     public static NetconfSessionPreferences fromStrings(final Collection<String> capabilities,
230                                                         final CapabilityOrigin capabilityOrigin) {
231         final Map<QName, CapabilityOrigin> moduleBasedCaps = new HashMap<>();
232         final Map<String, CapabilityOrigin> nonModuleCaps = new HashMap<>();
233
234         for (final String capability : capabilities) {
235             nonModuleCaps.put(capability, capabilityOrigin);
236             final int qmark = capability.indexOf('?');
237             if (qmark == -1) {
238                 continue;
239             }
240
241             final String namespace = capability.substring(0, qmark);
242             final Iterable<String> queryParams = AMP_SPLITTER.split(capability.substring(qmark + 1));
243             final String moduleName = MODULE_PARAM.from(queryParams);
244             if (Strings.isNullOrEmpty(moduleName)) {
245                 continue;
246             }
247
248             String revision = REVISION_PARAM.from(queryParams);
249             if (!Strings.isNullOrEmpty(revision)) {
250                 addModuleQName(moduleBasedCaps, nonModuleCaps, capability, cachedQName(namespace, revision, moduleName),
251                         capabilityOrigin);
252                 continue;
253             }
254
255             /*
256              * We have seen devices which mis-escape revision, but the revision may not
257              * even be there. First check if there is a substring that matches revision.
258              */
259             if (Iterables.any(queryParams, CONTAINS_REVISION)) {
260
261                 LOG.debug("Netconf device was not reporting revision correctly, trying to get amp;revision=");
262                 revision = BROKEN_REVISON_PARAM.from(queryParams);
263                 if (Strings.isNullOrEmpty(revision)) {
264                     LOG.warn("Netconf device returned revision incorrectly escaped for {}, ignoring it", capability);
265                     addModuleQName(moduleBasedCaps, nonModuleCaps, capability,
266                             cachedQName(namespace, moduleName), capabilityOrigin);
267                 } else {
268                     addModuleQName(moduleBasedCaps, nonModuleCaps, capability,
269                             cachedQName(namespace, revision, moduleName), capabilityOrigin);
270                 }
271                 continue;
272             }
273
274             // Fallback, no revision provided for module
275             addModuleQName(moduleBasedCaps, nonModuleCaps, capability,
276                     cachedQName(namespace, moduleName), capabilityOrigin);
277         }
278
279         return new NetconfSessionPreferences(ImmutableMap.copyOf(nonModuleCaps), ImmutableMap.copyOf(moduleBasedCaps));
280     }
281
282     private static void addModuleQName(final Map<QName, CapabilityOrigin> moduleBasedCaps,
283                                        final Map<String, CapabilityOrigin> nonModuleCaps, final String capability,
284                                        final QName qualifiedName, final CapabilityOrigin capabilityOrigin) {
285         moduleBasedCaps.put(qualifiedName, capabilityOrigin);
286         nonModuleCaps.remove(capability);
287     }
288
289     private final NetconfDeviceCapabilities capabilities = new NetconfDeviceCapabilities();
290
291     public NetconfDeviceCapabilities getNetconfDeviceCapabilities() {
292         return capabilities;
293     }
294
295
296 }