001/*
002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 */
018
019package org.nuxeo.ecm.platform.commandline.executor.service;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.nuxeo.common.Environment;
030import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters;
031import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
032import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
033import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
034import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult;
035import org.nuxeo.ecm.platform.commandline.executor.service.cmdtesters.CommandTestResult;
036import org.nuxeo.ecm.platform.commandline.executor.service.cmdtesters.CommandTester;
037import org.nuxeo.ecm.platform.commandline.executor.service.executors.Executor;
038import org.nuxeo.ecm.platform.commandline.executor.service.executors.ShellExecutor;
039import org.nuxeo.runtime.api.Framework;
040import org.nuxeo.runtime.model.ComponentContext;
041import org.nuxeo.runtime.model.ComponentInstance;
042import org.nuxeo.runtime.model.DefaultComponent;
043
044/**
045 * POJO implementation of the {@link CommandLineExecutorService} interface. Also handles the Extension Point logic.
046 *
047 * @author tiry
048 */
049public class CommandLineExecutorComponent extends DefaultComponent implements CommandLineExecutorService {
050
051    public static final String EP_ENV = "environment";
052
053    public static final String EP_CMD = "command";
054
055    public static final String EP_CMDTESTER = "commandTester";
056
057    public static final String DEFAULT_TESTER = "SystemPathTester";
058
059    public static final String DEFAULT_EXECUTOR = "ShellExecutor";
060
061    protected static Map<String, CommandLineDescriptor> commandDescriptors = new HashMap<>();
062
063    protected static EnvironmentDescriptor env = new EnvironmentDescriptor();
064
065    protected static Map<String, EnvironmentDescriptor> envDescriptors = new HashMap<>();
066
067    protected static Map<String, CommandTester> testers = new HashMap<>();
068
069    protected static Map<String, Executor> executors = new HashMap<>();
070
071    private static final Log log = LogFactory.getLog(CommandLineExecutorComponent.class);
072
073    @Override
074    public void activate(ComponentContext context) {
075        commandDescriptors = new HashMap<>();
076        env = new EnvironmentDescriptor();
077        testers = new HashMap<>();
078        executors = new HashMap<>();
079        executors.put(DEFAULT_EXECUTOR, new ShellExecutor());
080    }
081
082    @Override
083    public void deactivate(ComponentContext context) {
084        commandDescriptors = null;
085        env = null;
086        testers = null;
087        executors = null;
088    }
089
090    @Override
091    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
092        if (EP_ENV.equals(extensionPoint)) {
093            EnvironmentDescriptor desc = (EnvironmentDescriptor) contribution;
094            String name = desc.getName();
095            if (name == null) {
096                env.merge(desc);
097            } else {
098                for (String envName : name.split(",")) {
099                    if (envDescriptors.containsKey(envName)) {
100                        envDescriptors.get(envName).merge(desc);
101                    } else {
102                        envDescriptors.put(envName, desc);
103                    }
104                }
105            }
106        } else if (EP_CMD.equals(extensionPoint)) {
107            CommandLineDescriptor desc = (CommandLineDescriptor) contribution;
108            String name = desc.getName();
109
110            log.debug("Registering command: " + name);
111
112            if (!desc.isEnabled()) {
113                commandDescriptors.remove(name);
114                log.info("Command configured to not be enabled: " + name);
115                return;
116            }
117
118            String testerName = desc.getTester();
119            if (testerName == null) {
120                testerName = DEFAULT_TESTER;
121                log.debug("Using default tester for command: " + name);
122            }
123
124            CommandTester tester = testers.get(testerName);
125            boolean cmdAvailable = false;
126            if (tester == null) {
127                log.error("Unable to find tester '" + testerName + "', command will not be available: " + name);
128            } else {
129                log.debug("Using tester '" + testerName + "' for command: " + name);
130                CommandTestResult testResult = tester.test(desc);
131                cmdAvailable = testResult.succeed();
132                if (cmdAvailable) {
133                    log.info("Registered command: " + name);
134                } else {
135                    desc.setInstallErrorMessage(testResult.getErrorMessage());
136                    log.warn("Command not available: " + name + " (" + desc.getInstallErrorMessage() + ". "
137                            + desc.getInstallationDirective() + ')');
138                }
139            }
140            desc.setAvailable(cmdAvailable);
141            commandDescriptors.put(name, desc);
142        } else if (EP_CMDTESTER.equals(extensionPoint)) {
143            CommandTesterDescriptor desc = (CommandTesterDescriptor) contribution;
144            CommandTester tester;
145            try {
146                tester = (CommandTester) desc.getTesterClass().newInstance();
147            } catch (ReflectiveOperationException e) {
148                throw new RuntimeException(e);
149            }
150            testers.put(desc.getName(), tester);
151        }
152    }
153
154    @Override
155    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
156    }
157
158    /*
159     * Service interface
160     */
161    @Override
162    public ExecResult execCommand(String commandName, CmdParameters params) throws CommandNotAvailable {
163        CommandAvailability availability = getCommandAvailability(commandName);
164        if (!availability.isAvailable()) {
165            throw new CommandNotAvailable(availability);
166        }
167
168        CommandLineDescriptor cmdDesc = commandDescriptors.get(commandName);
169        Executor executor = executors.get(cmdDesc.getExecutor());
170        EnvironmentDescriptor environment = new EnvironmentDescriptor().merge(env).merge(
171                envDescriptors.getOrDefault(commandName, envDescriptors.get(cmdDesc.getCommand())));
172        return executor.exec(cmdDesc, params, environment);
173    }
174
175    @Override
176    public CommandAvailability getCommandAvailability(String commandName) {
177        if (!commandDescriptors.containsKey(commandName)) {
178            return new CommandAvailability(commandName + " is not a registered command");
179        }
180
181        CommandLineDescriptor desc = commandDescriptors.get(commandName);
182        if (desc.isAvailable()) {
183            return new CommandAvailability();
184        } else {
185            return new CommandAvailability(desc.getInstallationDirective(), desc.getInstallErrorMessage());
186        }
187    }
188
189    @Override
190    public List<String> getRegistredCommands() {
191        List<String> cmds = new ArrayList<>();
192        cmds.addAll(commandDescriptors.keySet());
193        return cmds;
194    }
195
196    @Override
197    public List<String> getAvailableCommands() {
198        List<String> cmds = new ArrayList<>();
199
200        for (String cmdName : commandDescriptors.keySet()) {
201            CommandLineDescriptor cmd = commandDescriptors.get(cmdName);
202            if (cmd.isAvailable()) {
203                cmds.add(cmdName);
204            }
205        }
206        return cmds;
207    }
208
209    // ******************************************
210    // for testing
211
212    public static CommandLineDescriptor getCommandDescriptor(String commandName) {
213        return commandDescriptors.get(commandName);
214    }
215
216    @Override
217    public CmdParameters getDefaultCmdParameters() {
218        CmdParameters params = new CmdParameters();
219        params.addNamedParameter("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
220        params.addNamedParameter(Environment.NUXEO_TMP_DIR, Framework.getProperty(Environment.NUXEO_TMP_DIR));
221        return params;
222    }
223
224}