2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.netconf.cli.reader.custom;
10 import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput;
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;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
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.Node;
35 import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl;
36 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
37 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.Module;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * Custom reader implementation for filter elements in get/get-config rpcs. This
46 * reader overrides the default anyxml reader and reads filter as a schema path.
48 public class FilterReader extends AbstractReader<DataSchemaNode> {
50 private static final Logger LOG = LoggerFactory.getLogger(FilterReader.class);
52 public static final String SEPARATOR = "/";
54 private final Map<String, QName> mappedModules;
55 private final Map<URI, QName> mappedModulesNamespace;
57 public FilterReader(final ConsoleIO console, final SchemaContext remoteSchemaContext) {
58 super(console, remoteSchemaContext);
60 mappedModules = Maps.newHashMap();
61 mappedModulesNamespace = Maps.newHashMap();
62 for (final Module module : remoteSchemaContext.getModules()) {
63 final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName());
64 mappedModules.put(moduleQName.getLocalName(), moduleQName);
65 mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName);
71 public static final QName FILTER_TYPE_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01",
73 public static final String FILTER_TYPE_VALUE_DEFAULT = "subtree";
76 protected List<Node<?>> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException {
77 boolean redSuccessfuly = false;
78 Node<?> newNode = null;
80 console.writeLn("Filter " + schemaNode.getQName().getLocalName());
81 console.writeLn("Submit path of the data to retrieve. Use TAB for autocomplete");
83 final String rawValue = console.read();
85 // FIXME skip should be somewhere in abstractReader
86 if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
87 return Collections.emptyList();
90 final List<QName> filterPartsQNames = Lists.newArrayList();
93 for (final String part : rawValue.split(SEPARATOR)) {
94 final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
95 filterPartsQNames.add(qName);
98 Node<?> previous = null;
100 for (final QName qName : Lists.reverse(filterPartsQNames)) {
101 previous = new CompositeNodeTOImpl(qName, null,
102 previous == null ? Collections.<Node<?>> emptyList()
103 : Collections.<Node<?>> singletonList(previous));
106 final Map<QName, String> attributes = Collections.singletonMap(FILTER_TYPE_QNAME,
107 FILTER_TYPE_VALUE_DEFAULT);
108 newNode = previous == null ? null : ImmutableCompositeNode.create(schemaNode.getQName(), attributes,
109 Collections.<Node<?>> singletonList(previous));
110 redSuccessfuly = true;
111 } catch (final ReadingException e) {
112 final String message = "Specified filter path isn't correct.";
113 LOG.error(message, e);
114 console.writeLn(message);
116 } while (!redSuccessfuly);
117 return Collections.<Node<?>> singletonList(newNode);
121 protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
122 return new FilterConsoleContext(schemaNode, getSchemaContext());
125 private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {
127 private final SchemaContext remoteSchemaContext;
129 public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
131 this.remoteSchemaContext = remoteSchemaContext;
135 protected List<Completer> getAdditionalCompleters() {
136 return Collections.<Completer> singletonList(new FilterCompleter(remoteSchemaContext));
141 private final class FilterCompleter implements Completer {
143 private final SchemaContext remoteSchemaContext;
145 // TODO add skip to filter completer, better soulution would be to add
146 // SKIP completer before context completer if possible
148 public FilterCompleter(final SchemaContext remoteSchemaContext) {
149 this.remoteSchemaContext = remoteSchemaContext;
153 public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
154 final int idx = buffer.lastIndexOf(SEPARATOR);
156 final Optional<DataNodeContainer> currentNode = getCurrentNode(remoteSchemaContext, buffer);
157 if (currentNode.isPresent()) {
159 final Collection<String> transformed = Collections2.transform(currentNode.get().getChildNodes(),
160 new Function<DataSchemaNode, String>() {
162 public String apply(final DataSchemaNode input) {
163 return IOUtil.qNameToKeyString(input.getQName(),
164 mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
168 fillCandidates(buffer.substring(idx + 1), candidates, transformed);
171 return idx == -1 ? 0 : idx + 1;
174 private void fillCandidates(final String buffer, final List<CharSequence> candidates,
175 final Collection<String> transformed) {
176 final SortedSet<String> strings = new TreeSet<>(transformed);
178 if (buffer == null) {
179 candidates.addAll(strings);
181 for (final String match : strings.tailSet(buffer)) {
182 if (!match.startsWith(buffer)) {
185 candidates.add(match);
189 if (candidates.size() == 1) {
190 candidates.set(0, candidates.get(0) + SEPARATOR);
194 private Optional<DataNodeContainer> getCurrentNode(DataNodeContainer parent, final String buffer) {
195 for (final String part : buffer.split(SEPARATOR)) {
196 if (!IOUtil.isQName(part)) {
197 return Optional.of(parent);
202 qName = IOUtil.qNameFromKeyString(part, mappedModules);
203 } catch (final ReadingException e) {
204 return Optional.of(parent);
207 final DataSchemaNode dataChildByName = parent.getDataChildByName(qName);
208 if (dataChildByName instanceof DataNodeContainer) {
209 parent = (DataNodeContainer) dataChildByName;
211 return Optional.absent();
214 return Optional.of(parent);