/* * 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.Preconditions; 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.CommandArgHandlerRegistry; 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; /** * 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 ConfigReader extends AbstractReader { public static final String SEPARATOR = "/"; private final CommandArgHandlerRegistry commandArgHandlerRegistry; private final Map mappedModules; private final Map mappedModulesNamespace; public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext, final CommandArgHandlerRegistry commandArgHandlerRegistry) { super(console, remoteSchemaContext); this.commandArgHandlerRegistry = commandArgHandlerRegistry; 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 + unite common code with FilterReader @Override protected List> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException { console.writeLn("Config " + schemaNode.getQName().getLocalName()); console.writeLn("Submit path of the data to edit. Use TAB for autocomplete"); final String rawValue = console.read(); // FIXME isSkip check should be somewhere in abstractReader if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) { return Collections.emptyList(); } final List filterPartsQNames = Lists.newArrayList(); for (final String part : rawValue.split(SEPARATOR)) { final QName qName = IOUtil.qNameFromKeyString(part, mappedModules); filterPartsQNames.add(qName); } List> previous = readInnerNode(rawValue); for (final QName qName : Lists.reverse(filterPartsQNames).subList(1, filterPartsQNames.size())) { previous = Collections.>singletonList( ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(qName)) .withValue(previous == null ? Collections.>emptyList() : (Collection) previous).build() ); } final DataContainerChild newNode = previous == null ? null : ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(schemaNode.getQName())) .withValue((Collection) previous).build(); return Collections.> singletonList(newNode); } private List> readInnerNode(final String pathString) throws ReadingException { final Optional schema = getCurrentNode(getSchemaContext(), pathString); Preconditions.checkState(schema.isPresent(), "Unable to find schema for %s", pathString); return commandArgHandlerRegistry.getGenericReader(getSchemaContext(), true).read(schema.get()); } @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; 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() && currentNode.get() instanceof DataNodeContainer) { final Collection childNodes = ((DataNodeContainer) currentNode.get()).getChildNodes(); final Collection transformed = Collections2.transform(childNodes, 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(DataSchemaNode parent, final String buffer) { for (final String part : buffer.split(SEPARATOR)) { if (IOUtil.isQName(part) == false) { return Optional.of(parent); } final QName qName; try { qName = IOUtil.qNameFromKeyString(part, mappedModules); } catch (final ReadingException e) { return Optional.of(parent); } if (parent instanceof DataNodeContainer) { parent = ((DataNodeContainer) parent).getDataChildByName(qName); } else { // This should check if we are at the end of buffer ? return Optional.of(parent); } } return Optional.of(parent); } }