/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.xd.shell.command; import java.io.File; import java.io.IOException; import java.util.List; import org.fusesource.jansi.Ansi; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.hateoas.PagedResources; import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.annotation.CliAvailabilityIndicator; import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliOption; import org.springframework.stereotype.Component; import org.springframework.xd.rest.client.ModuleOperations; import org.springframework.xd.rest.domain.DetailedModuleDefinitionResource; import org.springframework.xd.rest.domain.DetailedModuleDefinitionResource.Option; import org.springframework.xd.rest.domain.ModuleDefinitionResource; import org.springframework.xd.rest.domain.RESTModuleType; import org.springframework.xd.shell.XDShell; import org.springframework.xd.shell.util.Table; import org.springframework.xd.shell.util.TableHeader; import org.springframework.xd.shell.util.TableRow; import static org.fusesource.jansi.Ansi.ansi; /** * Commands for working with modules. Allows retrieval of information about available modules, as well as creating new * composed modules. * * @author Glenn Renfro * @author Eric Bottard * @author Florent Biville * @author David Turanski */ @Component public class ModuleCommands implements CommandMarker { private final static String COMPOSE_MODULE = "module compose"; private final static String LIST_MODULES = "module list"; private final static String DELETE_MODULE = "module delete"; private final static String MODULE_INFO = "module info"; private static final String UPLOAD_MODULE = "module upload"; @Autowired private XDShell xdShell; @CliAvailabilityIndicator({COMPOSE_MODULE, LIST_MODULES, MODULE_INFO, DELETE_MODULE, UPLOAD_MODULE}) public boolean available() { return xdShell.getSpringXDOperations() != null; } @CliCommand(value = MODULE_INFO, help = "Get information about a module") public String moduleInfo( @CliOption(mandatory = true, key = {"name", ""}, help = "name of the module to query, in the form 'type:name'") QualifiedModuleName module, @CliOption(key = "hidden", help = "whether to show 'hidden' options", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean showHidden ) { DetailedModuleDefinitionResource info = moduleOperations().info(module.name, module.type); List<Option> options = info.getOptions(); StringBuilder result = new StringBuilder(); result.append("Information about ").append(module.type.name()).append(" module '").append(module.name).append( "':\n\n"); if (info.getShortDescription() != null) { result.append(info.getShortDescription()).append("\n\n"); } if (options == null) { result.append("Module options metadata is not available"); } else { Table table = new Table().addHeader(1, new TableHeader("Option Name")).addHeader(2, new TableHeader("Description")).addHeader( 3, new TableHeader("Default")).addHeader(4, new TableHeader("Type")); for (DetailedModuleDefinitionResource.Option o : options) { if (!showHidden && o.isHidden()) { continue; } final TableRow row = new TableRow(); row.addValue(1, o.getName()) .addValue(2, o.getDescription()) .addValue(3, prettyPrintDefaultValue(o)) .addValue(4, o.getType() == null ? "<unknown>" : o.getType()); table.getRows().add(row); } result.append(table.toString()); } return result.toString(); } /** * Escapes some special values so that they don't disturb console rendering and are easier to read. */ private String prettyPrintDefaultValue(Option o) { if (o.getDefaultValue() == null) { return "<none>"; } return o.getDefaultValue() .replace("\n", "\\n") .replace("\t", "\\t") .replace("\f", "\\f"); } @CliCommand(value = COMPOSE_MODULE, help = "Create a virtual module") public String composeModule( @CliOption(mandatory = true, key = {"name", ""}, help = "the name to give to the module") String name, @CliOption(mandatory = true, key = "definition", optionContext = "completion-module disable-string-converter", help = "module definition using xd dsl") String dsl, @CliOption(key = "force", help = "force update if module already exists (only if not in use)", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean force ) { ModuleDefinitionResource composedModule = moduleOperations().composeModule(name, dsl, force); return String.format(("Successfully created module '%s' with type %s"), composedModule.getName(), composedModule.getType()); } @CliCommand(value = UPLOAD_MODULE, help = "Upload a new module") public String uploadModule( @CliOption(mandatory = true, key = {"type"}, help = "the type for the uploaded module") RESTModuleType type, @CliOption(mandatory = true, key = {"name"}, help = "the name for the uploaded module") String name, @CliOption(mandatory = true, key = {"", "file"}, help = "path to the module archive") File file, @CliOption(key = "force", help = "force update if module already exists (only if not in use)", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean force ) throws IOException { Resource resource = new FileSystemResource(file); ModuleDefinitionResource composedModule = moduleOperations().uploadModule(name, type, resource, force); return String.format(("Successfully uploaded module '%s:%s'"), composedModule.getType(), composedModule.getName()); } @CliCommand(value = DELETE_MODULE, help = "Delete a virtual module") public String destroyModule( @CliOption(mandatory = true, key = {"name", ""}, help = "name of the module to delete, in the form 'type:name'") QualifiedModuleName module ) { moduleOperations().deleteModule(module.name, module.type); return String.format(("Successfully destroyed module '%s' with type %s"), module.name, module.type); } @CliCommand(value = LIST_MODULES, help = "List all modules") public Table listModules() { PagedResources<ModuleDefinitionResource> modules = moduleOperations().list(null); return new ModuleList(modules).renderByType(); } private ModuleOperations moduleOperations() { return xdShell.getSpringXDOperations().moduleOperations(); } public static class QualifiedModuleName { public RESTModuleType type; public String name; public QualifiedModuleName(String name, RESTModuleType type) { this.name = name; this.type = type; } } }