/* * 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.operation.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jboss.as.cli.CommandArgument; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandFormatException; import org.jboss.as.cli.CommandLineCompleter; import org.jboss.as.cli.Util; import org.jboss.as.cli.handlers.FilenameTabCompleter; import org.jboss.as.cli.handlers.SimpleTabCompleter; import org.jboss.as.cli.impl.AttributeNamePathCompleter; import org.jboss.as.cli.impl.DeploymentItemCompleter; import org.jboss.as.cli.impl.ValueTypeCompleter; import org.jboss.as.cli.operation.OperationCandidatesProvider; import org.jboss.as.cli.operation.OperationFormatException; import org.jboss.as.cli.operation.OperationRequestAddress; import org.jboss.as.cli.operation.OperationRequestHeader; import org.jboss.as.cli.operation.ParsedCommandLine; import org.jboss.as.cli.parsing.ParserUtil; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; /** * * @author Alexey Loubyansky */ public class DefaultOperationCandidatesProvider implements OperationCandidatesProvider { private static final SimpleTabCompleter NO_CANDIDATES_COMPLETER = new SimpleTabCompleter(new String[]{}); private static final CommandLineCompleter BOOLEAN_HEADER_COMPLETER = new CommandLineCompleter(){ private final DefaultCallbackHandler parsedOp = new DefaultCallbackHandler(); @Override public int complete(CommandContext ctx, String buffer, int cursor, List<String> candidates) { try { ParserUtil.parseHeaders(buffer, parsedOp); } catch (CommandFormatException e) { //e.printStackTrace(); return -1; } if(parsedOp.endsOnSeparator()) { candidates.add(Util.FALSE); candidates.add(Util.TRUE); return buffer.length(); } if(parsedOp.getLastHeader() == null) { candidates.add("="); return buffer.length(); } int result = SimpleTabCompleter.BOOLEAN.complete(ctx, buffer.substring(parsedOp.getLastChunkIndex()), cursor, candidates); // Special case when the value is already complete, add a separator. if (candidates.size() == 1) { if (candidates.get(0).equals(buffer.substring(parsedOp.getLastChunkIndex()))) { candidates.clear(); candidates.add(buffer.substring(parsedOp.getLastChunkIndex()) + ";"); } }// No value... if(result < 0) { return result; } return parsedOp.getLastChunkIndex() + result; }}; private static final CommandLineCompleter INT_HEADER_COMPLETER = new CommandLineCompleter(){ private final DefaultCallbackHandler parsedOp = new DefaultCallbackHandler(); @Override public int complete(CommandContext ctx, String buffer, int cursor, List<String> candidates) { try { ParserUtil.parseHeaders(buffer, parsedOp); } catch (CommandFormatException e) { //e.printStackTrace(); return -1; } if(parsedOp.endsOnSeparator()) { return buffer.length(); } if(parsedOp.getLastHeader() == null) { candidates.add("="); return buffer.length(); } return buffer.length(); }}; private static final Map<String, OperationRequestHeader> HEADERS; static { HEADERS = new HashMap<String, OperationRequestHeader>(); HEADERS.put(RolloutPlanRequestHeader.INSTANCE.getName(), RolloutPlanRequestHeader.INSTANCE); addBooleanHeader(Util.ALLOW_RESOURCE_SERVICE_RESTART); addBooleanHeader(Util.ROLLBACK_ON_RUNTIME_FAILURE); addIntHeader(Util.BLOCKING_TIMEOUT); } private static void addBooleanHeader(final String name) { OperationRequestHeader header = new OperationRequestHeader(){ @Override public String getName() { return name; } @Override public CommandLineCompleter getCompleter() { return BOOLEAN_HEADER_COMPLETER; }}; HEADERS.put(header.getName(), header); } private static void addIntHeader(final String name) { OperationRequestHeader header = new OperationRequestHeader(){ @Override public String getName() { return name; } @Override public CommandLineCompleter getCompleter() { return INT_HEADER_COMPLETER; }}; HEADERS.put(header.getName(), header); } /* (non-Javadoc) * @see org.jboss.as.cli.CandidatesProvider#getNodeNames(org.jboss.as.cli.Prefix) */ @Override public List<String> getNodeNames(CommandContext ctx, OperationRequestAddress prefix) { ModelControllerClient client = ctx.getModelControllerClient(); if(client == null) { return Collections.emptyList(); } if(prefix.isEmpty()) { throw new IllegalArgumentException("The prefix must end on a type but it's empty."); } if(!prefix.endsOnType()) { throw new IllegalArgumentException("The prefix doesn't end on a type."); } final ModelNode request; DefaultOperationRequestBuilder builder = new DefaultOperationRequestBuilder(prefix); try { builder.setOperationName(Util.READ_CHILDREN_NAMES); builder.addProperty(Util.CHILD_TYPE, prefix.getNodeType()); builder.addProperty(Util.INCLUDE_SINGLETONS, "true"); request = builder.buildRequest(); } catch (OperationFormatException e1) { throw new IllegalStateException("Failed to build operation", e1); } List<String> result; try { ModelNode outcome = client.execute(request); if (!Util.isSuccess(outcome)) { // TODO logging... exception? result = Collections.emptyList(); } else { result = Util.getList(outcome); } } catch (Exception e) { result = Collections.emptyList(); } return result; } /* (non-Javadoc) * @see org.jboss.as.cli.CandidatesProvider#getNodeTypes(org.jboss.as.cli.Prefix) */ @Override public List<String> getNodeTypes(CommandContext ctx, OperationRequestAddress prefix) { return Util.getNodeTypes(ctx.getModelControllerClient(), prefix); } @Override public List<String> getOperationNames(CommandContext ctx, OperationRequestAddress prefix) { return Util.getOperationNames(ctx, prefix); } @Override public List<CommandArgument> getProperties(CommandContext ctx, String operationName, OperationRequestAddress address) { final ModelControllerClient client = ctx.getModelControllerClient(); if(client == null) { return Collections.emptyList(); } /* if(address.endsOnType()) { throw new IllegalArgumentException("The prefix isn't expected to end on a type."); } */ final ModelNode request; final DefaultOperationRequestBuilder builder = new DefaultOperationRequestBuilder(address); try { builder.setOperationName(Util.READ_OPERATION_DESCRIPTION); builder.addProperty(Util.NAME, operationName); request = builder.buildRequest(); } catch (OperationFormatException e1) { throw new IllegalStateException("Failed to build operation", e1); } final Map<String,CommandLineCompleterFactory> globalOpProps = globalOpPropCompleters.get(operationName); List<CommandArgument> result; try { ModelNode outcome = client.execute(request); if (!Util.isSuccess(outcome)) { result = Collections.emptyList(); } else { final ModelNode resultNode = outcome.get(Util.RESULT); if(!resultNode.isDefined()) { return Collections.emptyList(); } final ModelNode reqProps = resultNode.get(Util.REQUEST_PROPERTIES); if(!reqProps.isDefined()) { return Collections.emptyList(); } final List<Property> propList = reqProps.asPropertyList(); result = new ArrayList<CommandArgument>(propList.size()); for(final Property prop : propList) { final CommandLineCompleter completer = getCompleter(globalOpProps, prop, ctx, operationName, address); result.add(new CommandArgument(){ final String argName = prop.getName(); @Override public String getFullName() { return argName; } @Override public String getShortName() { return null; } @Override public int getIndex() { return -1; } @Override public boolean isPresent(ParsedCommandLine args) throws CommandFormatException { return args.hasProperty(argName); } @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { return !isPresent(ctx.getParsedCommandLine()); } @Override public String getValue(ParsedCommandLine args) throws CommandFormatException { return args.getPropertyValue(argName); } @Override public String getValue(ParsedCommandLine args, boolean required) throws CommandFormatException { if(!isPresent(args)) { throw new CommandFormatException("Property '" + argName + "' is missing required value."); } return args.getPropertyValue(argName); } @Override public boolean isValueComplete(ParsedCommandLine args) throws CommandFormatException { if(!isPresent(args)) { return false; } if(argName.equals(args.getLastParsedPropertyName())) { return false; } return true; } @Override public boolean isValueRequired() { boolean required = true; ModelNode mn = prop.getValue().get("type"); if (mn != null) { // No value required for boolean required = mn.asType() != ModelType.BOOLEAN; } return required; } @Override public CommandLineCompleter getValueCompleter() { return completer; }}); } } } catch (Exception e) { result = Collections.emptyList(); } return result; } private CommandLineCompleter getCompleter(final Map<String, CommandLineCompleterFactory> globalOpProps, final Property prop, CommandContext ctx, String operationName, OperationRequestAddress address) throws IllegalArgumentException { CommandLineCompleter propCompleter = null; final CommandLineCompleterFactory factory = globalOpProps == null ? null : globalOpProps.get(prop.getName()); if (factory != null) { propCompleter = factory.createCompleter(ctx, address); } if (propCompleter == null) { propCompleter = getCompleter(prop, ctx, address); } return propCompleter; } static CommandLineCompleter getCompleter(final Property prop, CommandContext ctx, OperationRequestAddress address) { ModelNode attrDescr = prop.getValue(); final ModelNode typeNode = attrDescr.get(Util.TYPE); if (typeNode.isDefined() && ModelType.BOOLEAN.equals(typeNode.asType())) { return SimpleTabCompleter.BOOLEAN; } if (attrDescr.has(Util.VALUE_TYPE)) { final ModelNode valueTypeNode = attrDescr.get(Util.VALUE_TYPE); if (typeNode.isDefined() && ModelType.LIST.equals(typeNode.asType())) { return new ValueTypeCompleter(attrDescr, address); } try { // the logic is: if value-type is set to a specific type // (i.e. doesn't describe a custom structure) // then if allowed is specified, use it. // it might be broken but so far this is not looking clear to me valueTypeNode.asType(); if (attrDescr.has(Util.ALLOWED)) { return getAllowedCompleter(prop); } // Possibly a Map. if (typeNode.isDefined() && ModelType.OBJECT.equals(typeNode.asType())) { return new ValueTypeCompleter(attrDescr, address); } } catch (IllegalArgumentException e) { // TODO this means value-type describes a custom structure return new ValueTypeCompleter(attrDescr, address); } } if (attrDescr.has(Util.FILESYSTEM_PATH) && attrDescr.get(Util.FILESYSTEM_PATH).asBoolean()) { return FilenameTabCompleter.newCompleter(ctx); } if (attrDescr.has(Util.RELATIVE_TO) && attrDescr.get(Util.RELATIVE_TO).asBoolean()) { return new DeploymentItemCompleter(address); } if (attrDescr.has(Util.ALLOWED)) { return getAllowedCompleter(prop); } if (attrDescr.has(Util.CAPABILITY_REFERENCE)) { return new CapabilityReferenceCompleter(address, attrDescr.get(Util.CAPABILITY_REFERENCE).asString()); } return null; } private static CommandLineCompleter getAllowedCompleter(final Property prop) { final ModelNode allowedNode = prop.getValue().get(Util.ALLOWED); if(allowedNode.isDefined()) { final List<ModelNode> nodeList = allowedNode.asList(); final String[] values = new String[nodeList.size()]; for(int i = 0; i < values.length; ++i) { values[i] = nodeList.get(i).asString(); } return new SimpleTabCompleter(values); } return null; } @Override public Map<String, OperationRequestHeader> getHeaders(CommandContext ctx) { return HEADERS; } private static final Map<String, Map<String, CommandLineCompleterFactory>> globalOpPropCompleters = new HashMap<String, Map<String, CommandLineCompleterFactory>>(); static void addGlobalOpPropCompleter(String op, String prop, CommandLineCompleterFactory factory) { Map<String, CommandLineCompleterFactory> propMap = globalOpPropCompleters.get(op); if(propMap == null) { propMap = new HashMap<String,CommandLineCompleterFactory>(); globalOpPropCompleters.put(op, propMap); } propMap.put(prop, factory); } static CommandLineCompleterFactory getGlobalOpPropCompleter(String op, String prop) { final Map<String, CommandLineCompleterFactory> propMap = globalOpPropCompleters.get(op); return propMap == null ? null : propMap.get(prop); } static { final CommandLineCompleterFactory attrNameCompleter = new CommandLineCompleterFactory(){ @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address); }}; addGlobalOpPropCompleter(Util.UNDEFINE_ATTRIBUTE, Util.NAME, attrNameCompleter); addGlobalOpPropCompleter(Util.READ_ATTRIBUTE, Util.NAME, attrNameCompleter); addGlobalOpPropCompleter(Util.WRITE_ATTRIBUTE, Util.NAME, new CommandLineCompleterFactory(){ @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address, true); }}); addGlobalOpPropCompleter(Util.WRITE_ATTRIBUTE, Util.VALUE, new CommandLineCompleterFactory(){ @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { final String propName = ctx.getParsedCommandLine().getPropertyValue(Util.NAME); if(propName == null) { return NO_CANDIDATES_COMPLETER; } final ModelNode req = new ModelNode(); 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); final ModelNode response; try { response = ctx.getModelControllerClient().execute(req); } catch (Exception e) { return NO_CANDIDATES_COMPLETER; } final ModelNode result = response.get(Util.RESULT); if (!result.isDefined()) { return NO_CANDIDATES_COMPLETER; } final ModelNode attrs = result.get(Util.ATTRIBUTES); if(!attrs.isDefined()) { return NO_CANDIDATES_COMPLETER; } final ModelNode attrDescr = attrs.get(propName); if(!attrDescr.isDefined()) { return NO_CANDIDATES_COMPLETER; } Property prop = new Property(propName, attrDescr); return getCompleter(prop, ctx, address); }}); addGlobalOpPropCompleter(Util.READ_OPERATION_DESCRIPTION, Util.NAME, new CommandLineCompleterFactory(){ @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new OperationNameCompleter(address); }}); final CommandLineCompleterFactory childTypeCompleter = new CommandLineCompleterFactory(){ @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new ChildTypeCompleter(address); }}; addGlobalOpPropCompleter(Util.READ_CHILDREN_NAMES, Util.CHILD_TYPE, childTypeCompleter); addGlobalOpPropCompleter(Util.READ_CHILDREN_RESOURCES, Util.CHILD_TYPE, childTypeCompleter); final CommandLineCompleterFactory mapAttrNameCompleter = new CommandLineCompleterFactory() { @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address, false, AttributeNamePathCompleter.MAP_FILTER); } }; final CommandLineCompleterFactory mapOnlyWritableAttrNameCompleter = new CommandLineCompleterFactory() { @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address, true, AttributeNamePathCompleter.MAP_FILTER); } }; final CommandLineCompleterFactory listAttrNameCompleter = new CommandLineCompleterFactory() { @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address, AttributeNamePathCompleter.LIST_FILTER); } }; final CommandLineCompleterFactory listOnlyWritableAttrNameCompleter = new CommandLineCompleterFactory() { @Override public CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address) { return new AttributeNamePathCompleter(address, true, AttributeNamePathCompleter.LIST_FILTER); } }; addGlobalOpPropCompleter("map-put", Util.NAME, mapOnlyWritableAttrNameCompleter); addGlobalOpPropCompleter("map-remove", Util.NAME, mapOnlyWritableAttrNameCompleter); addGlobalOpPropCompleter("map-get", Util.NAME, mapAttrNameCompleter); addGlobalOpPropCompleter("map-clear", Util.NAME, mapOnlyWritableAttrNameCompleter); addGlobalOpPropCompleter("list-add", Util.NAME, listOnlyWritableAttrNameCompleter); addGlobalOpPropCompleter("list-remove", Util.NAME, listOnlyWritableAttrNameCompleter); addGlobalOpPropCompleter("list-get", Util.NAME, listAttrNameCompleter); addGlobalOpPropCompleter("list-clear", Util.NAME, listOnlyWritableAttrNameCompleter); } interface CommandLineCompleterFactory { CommandLineCompleter createCompleter(CommandContext ctx, OperationRequestAddress address); } }