/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.netconf.cli.reader.custom; import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.io.IOException; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import jline.console.completer.Completer; import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; import org.opendaylight.controller.netconf.cli.io.ConsoleContext; import org.opendaylight.controller.netconf.cli.io.ConsoleIO; import org.opendaylight.controller.netconf.cli.io.IOUtil; import org.opendaylight.controller.netconf.cli.reader.AbstractReader; import org.opendaylight.controller.netconf.cli.reader.ReadingException; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Custom reader implementation for filter elements in get/get-config rpcs. This * reader overrides the default anyxml reader and reads filter as a schema path. */ public class FilterReader extends AbstractReader { private static final Logger LOG = LoggerFactory.getLogger(FilterReader.class); public static final String SEPARATOR = "/"; private final Map mappedModules; private final Map mappedModulesNamespace; public FilterReader(final ConsoleIO console, final SchemaContext remoteSchemaContext) { super(console, remoteSchemaContext); mappedModules = Maps.newHashMap(); mappedModulesNamespace = Maps.newHashMap(); for (final Module module : remoteSchemaContext.getModules()) { final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName()); mappedModules.put(moduleQName.getLocalName(), moduleQName); mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName); } } // FIXME refactor public static final QName FILTER_TYPE_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", "type"); public static final String FILTER_TYPE_VALUE_DEFAULT = "subtree"; @Override protected List> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException { boolean redSuccessfuly = false; DataContainerChild newNode = null; do { console.writeLn("Filter " + schemaNode.getQName().getLocalName()); console.writeLn("Submit path of the data to retrieve. Use TAB for autocomplete"); final String rawValue = console.read(); // FIXME skip should be somewhere in abstractReader if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) { return Collections.emptyList(); } final List filterPartsQNames = Lists.newArrayList(); try { for (final String part : rawValue.split(SEPARATOR)) { final QName qName = IOUtil.qNameFromKeyString(part, mappedModules); filterPartsQNames.add(qName); } DataContainerChild previous = null; for (final QName qName : Lists.reverse(filterPartsQNames)) { previous = ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(qName)) .withValue(previous == null ? Collections.>emptyList() : Collections.>singletonList(previous)).build(); } final Map attributes = Collections.singletonMap(FILTER_TYPE_QNAME, FILTER_TYPE_VALUE_DEFAULT); newNode = previous == null ? null : ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(schemaNode.getQName())).withChild(previous).build(); redSuccessfuly = true; } catch (final ReadingException e) { final String message = "Specified filter path isn't correct."; LOG.error(message, e); console.writeLn(message); } } while (!redSuccessfuly); return Collections.> singletonList(newNode); } @Override protected ConsoleContext getContext(final DataSchemaNode schemaNode) { return new FilterConsoleContext(schemaNode, getSchemaContext()); } private final class FilterConsoleContext extends BaseConsoleContext { private final SchemaContext remoteSchemaContext; public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) { super(schemaNode); this.remoteSchemaContext = remoteSchemaContext; } @Override protected List getAdditionalCompleters() { return Collections. singletonList(new FilterCompleter(remoteSchemaContext)); } } private final class FilterCompleter implements Completer { private final SchemaContext remoteSchemaContext; // TODO add skip to filter completer, better soulution would be to add // SKIP completer before context completer if possible public FilterCompleter(final SchemaContext remoteSchemaContext) { this.remoteSchemaContext = remoteSchemaContext; } @Override public int complete(final String buffer, final int cursor, final List candidates) { final int idx = buffer.lastIndexOf(SEPARATOR); final Optional currentNode = getCurrentNode(remoteSchemaContext, buffer); if (currentNode.isPresent()) { final Collection transformed = Collections2.transform(currentNode.get().getChildNodes(), new Function() { @Override public String apply(final DataSchemaNode input) { return IOUtil.qNameToKeyString(input.getQName(), mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName()); } }); fillCandidates(buffer.substring(idx + 1), candidates, transformed); } return idx == -1 ? 0 : idx + 1; } private void fillCandidates(final String buffer, final List candidates, final Collection transformed) { final SortedSet strings = new TreeSet<>(transformed); if (buffer == null) { candidates.addAll(strings); } else { for (final String match : strings.tailSet(buffer)) { if (!match.startsWith(buffer)) { break; } candidates.add(match); } } if (candidates.size() == 1) { candidates.set(0, candidates.get(0) + SEPARATOR); } } private Optional getCurrentNode(DataNodeContainer parent, final String buffer) { for (final String part : buffer.split(SEPARATOR)) { if (!IOUtil.isQName(part)) { return Optional.of(parent); } QName qName; try { qName = IOUtil.qNameFromKeyString(part, mappedModules); } catch (final ReadingException e) { return Optional.of(parent); } final DataSchemaNode dataChildByName = parent.getDataChildByName(qName); if (dataChildByName instanceof DataNodeContainer) { parent = (DataNodeContainer) dataChildByName; } else { return Optional.absent(); } } return Optional.of(parent); } } }