CDS: Add stress test RPC to the cars model
[controller.git] / opendaylight / netconf / tools / netconf-cli / src / main / java / org / opendaylight / controller / netconf / cli / reader / custom / FilterReader.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.netconf.cli.reader.custom;
9
10 import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput;
11
12 import com.google.common.base.Function;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.Lists;
17 import com.google.common.collect.Maps;
18 import java.io.IOException;
19 import java.net.URI;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.SortedSet;
25 import java.util.TreeSet;
26 import jline.console.completer.Completer;
27 import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext;
28 import org.opendaylight.controller.netconf.cli.io.ConsoleContext;
29 import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
30 import org.opendaylight.controller.netconf.cli.io.IOUtil;
31 import org.opendaylight.controller.netconf.cli.reader.AbstractReader;
32 import org.opendaylight.controller.netconf.cli.reader.ReadingException;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
38 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.Module;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * Custom reader implementation for filter elements in get/get-config rpcs. This
47  * reader overrides the default anyxml reader and reads filter as a schema path.
48  */
49 public class FilterReader extends AbstractReader<DataSchemaNode> {
50
51     private static final Logger LOG = LoggerFactory.getLogger(FilterReader.class);
52
53     public static final String SEPARATOR = "/";
54
55     private final Map<String, QName> mappedModules;
56     private final Map<URI, QName> mappedModulesNamespace;
57
58     public FilterReader(final ConsoleIO console, final SchemaContext remoteSchemaContext) {
59         super(console, remoteSchemaContext);
60
61         mappedModules = Maps.newHashMap();
62         mappedModulesNamespace = Maps.newHashMap();
63         for (final Module module : remoteSchemaContext.getModules()) {
64             final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName());
65             mappedModules.put(moduleQName.getLocalName(), moduleQName);
66             mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName);
67         }
68     }
69
70     // FIXME refactor
71
72     public static final QName FILTER_TYPE_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01",
73             "type");
74     public static final String FILTER_TYPE_VALUE_DEFAULT = "subtree";
75
76     @Override
77     protected List<NormalizedNode<?, ?>> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException {
78         boolean redSuccessfuly = false;
79         DataContainerChild<?, ?> newNode = null;
80         do {
81             console.writeLn("Filter " + schemaNode.getQName().getLocalName());
82             console.writeLn("Submit path of the data to retrieve. Use TAB for autocomplete");
83
84             final String rawValue = console.read();
85
86             // FIXME skip should be somewhere in abstractReader
87             if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
88                 return Collections.emptyList();
89             }
90
91             final List<QName> filterPartsQNames = Lists.newArrayList();
92
93             try {
94                 for (final String part : rawValue.split(SEPARATOR)) {
95                     final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
96                     filterPartsQNames.add(qName);
97                 }
98
99                 DataContainerChild<?, ?> previous = null;
100
101                 for (final QName qName : Lists.reverse(filterPartsQNames)) {
102                     previous = ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(qName))
103                             .withValue(previous == null ? Collections.<DataContainerChild<?, ?>>emptyList()
104                                     : Collections.<DataContainerChild<?, ?>>singletonList(previous)).build();
105                 }
106
107                 final Map<QName, String> attributes = Collections.singletonMap(FILTER_TYPE_QNAME,
108                         FILTER_TYPE_VALUE_DEFAULT);
109                 newNode = previous == null ? null : ImmutableContainerNodeBuilder.create()
110                         .withNodeIdentifier(new NodeIdentifier(schemaNode.getQName())).withChild(previous).build();
111                 redSuccessfuly = true;
112             } catch (final ReadingException e) {
113                 final String message = "Specified filter path isn't correct.";
114                 LOG.error(message, e);
115                 console.writeLn(message);
116             }
117         } while (!redSuccessfuly);
118         return Collections.<NormalizedNode<?, ?>> singletonList(newNode);
119     }
120
121     @Override
122     protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
123         return new FilterConsoleContext(schemaNode, getSchemaContext());
124     }
125
126     private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {
127
128         private final SchemaContext remoteSchemaContext;
129
130         public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
131             super(schemaNode);
132             this.remoteSchemaContext = remoteSchemaContext;
133         }
134
135         @Override
136         protected List<Completer> getAdditionalCompleters() {
137             return Collections.<Completer> singletonList(new FilterCompleter(remoteSchemaContext));
138         }
139
140     }
141
142     private final class FilterCompleter implements Completer {
143
144         private final SchemaContext remoteSchemaContext;
145
146         // TODO add skip to filter completer, better soulution would be to add
147         // SKIP completer before context completer if possible
148
149         public FilterCompleter(final SchemaContext remoteSchemaContext) {
150             this.remoteSchemaContext = remoteSchemaContext;
151         }
152
153         @Override
154         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
155             final int idx = buffer.lastIndexOf(SEPARATOR);
156
157             final Optional<DataNodeContainer> currentNode = getCurrentNode(remoteSchemaContext, buffer);
158             if (currentNode.isPresent()) {
159
160                 final Collection<String> transformed = Collections2.transform(currentNode.get().getChildNodes(),
161                         new Function<DataSchemaNode, String>() {
162                             @Override
163                             public String apply(final DataSchemaNode input) {
164                                 return IOUtil.qNameToKeyString(input.getQName(),
165                                         mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
166                             }
167                         });
168
169                 fillCandidates(buffer.substring(idx + 1), candidates, transformed);
170             }
171
172             return idx == -1 ? 0 : idx + 1;
173         }
174
175         private void fillCandidates(final String buffer, final List<CharSequence> candidates,
176                 final Collection<String> transformed) {
177             final SortedSet<String> strings = new TreeSet<>(transformed);
178
179             if (buffer == null) {
180                 candidates.addAll(strings);
181             } else {
182                 for (final String match : strings.tailSet(buffer)) {
183                     if (!match.startsWith(buffer)) {
184                         break;
185                     }
186                     candidates.add(match);
187                 }
188             }
189
190             if (candidates.size() == 1) {
191                 candidates.set(0, candidates.get(0) + SEPARATOR);
192             }
193         }
194
195         private Optional<DataNodeContainer> getCurrentNode(DataNodeContainer parent, final String buffer) {
196             for (final String part : buffer.split(SEPARATOR)) {
197                 if (!IOUtil.isQName(part)) {
198                     return Optional.of(parent);
199                 }
200
201                 QName qName;
202                 try {
203                     qName = IOUtil.qNameFromKeyString(part, mappedModules);
204                 } catch (final ReadingException e) {
205                     return Optional.of(parent);
206                 }
207
208                 final DataSchemaNode dataChildByName = parent.getDataChildByName(qName);
209                 if (dataChildByName instanceof DataNodeContainer) {
210                     parent = (DataNodeContainer) dataChildByName;
211                 } else {
212                     return Optional.absent();
213                 }
214             }
215             return Optional.of(parent);
216         }
217     }
218
219 }