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.netconf.cli;
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import java.io.IOException;
13 import java.util.Collections;
14 import java.util.List;
16 import jline.console.UserInterruptException;
17 import jline.console.completer.Completer;
18 import jline.console.completer.StringsCompleter;
19 import org.opendaylight.netconf.cli.commands.Command;
20 import org.opendaylight.netconf.cli.commands.CommandConstants;
21 import org.opendaylight.netconf.cli.commands.CommandDispatcher;
22 import org.opendaylight.netconf.cli.commands.CommandInvocationException;
23 import org.opendaylight.netconf.cli.commands.input.Input;
24 import org.opendaylight.netconf.cli.commands.input.InputDefinition;
25 import org.opendaylight.netconf.cli.commands.output.Output;
26 import org.opendaylight.netconf.cli.commands.output.OutputDefinition;
27 import org.opendaylight.netconf.cli.io.ConsoleContext;
28 import org.opendaylight.netconf.cli.io.ConsoleIO;
29 import org.opendaylight.netconf.cli.reader.ReadingException;
30 import org.opendaylight.netconf.cli.writer.OutFormatter;
31 import org.opendaylight.netconf.cli.writer.WriteException;
32 import org.opendaylight.netconf.cli.writer.Writer;
33 import org.opendaylight.netconf.cli.writer.impl.NormalizedNodeWriter;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
40 * The top level cli state that dispatches command executions.
42 public class Cli implements Runnable {
43 private final CommandDispatcher commandRegistry;
44 private final CommandArgHandlerRegistry argumentHandlerRegistry;
45 private final SchemaContextRegistry schemaContextRegistry;
46 private final ConsoleIO consoleIO;
48 public Cli(final ConsoleIO consoleIO, final CommandDispatcher commandRegistry,
49 final CommandArgHandlerRegistry argumentHandlerRegistry,
50 final SchemaContextRegistry schemaContextRegistry) {
51 this.consoleIO = consoleIO;
52 this.commandRegistry = commandRegistry;
53 this.argumentHandlerRegistry = argumentHandlerRegistry;
54 this.schemaContextRegistry = schemaContextRegistry;
60 consoleIO.writeLn("Cli is up, available commands:");
61 final RootConsoleContext consoleContext = new RootConsoleContext(commandRegistry);
62 consoleIO.enterContext(consoleContext);
64 consoleIO.writeLn("");
67 final String commandName = consoleIO.read();
68 final Optional<Command> commandOpt = commandRegistry.getCommand(commandName);
70 if (!commandOpt.isPresent()) {
74 final Command command = commandOpt.get();
76 consoleIO.enterContext(command.getConsoleContext());
77 final Output response = command.invoke(handleInput(command.getInputDefinition()));
78 handleOutput(command, response);
79 } catch (final CommandInvocationException e) {
80 consoleIO.write(e.getMessage());
81 } catch (final UserInterruptException e) {
82 consoleIO.writeLn("Command " + command.getCommandId() + " was terminated.");
84 consoleIO.leaveContext();
88 } catch (final IOException e) {
89 throw new RuntimeException("IO failure", e);
93 private void handleOutput(final Command command, final Output response) {
94 final OutputDefinition outputDefinition = command.getOutputDefinition();
96 final Writer<DataSchemaNode> outHandler = argumentHandlerRegistry.getGenericWriter();
97 if (outputDefinition.isEmpty()) {
98 handleEmptyOutput(command, response);
100 handleRegularOutput(response, outputDefinition, outHandler);
104 private void handleRegularOutput(final Output response, final OutputDefinition outputDefinition,
105 final Writer<DataSchemaNode> outHandler) {
106 final Map<DataSchemaNode, List<NormalizedNode<?, ?>>> unwrap = response.unwrap(outputDefinition);
108 for (final DataSchemaNode schemaNode : unwrap.keySet()) {
109 Preconditions.checkNotNull(schemaNode);
113 // FIXME move custom writer to GenericWriter/Serializers ...
114 // this checks only first level
115 final Optional<Class<? extends Writer<DataSchemaNode>>> customReaderClassOpt = tryGetCustomHandler(
118 if (customReaderClassOpt.isPresent()) {
119 final Writer<DataSchemaNode> customReaderInstance = argumentHandlerRegistry
120 .getCustomWriter(customReaderClassOpt.get());
121 Preconditions.checkNotNull(customReaderInstance, "Unknown custom writer: %s",
122 customReaderClassOpt.get());
123 customReaderInstance.write(schemaNode, unwrap.get(schemaNode));
125 outHandler.write(schemaNode, unwrap.get(schemaNode));
128 } catch (final WriteException e) {
129 throw new IllegalStateException("Unable to write value for: " + schemaNode.getQName() + " from: "
130 + unwrap.get(schemaNode), e);
135 private void handleEmptyOutput(final Command command, final Output response) {
137 new NormalizedNodeWriter(consoleIO, new OutFormatter()).write(null,
138 Collections.<NormalizedNode<?, ?>>singletonList(response.getOutput()));
139 } catch (final WriteException e) {
140 throw new IllegalStateException("Unable to write value for: " + response.getOutput().getNodeType()
141 + " from: " + command.getCommandId(), e);
145 private Input handleInput(final InputDefinition inputDefinition) {
146 List<NormalizedNode<?, ?>> allArgs = Collections.emptyList();
148 if (!inputDefinition.isEmpty()) {
149 allArgs = argumentHandlerRegistry.getGenericReader(schemaContextRegistry.getLocalSchemaContext()).read(
150 inputDefinition.getInput());
152 } catch (final ReadingException e) {
153 throw new IllegalStateException("Unable to read value for: " + inputDefinition.getInput().getQName(), e);
156 return new Input(allArgs);
159 // TODO move tryGet to GenericWriter, GenericReader has the same code
160 private <T> Optional<Class<? extends T>> tryGetCustomHandler(final DataSchemaNode dataSchemaNode) {
162 for (final UnknownSchemaNode unknownSchemaNode : dataSchemaNode.getUnknownSchemaNodes()) {
164 if (isExtenstionForCustomHandler(unknownSchemaNode)) {
165 final String argumentHandlerClassName = unknownSchemaNode.getNodeParameter();
167 final Class<?> argumentClass = Class.forName(argumentHandlerClassName);
168 // TODO add check before cast
169 return Optional.of((Class<? extends T>) argumentClass);
170 } catch (final ClassNotFoundException e) {
171 throw new IllegalArgumentException("Unknown custom reader class " + argumentHandlerClassName
172 + " for: " + dataSchemaNode.getQName());
177 return Optional.absent();
180 private boolean isExtenstionForCustomHandler(final UnknownSchemaNode unknownSchemaNode) {
181 final QName qName = unknownSchemaNode.getExtensionDefinition().getQName();
182 return qName.equals(CommandConstants.ARG_HANDLER_EXT_QNAME);
185 private static final class RootConsoleContext implements ConsoleContext {
187 private final Completer completer;
189 RootConsoleContext(final CommandDispatcher commandRegistry) {
190 completer = new CommandCompleter(commandRegistry);
194 public Completer getCompleter() {
199 public Optional<String> getPrompt() {
200 return Optional.absent();
203 private class CommandCompleter extends StringsCompleter {
205 private final CommandDispatcher commandRegistry;
207 CommandCompleter(final CommandDispatcher commandRegistry) {
208 this.commandRegistry = commandRegistry;
212 public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
213 getStrings().clear();
214 getStrings().addAll(commandRegistry.getCommandIds());
215 return super.complete(buffer, cursor, candidates);