/* * JBoss, Home of Professional Open Source. * Copyright 2015, 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.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ServiceLoader; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandHandlerProvider; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.CommandRegistry; import org.jboss.as.cli.ControllerAddress; import org.jboss.as.cli.Util; import org.jboss.as.cli.handlers.CommandHandlerWithHelp; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.dmr.ModelNode; import org.jboss.dmr.Property; import org.jboss.modules.ModuleClassLoader; import org.jboss.modules.ModuleIdentifier; import org.jboss.modules.ModuleLoadException; import org.jboss.modules.ModuleLoader; /** * * @author Alexey Loubyansky */ class ExtensionsLoader { private final ModuleLoader moduleLoader; private final CommandContext ctx; private final CommandRegistry registry; /** current address from which the commands are loaded, this could be null */ private ControllerAddress currentAddress; /** commands loaded from the current address*/ private List<String> loadedCommands = Collections.emptyList(); /** error messages collected during loading the commands */ private List<String> errors = Collections.emptyList(); ExtensionsLoader(CommandRegistry registry, CommandContext ctx) throws CommandLineException { assert registry != null : "command registry is null"; assert ctx != null : "command context is null"; if(getClass().getClassLoader() instanceof ModuleClassLoader) { moduleLoader = ModuleLoader.forClassLoader(getClass().getClassLoader()); } else { moduleLoader = null; } this.ctx = ctx; this.registry = registry; registry.registerHandler(new ExtensionCommandsHandler(), false, ExtensionCommandsHandler.NAME); } void resetHandlers() { for(String cmd : loadedCommands) { registry.remove(cmd); } currentAddress = null; errors = Collections.emptyList(); loadedCommands = Collections.emptyList(); } /** * Using the client, iterates through the available domain management model extensions * and tries to load CLI command handlers from their modules. * * @param registry * @param address * @param client */ void loadHandlers(ControllerAddress address) throws CommandLineException { ModelControllerClient client = ctx.getModelControllerClient(); assert client != null : "client is null"; if(moduleLoader == null) { ctx.printLine("Warning! The CLI is running in a non-modular environment and cannot load commands from management extensions."); return; } if(address != null && currentAddress != null && address.equals(currentAddress)) { return; } // remove previously loaded commands resetHandlers(); currentAddress = address; final ModelNode req = new ModelNode(); req.get(Util.ADDRESS).setEmptyList(); req.get(Util.OPERATION).set(Util.READ_CHILDREN_RESOURCES); req.get(Util.CHILD_TYPE).set(Util.EXTENSION); final ModelNode response; try { response = client.execute(req); } catch (IOException e) { throw new CommandLineException("Extensions loader failed to read extensions", e); } if(!Util.isSuccess(response)) { throw new CommandLineException("Extensions loader failed to read extensions: " + Util.getFailureDescription(response)); } final ModelNode result = response.get(Util.RESULT); if(!result.isDefined()) { throw new CommandLineException("Extensions loader failed to read extensions: " + result.asString()); } for(Property ext : result.asPropertyList()) { ModelNode module = ext.getValue().get(Util.MODULE); if(!module.isDefined()) { addError("Extension " + ext.getName() + " is missing module attribute"); } else { final ModuleIdentifier moduleId = ModuleIdentifier.fromString(module.asString()); ModuleClassLoader cl; try { cl = moduleLoader.loadModule(moduleId).getClassLoader(); ServiceLoader<CommandHandlerProvider> loader = ServiceLoader.load(CommandHandlerProvider.class, cl); for (CommandHandlerProvider provider : loader) { try { registry.registerHandler(provider.createCommandHandler(ctx), provider.isTabComplete(), provider.getNames()); addCommands(Arrays.asList(provider.getNames())); } catch(CommandRegistry.RegisterHandlerException e) { addError(e.getLocalizedMessage()); final List<String> addedCommands = new ArrayList<String>(Arrays.asList(provider.getNames())); addedCommands.removeAll(e.getNotAddedNames()); addCommands(addedCommands); } } } catch (ModuleLoadException e) { addError("Module " + module.asString() + " from extension " + ext.getName() + " available on the server couldn't be loaded locally: " + e.getLocalizedMessage()); } } } if(!errors.isEmpty()) { ctx.printLine("Warning! There were errors trying to load extensions. For more details, please, execute 'extension-commands --errors'"); } } private void addCommands(List<String> names) { if(loadedCommands.isEmpty()) { loadedCommands = new ArrayList<String>(); } loadedCommands.addAll(names); } private void addError(String msg) { switch(errors.size()) { case 0: errors = Collections.singletonList(msg); break; case 1: errors = new ArrayList<String>(errors); default: errors.add(msg); } } class ExtensionCommandsHandler extends CommandHandlerWithHelp { private static final String NAME = "extension-commands"; private final ArgumentWithoutValue errorsArg = new ArgumentWithoutValue(this, "--errors"); ExtensionCommandsHandler() { super(NAME); } @Override protected void doHandle(CommandContext ctx) throws CommandLineException { if (errorsArg.isPresent(ctx.getParsedCommandLine()) && !errors.isEmpty()) { final StringBuilder buf = new StringBuilder(); buf.append("The following problems were encountered while looking for additional commands in extensions:\n"); for (int i = 0; i < errors.size(); ++i) { final String error = errors.get(i); buf.append(i + 1).append(") ").append(error).append(Util.LINE_SEPARATOR); } ctx.printLine(buf.toString()); } } } }