/* * JBoss, Home of Professional Open Source. * Copyright 2016, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.cli.handlers; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.jboss.as.cli.CommandArgument; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandFormatException; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.Util; import org.jboss.as.cli.impl.ArgumentWithValue; import org.jboss.as.cli.impl.ArgumentWithoutValue; import org.jboss.as.cli.operation.CommandLineParser; import org.jboss.as.cli.operation.OperationFormatException; import org.jboss.as.cli.operation.OperationRequestAddress; import org.jboss.as.cli.operation.OperationRequestAddress.Node; import org.jboss.as.cli.operation.OperationRequestCompleter; import org.jboss.as.cli.operation.ParsedCommandLine; import org.jboss.as.cli.operation.impl.DefaultCallbackHandler; import org.jboss.as.cli.operation.impl.DefaultOperationRequestAddress; import org.jboss.as.cli.operation.impl.DefaultOperationRequestBuilder; import org.jboss.as.cli.util.SimpleTable; import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; import org.jboss.logging.Logger; /** * * @author Alexey Loubyansky */ public class LsHandler extends BaseOperationCommand { private final ArgumentWithValue nodePath; private final ArgumentWithoutValue l; private final ArgumentWithoutValue resolve; public LsHandler(CommandContext ctx) { this(ctx, "ls"); } public LsHandler(CommandContext ctx, String command) { super(ctx, command, true); l = new ArgumentWithoutValue(this, "-l"); nodePath = new ArgumentWithValue(this, OperationRequestCompleter.ARG_VALUE_COMPLETER, 0, "--node-path"); resolve = new ArgumentWithoutValue(this, "--resolve-expressions") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if(!super.canAppearNext(ctx)) { return false; } ModelNode op = new ModelNode(); op.get("operation").set("read-operation-description"); op.get("name").set("read-attribute"); OperationRequestAddress address = getOperationRequestAddress(ctx); op = getAddressNode(ctx, address, op); ModelNode returnVal = new ModelNode(); try { returnVal = ctx.getModelControllerClient().execute(op); } catch (IOException e) { throw new CommandFormatException("Failed to read resource: " + e.getLocalizedMessage(), e); } if( returnVal.hasDefined("outcome") && returnVal.get("outcome").asString().equals("success")){ ModelNode result = returnVal.get("result"); if(result.hasDefined("request-properties")){ ModelNode properties = result.get("request-properties"); if( properties.hasDefined("resolve-expressions")) { return true; } } } return false; } }; } @Override protected void recognizeArguments(CommandContext ctx) throws CommandFormatException { super.recognizeArguments(ctx); final ParsedCommandLine parsedCmd = ctx.getParsedCommandLine(); if(resolve.isPresent(parsedCmd)) { ModelNode op = new ModelNode(); op.get("operation").set("read-operation-description"); op.get("name").set("read-attribute"); OperationRequestAddress address = getOperationRequestAddress(ctx); op = getAddressNode(ctx, address, op); ModelNode returnVal = new ModelNode(); try { if (ctx.getModelControllerClient() != null) { returnVal = ctx.getModelControllerClient().execute(op); } } catch (IOException e) { throw new CommandFormatException("Failed to read resource: " + e.getLocalizedMessage(), e); } if (returnVal.hasDefined("outcome") && returnVal.get("outcome").asString().equals("success")) { ModelNode result = returnVal.get("result"); if (result.hasDefined("request-properties")) { ModelNode properties = result.get("request-properties"); if (!properties.hasDefined("resolve-expressions") && resolve.isPresent(parsedCmd)) { throw new OperationFormatException("Resolve Expression argument not available at this location."); } } } } } @Override protected void handleResponse(CommandContext ctx, ModelNode outcome, boolean composite) throws CommandLineException { final ParsedCommandLine parsedCmd = ctx.getParsedCommandLine(); if(!composite) { final List<ModelNode> nodeList = outcome.get(Util.RESULT).asList(); if(!nodeList.isEmpty()) { final List<String> list = new ArrayList<String>(nodeList.size()); for(ModelNode node : nodeList) { list.add(node.asString()); } printList(ctx, list, l.isPresent(parsedCmd)); } return; } List<String> additionalProps = Collections.emptyList(); if (l.isPresent(parsedCmd)) { final Set<String> argNames = parsedCmd.getPropertyNames(); final boolean resolvePresent = resolve.isPresent(parsedCmd); final int recognizedArgs = 1 + (resolvePresent ? 1 : 0); if (argNames.size() > recognizedArgs) { additionalProps = new ArrayList<String>(argNames.size() - recognizedArgs); int i = 0; for (String arg : argNames) { if (arg.equals(l.getFullName()) || resolvePresent && arg.equals(resolve.getFullName())) { continue; } final String prop; if (arg.length() > 1 && arg.charAt(0) == '-') { if (arg.charAt(1) == '-') { prop = arg.substring(2); } else { prop = arg.substring(1); } } else { prop = arg; } additionalProps.add(prop); } } } ModelNode resultNode = outcome.get(Util.RESULT); ModelNode attrDescriptions = null; ModelNode childDescriptions = null; if (resultNode.hasDefined(Util.STEP_3)) { final ModelNode stepOutcome = resultNode.get(Util.STEP_3); if (Util.isSuccess(stepOutcome)) { if (stepOutcome.hasDefined(Util.RESULT)) { final ModelNode descrResult = stepOutcome.get(Util.RESULT); if (descrResult.hasDefined(Util.ATTRIBUTES)) { attrDescriptions = descrResult.get(Util.ATTRIBUTES); } if (descrResult.hasDefined(Util.CHILDREN)) { childDescriptions = descrResult.get(Util.CHILDREN); } } else { throw new CommandFormatException("Result is not available for read-resource-description request: " + outcome); } } else { throw new CommandFormatException("Failed to get resource description: " + outcome); } } List<String> names = null; List<String> typeNames = null; if (resultNode.hasDefined(Util.STEP_1)) { ModelNode typesOutcome = resultNode.get(Util.STEP_1); if (Util.isSuccess(typesOutcome)) { if (typesOutcome.hasDefined(Util.RESULT)) { final ModelNode resourceResult = typesOutcome.get(Util.RESULT); final List<ModelNode> types = resourceResult.asList(); if (!types.isEmpty()) { typeNames = new ArrayList<String>(); for (ModelNode type : types) { typeNames.add(type.asString()); } if (childDescriptions == null && attrDescriptions == null) { names = typeNames; } } } else { throw new CommandFormatException("Result is not available for read-children-types request: " + outcome); } } else { throw new CommandFormatException("Failed to fetch type names: " + outcome); } } else { throw new CommandFormatException("The result for children type names is not available: " + outcome); } if (resultNode.hasDefined(Util.STEP_2)) { ModelNode resourceOutcome = resultNode.get(Util.STEP_2); if (Util.isSuccess(resourceOutcome)) { if (resourceOutcome.hasDefined(Util.RESULT)) { final ModelNode resourceResult = resourceOutcome.get(Util.RESULT); final List<Property> props = resourceResult.asPropertyList(); if (!props.isEmpty()) { final SimpleTable attrTable; if (attrDescriptions == null) { attrTable = null; } else { if (!additionalProps.isEmpty()) { String[] headers = new String[3 + additionalProps.size()]; headers[0] = "ATTRIBUTE"; headers[1] = "VALUE"; headers[2] = "TYPE"; int i = 3; for (String additional : additionalProps) { headers[i++] = additional.toUpperCase(Locale.ENGLISH); } attrTable = new SimpleTable(headers); } else { attrTable = new SimpleTable(new String[] { "ATTRIBUTE", "VALUE", "TYPE" }); } } SimpleTable childrenTable = childDescriptions == null ? null : new SimpleTable(new String[] { "CHILD", "MIN-OCCURS", "MAX-OCCURS" }); if (typeNames == null && attrTable == null && childrenTable == null) { typeNames = new ArrayList<String>(); names = typeNames; } for (Property prop : props) { final StringBuilder buf = new StringBuilder(); if (typeNames == null || !typeNames.contains(prop.getName())) { if (attrDescriptions == null) { buf.append(prop.getName()); buf.append('='); buf.append(prop.getValue().asString()); // TODO the value should be formatted nicer but the current // formatter uses new lines for complex value which doesn't work here // final ModelNode value = prop.getValue(); // ModelNodeFormatter.Factory.forType(value.getType()).format(buf, 0, value); typeNames.add(buf.toString()); buf.setLength(0); } else { final String[] line = new String[attrTable.columnsTotal()]; line[0] = prop.getName(); line[1] = prop.getValue().asString(); if (attrDescriptions.hasDefined(prop.getName())) { final ModelNode attrDescr = attrDescriptions.get(prop.getName()); line[2] = getAsString(attrDescr, Util.TYPE); if (!additionalProps.isEmpty()) { int i = 3; for (String additional : additionalProps) { line[i++] = getAsString(attrDescr, additional); } } } else { for (int i = 2; i < line.length; ++i) { line[i] = "n/a"; } } attrTable.addLine(line); } } else if (childDescriptions != null) { if (childDescriptions.hasDefined(prop.getName())) { final ModelNode childDescr = childDescriptions.get(prop.getName()); final Integer maxOccurs = getAsInteger(childDescr, Util.MAX_OCCURS); childrenTable.addLine(new String[] {prop.getName(), getAsString(childDescr, Util.MIN_OCCURS), maxOccurs == null ? "n/a" : (maxOccurs == Integer.MAX_VALUE ? "unbounded" : maxOccurs.toString()) }); } else { childrenTable.addLine(new String[] {prop.getName(), "n/a", "n/a" }); } } } StringBuilder buf = null; if (attrTable != null && !attrTable.isEmpty()) { buf = new StringBuilder(); attrTable.append(buf, true); } if (childrenTable != null && !childrenTable.isEmpty()) { if (buf == null) { buf = new StringBuilder(); } else { buf.append("\n\n"); } childrenTable.append(buf, true); } if (buf != null) { ctx.printLine(buf.toString()); } } } else { throw new CommandFormatException("Result is not available for read-resource request: " + outcome); } } else { throw new CommandFormatException("Failed to fetch attributes: " + outcome); } } else { throw new CommandFormatException("The result for attributes is not available: " + outcome); } if(names != null) { printList(ctx, names, l.isPresent(parsedCmd)); } } @Override protected ModelNode buildRequestWithoutHeaders(CommandContext ctx) throws CommandFormatException { final ParsedCommandLine parsedCmd = ctx.getParsedCommandLine(); final OperationRequestAddress address = getOperationRequestAddress(ctx); if(address.endsOnType()) { final String type = address.getNodeType(); address.toParentNode(); final DefaultOperationRequestBuilder builder = new DefaultOperationRequestBuilder(address); try { builder.setOperationName(Util.READ_CHILDREN_NAMES); builder.addProperty(Util.CHILD_TYPE, type); return builder.buildRequest(); } catch (OperationFormatException e) { throw new IllegalStateException("Failed to build operation", e); } } final ModelNode composite = new ModelNode(); composite.get(Util.OPERATION).set(Util.COMPOSITE); composite.get(Util.ADDRESS).setEmptyList(); final ModelNode steps = composite.get(Util.STEPS); { ModelNode typesRequest = new ModelNode(); typesRequest.get(Util.OPERATION).set(Util.READ_CHILDREN_TYPES); typesRequest = getAddressNode(ctx, address, typesRequest); steps.add(typesRequest); } { ModelNode resourceRequest = new ModelNode(); resourceRequest.get(Util.OPERATION).set(Util.READ_RESOURCE); resourceRequest = getAddressNode(ctx, address, resourceRequest); resourceRequest.get(Util.INCLUDE_RUNTIME).set(Util.TRUE); if( resolve.isPresent(parsedCmd) ){ resourceRequest.get(Util.RESOLVE_EXPRESSIONS).set(Util.TRUE); } steps.add(resourceRequest); } if (l.isPresent(parsedCmd)) { steps.add(Util.buildRequest(ctx, address, Util.READ_RESOURCE_DESCRIPTION)); } return composite; } @Override protected Map<String, CommandArgument> getArgumentsMap(CommandContext ctx) { Map<String, CommandArgument> ret = new HashMap<>(super.getArgumentsMap(ctx)); try { Map<String, CommandArgument> dyns = getDynamicOptions(ctx); ret.putAll(dyns); } catch (CommandFormatException ex) { // XXX OK will not break CLI Logger.getLogger(getClass()).trace("Exception while retreiving ls " + "description properties", ex); } return ret; } @Override public Collection<CommandArgument> getArguments(CommandContext ctx) { List<CommandArgument> args = new ArrayList<>(super.getArguments(ctx)); try { args.addAll(getDynamicOptions(ctx).values()); } catch (CommandFormatException ex) { // XXX OK will not break CLI Logger.getLogger(getClass()).trace("Exception while retreiving ls " + "description properties", ex); } return args; } private Map<String, CommandArgument> getDynamicOptions(CommandContext ctx) throws CommandFormatException { if (ctx.getModelControllerClient() == null) { return Collections.emptyMap(); } final OperationRequestAddress address = getOperationRequestAddress(ctx); if (address.endsOnType()) { return Collections.emptyMap(); } final ModelNode req = new ModelNode(); if (address.isEmpty()) { req.get(Util.ADDRESS).setEmptyList(); } else { final ModelNode addrNode = req.get(Util.ADDRESS); for (OperationRequestAddress.Node node : address) { addrNode.add(node.getType(), node.getName()); } } req.get(Util.OPERATION).set(Util.READ_RESOURCE_DESCRIPTION); Map<String, CommandArgument> options = Collections.emptyMap(); try { final ModelNode response = ctx.getModelControllerClient().execute(req); if (Util.isSuccess(response)) { if (response.hasDefined(Util.RESULT)) { final ModelNode result = response.get(Util.RESULT); if (result.hasDefined(Util.ATTRIBUTES)) { options = new TreeMap<>(); ModelNode attributes = result.get(Util.ATTRIBUTES); for (String key : attributes.keys()) { ModelNode attribute = attributes.get(key); for (String k : attribute.keys()) { ArgumentWithoutValue wv = new ArgumentWithoutValue(new CommandHandlerWithArguments() { @Override public boolean isAvailable(CommandContext ctx) { return LsHandler.this.isAvailable(ctx); } @Override public boolean isBatchMode(CommandContext ctx) { return LsHandler.this.isBatchMode(ctx); } @Override public void handle(CommandContext ctx) throws CommandLineException { LsHandler.this.handle(ctx); } @Override public void addArgument(CommandArgument arg) { // Noop. } }, "--" + k); wv.addRequiredPreceding(l); options.put("--" + k, wv); } } } } } } catch (Exception e) { throw new RuntimeException(e); } return options; } protected String getAsString(final ModelNode attrDescr, String name) { if(attrDescr == null) { return "n/a"; } return attrDescr.has(name) ? attrDescr.get(name).asString() : "n/a"; } protected Integer getAsInteger(final ModelNode attrDescr, String name) { if(attrDescr == null) { return null; } return attrDescr.has(name) ? attrDescr.get(name).asInt() : null; } private OperationRequestAddress getOperationRequestAddress(CommandContext ctx) throws CommandFormatException{ final ParsedCommandLine parsedCmd = ctx.getParsedCommandLine(); String nodePathString = nodePath.getValue(parsedCmd); final OperationRequestAddress address; if (nodePathString != null) { address = new DefaultOperationRequestAddress(ctx.getCurrentNodePath()); // this is for correct parsing of escaped characters nodePathString = ctx.getArgumentsString(); // this method could return null // it would be better to call parsedCmd.getOriginalLine() instead // but this method is useful to handle command lines with line breaks if(nodePathString == null) { nodePathString = parsedCmd.getOriginalLine(); int cmdNameLength = parsedCmd.getOperationName().length(); if (nodePathString.length() == cmdNameLength) { return address; } else { nodePathString = nodePathString.substring(cmdNameLength + 1); } } nodePathString = getNodePath(nodePathString); final CommandLineParser.CallbackHandler handler = new DefaultCallbackHandler(address); ctx.getCommandLineParser().parse(nodePathString, handler); } else { address = new DefaultOperationRequestAddress(ctx.getCurrentNodePath()); } return address; } protected String getNodePath(String nodePathString) { int i = 0; while(i < nodePathString.length()) { i = nodePathString.indexOf('-', i); if(i < 0) { break; } final StringBuilder buf; if(i > 0) { if(!Character.isWhitespace(nodePathString.charAt(i - 1))) { ++i; continue; } buf = new StringBuilder(nodePathString.length()); buf.append(nodePathString.substring(0, i - 1)); } else { buf = new StringBuilder(nodePathString.length()); } int end = i + 1; while(end < nodePathString.length()) { if(Character.isWhitespace(nodePathString.charAt(end))) { break; } ++end; } if(end < nodePathString.length()) { buf.append(nodePathString.substring(end)); } nodePathString = buf.toString(); } return nodePathString.trim(); } private ModelNode getAddressNode(CommandContext ctx, OperationRequestAddress address, ModelNode op) throws CommandFormatException{ ModelNode addressNode = op.get(Util.ADDRESS); if (address.isEmpty()) { addressNode.setEmptyList(); } else { Iterator<Node> iterator = address.iterator(); while (iterator.hasNext()) { OperationRequestAddress.Node node = iterator.next(); if (node.getName() != null) { if (node.getName().equals("*")) { throw new CommandFormatException("* is not supported in node path argument"); } addressNode.add(node.getType(), node.getName()); } else if (iterator.hasNext()) { throw new OperationFormatException( "Expected a node name for type '" + node.getType() + "' in path '" + ctx.getNodePathFormatter().format( address) + "'"); } } } return op; } }