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;
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.controller.netconf.cli.commands.Command;
20 import org.opendaylight.controller.netconf.cli.commands.CommandConstants;
21 import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher;
22 import org.opendaylight.controller.netconf.cli.commands.CommandInvocationException;
23 import org.opendaylight.controller.netconf.cli.commands.input.Input;
24 import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition;
25 import org.opendaylight.controller.netconf.cli.commands.output.Output;
26 import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition;
27 import org.opendaylight.controller.netconf.cli.io.ConsoleContext;
28 import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
29 import org.opendaylight.controller.netconf.cli.reader.ReadingException;
30 import org.opendaylight.controller.netconf.cli.writer.OutFormatter;
31 import org.opendaylight.controller.netconf.cli.writer.WriteException;
32 import org.opendaylight.controller.netconf.cli.writer.Writer;
33 import org.opendaylight.controller.netconf.cli.writer.impl.CompositeNodeWriter;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.data.api.Node;
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, final SchemaContextRegistry schemaContextRegistry) {
50 this.consoleIO = consoleIO;
51 this.commandRegistry = commandRegistry;
52 this.argumentHandlerRegistry = argumentHandlerRegistry;
53 this.schemaContextRegistry = schemaContextRegistry;
59 consoleIO.writeLn("Cli is up, available commands:");
60 final RootConsoleContext consoleContext = new RootConsoleContext(commandRegistry);
61 consoleIO.enterContext(consoleContext);
63 consoleIO.writeLn("");
66 final String commandName = consoleIO.read();
67 final Optional<Command> commandOpt = commandRegistry.getCommand(commandName);
69 if (commandOpt.isPresent() == false) {
73 final Command command = commandOpt.get();
75 consoleIO.enterContext(command.getConsoleContext());
76 final Output response = command.invoke(handleInput(command.getInputDefinition()));
77 handleOutput(command, response);
78 } catch (final CommandInvocationException e) {
79 consoleIO.write(e.getMessage());
80 } catch (final UserInterruptException e) {
81 consoleIO.writeLn("Command " + command.getCommandId() + " was terminated.");
83 consoleIO.leaveContext();
87 } catch (final IOException e) {
88 throw new RuntimeException("IO failure", e);
92 private void handleOutput(final Command command, final Output response) {
93 final OutputDefinition outputDefinition = command.getOutputDefinition();
95 final Writer<DataSchemaNode> outHandler = argumentHandlerRegistry.getGenericWriter();
96 if (outputDefinition.isEmpty()) {
97 handleEmptyOutput(command, response);
99 handleRegularOutput(response, outputDefinition, outHandler);
103 private void handleRegularOutput(final Output response, final OutputDefinition outputDefinition,
104 final Writer<DataSchemaNode> outHandler) {
105 final Map<DataSchemaNode, List<Node<?>>> unwrap = response.unwrap(outputDefinition);
107 for (final DataSchemaNode schemaNode : unwrap.keySet()) {
108 Preconditions.checkNotNull(schemaNode);
112 // FIXME move custom writer to GenericWriter/Serializers ...
113 // this checks only first level
114 final Optional<Class<? extends Writer<DataSchemaNode>>> customReaderClassOpt = tryGetCustomHandler(schemaNode);
116 if (customReaderClassOpt.isPresent()) {
117 final Writer<DataSchemaNode> customReaderInstance = argumentHandlerRegistry
118 .getCustomWriter(customReaderClassOpt.get());
119 Preconditions.checkNotNull(customReaderInstance, "Unknown custom writer: %s",
120 customReaderClassOpt.get());
121 customReaderInstance.write(schemaNode, unwrap.get(schemaNode));
123 outHandler.write(schemaNode, unwrap.get(schemaNode));
126 } catch (final WriteException e) {
127 throw new IllegalStateException("Unable to write value for: " + schemaNode.getQName() + " from: "
128 + unwrap.get(schemaNode), e);
133 private void handleEmptyOutput(final Command command, final Output response) {
135 new CompositeNodeWriter(consoleIO, new OutFormatter()).write(null,
136 Collections.<Node<?>> singletonList(response.getOutput()));
137 } catch (final WriteException e) {
138 throw new IllegalStateException("Unable to write value for: " + response.getOutput().getNodeType()
139 + " from: " + command.getCommandId(), e);
143 private Input handleInput(final InputDefinition inputDefinition) {
144 List<Node<?>> allArgs = Collections.emptyList();
146 if (!inputDefinition.isEmpty()) {
147 allArgs = argumentHandlerRegistry.getGenericReader(schemaContextRegistry.getLocalSchemaContext()).read(
148 inputDefinition.getInput());
150 } catch (final ReadingException e) {
151 throw new IllegalStateException("Unable to read value for: " + inputDefinition.getInput().getQName(), e);
154 return new Input(allArgs);
157 // TODO move tryGet to GenericWriter, GenericReader has the same code
158 private <T> Optional<Class<? extends T>> tryGetCustomHandler(final DataSchemaNode dataSchemaNode) {
160 for (final UnknownSchemaNode unknownSchemaNode : dataSchemaNode.getUnknownSchemaNodes()) {
162 if (isExtenstionForCustomHandler(unknownSchemaNode)) {
163 final String argumentHandlerClassName = unknownSchemaNode.getNodeParameter();
165 final Class<?> argumentClass = Class.forName(argumentHandlerClassName);
166 // TODO add check before cast
167 return Optional.<Class<? extends T>> of((Class<? extends T>) argumentClass);
168 } catch (final ClassNotFoundException e) {
169 throw new IllegalArgumentException("Unknown custom reader class " + argumentHandlerClassName
170 + " for: " + dataSchemaNode.getQName());
175 return Optional.absent();
178 private boolean isExtenstionForCustomHandler(final UnknownSchemaNode unknownSchemaNode) {
179 final QName qName = unknownSchemaNode.getExtensionDefinition().getQName();
180 return qName.equals(CommandConstants.ARG_HANDLER_EXT_QNAME);
183 private static final class RootConsoleContext implements ConsoleContext {
185 private final Completer completer;
187 public RootConsoleContext(final CommandDispatcher commandRegistry) {
188 completer = new CommandCompleter(commandRegistry);
192 public Completer getCompleter() {
197 public Optional<String> getPrompt() {
198 return Optional.absent();
201 private class CommandCompleter extends StringsCompleter {
203 private final CommandDispatcher commandRegistry;
205 public CommandCompleter(final CommandDispatcher commandRegistry) {
206 this.commandRegistry = commandRegistry;
210 public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
211 getStrings().clear();
212 getStrings().addAll(commandRegistry.getCommandIds());
213 return super.complete(buffer, cursor, candidates);