Merge "Bug 1239 - Clean up and refactor netconf-ssh client"
[controller.git] / opendaylight / netconf / netconf-cli / src / main / java / org / opendaylight / controller / netconf / cli / Cli.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.netconf.cli;
9
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;
15 import java.util.Map;
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;
38
39 /**
40  * The top level cli state that dispatches command executions
41  */
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;
47
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;
54     }
55
56     @Override
57     public void run() {
58         try {
59             consoleIO.writeLn("Cli is up, available commands:");
60             final RootConsoleContext consoleContext = new RootConsoleContext(commandRegistry);
61             consoleIO.enterContext(consoleContext);
62             consoleIO.complete();
63             consoleIO.writeLn("");
64
65             while (true) {
66                 final String commandName = consoleIO.read();
67                 final Optional<Command> commandOpt = commandRegistry.getCommand(commandName);
68
69                 if (commandOpt.isPresent() == false) {
70                     continue;
71                 }
72
73                 final Command command = commandOpt.get();
74                 try {
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.");
82                 } finally {
83                     consoleIO.leaveContext();
84                 }
85
86             }
87         } catch (final IOException e) {
88             throw new RuntimeException("IO failure", e);
89         }
90     }
91
92     private void handleOutput(final Command command, final Output response) {
93         final OutputDefinition outputDefinition = command.getOutputDefinition();
94
95         final Writer<DataSchemaNode> outHandler = argumentHandlerRegistry.getGenericWriter();
96         if (outputDefinition.isEmpty()) {
97             handleEmptyOutput(command, response);
98         } else {
99             handleRegularOutput(response, outputDefinition, outHandler);
100         }
101     }
102
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);
106
107         for (final DataSchemaNode schemaNode : unwrap.keySet()) {
108             Preconditions.checkNotNull(schemaNode);
109
110             try {
111
112                 // FIXME move custom writer to GenericWriter/Serializers ...
113                 // this checks only first level
114                 final Optional<Class<? extends Writer<DataSchemaNode>>> customReaderClassOpt = tryGetCustomHandler(schemaNode);
115
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));
122                 } else {
123                     outHandler.write(schemaNode, unwrap.get(schemaNode));
124                 }
125
126             } catch (final WriteException e) {
127                 throw new IllegalStateException("Unable to write value for: " + schemaNode.getQName() + " from: "
128                         + unwrap.get(schemaNode), e);
129             }
130         }
131     }
132
133     private void handleEmptyOutput(final Command command, final Output response) {
134         try {
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);
140         }
141     }
142
143     private Input handleInput(final InputDefinition inputDefinition) {
144         List<Node<?>> allArgs = Collections.emptyList();
145         try {
146             if (!inputDefinition.isEmpty()) {
147                 allArgs = argumentHandlerRegistry.getGenericReader(schemaContextRegistry.getLocalSchemaContext()).read(
148                         inputDefinition.getInput());
149             }
150         } catch (final ReadingException e) {
151             throw new IllegalStateException("Unable to read value for: " + inputDefinition.getInput().getQName(), e);
152         }
153
154         return new Input(allArgs);
155     }
156
157     // TODO move tryGet to GenericWriter, GenericReader has the same code
158     private <T> Optional<Class<? extends T>> tryGetCustomHandler(final DataSchemaNode dataSchemaNode) {
159
160         for (final UnknownSchemaNode unknownSchemaNode : dataSchemaNode.getUnknownSchemaNodes()) {
161
162             if (isExtenstionForCustomHandler(unknownSchemaNode)) {
163                 final String argumentHandlerClassName = unknownSchemaNode.getNodeParameter();
164                 try {
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());
171                 }
172             }
173         }
174
175         return Optional.absent();
176     }
177
178     private boolean isExtenstionForCustomHandler(final UnknownSchemaNode unknownSchemaNode) {
179         final QName qName = unknownSchemaNode.getExtensionDefinition().getQName();
180         return qName.equals(CommandConstants.ARG_HANDLER_EXT_QNAME);
181     }
182
183     private static final class RootConsoleContext implements ConsoleContext {
184
185         private final Completer completer;
186
187         public RootConsoleContext(final CommandDispatcher commandRegistry) {
188             completer = new CommandCompleter(commandRegistry);
189         }
190
191         @Override
192         public Completer getCompleter() {
193             return completer;
194         }
195
196         @Override
197         public Optional<String> getPrompt() {
198             return Optional.absent();
199         }
200
201         private class CommandCompleter extends StringsCompleter {
202
203             private final CommandDispatcher commandRegistry;
204
205             public CommandCompleter(final CommandDispatcher commandRegistry) {
206                 this.commandRegistry = commandRegistry;
207             }
208
209             @Override
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);
214             }
215         }
216     }
217
218 }