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;
025import java.util.regex.Pattern;
026
027import org.apache.commons.lang3.SystemUtils;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.nuxeo.common.Environment;
032import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters;
033import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
034import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
035import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
036import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult;
037import org.nuxeo.ecm.platform.commandline.executor.service.cmdtesters.CommandTestResult;
038import org.nuxeo.ecm.platform.commandline.executor.service.cmdtesters.CommandTester;
039import org.nuxeo.ecm.platform.commandline.executor.service.executors.Executor;
040import org.nuxeo.ecm.platform.commandline.executor.service.executors.ShellExecutor;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.model.ComponentContext;
043import org.nuxeo.runtime.model.ComponentInstance;
044import org.nuxeo.runtime.model.DefaultComponent;
045
046/**
047 * POJO implementation of the {@link CommandLineExecutorService} interface. Also handles the Extension Point logic.
048 *
049 * @author tiry
050 */
051public class CommandLineExecutorComponent extends DefaultComponent implements CommandLineExecutorService {
052
053    public static final String EP_ENV = "environment";
054
055    public static final String EP_CMD = "command";
056
057    public static final String EP_CMDTESTER = "commandTester";
058
059    public static final String DEFAULT_TESTER = "SystemPathTester";
060
061    public static final String DEFAULT_EXECUTOR = "ShellExecutor";
062
063    protected static Map<String, CommandLineDescriptor> commandDescriptors = new HashMap<>();
064
065    protected static EnvironmentDescriptor env = new EnvironmentDescriptor();
066
067    protected static Map<String, EnvironmentDescriptor> envDescriptors = new HashMap<>();
068
069    protected static Map<String, CommandTester> testers = new HashMap<>();
070
071    protected static Map<String, Executor> executors = new HashMap<>();
072
073    private static final Log log = LogFactory.getLog(CommandLineExecutorComponent.class);
074
075    @Override
076    public void activate(ComponentContext context) {
077        commandDescriptors = new HashMap<>();
078        env = new EnvironmentDescriptor();
079        testers = new HashMap<>();
080        executors = new HashMap<>();
081        executors.put(DEFAULT_EXECUTOR, new ShellExecutor());
082    }
083
084    @Override
085    public void deactivate(ComponentContext context) {
086        commandDescriptors = null;
087        env = null;
088        testers = null;
089        executors = null;
090    }
091
092    @Override
093    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
094        if (EP_ENV.equals(extensionPoint)) {
095            EnvironmentDescriptor desc = (EnvironmentDescriptor) contribution;
096            String name = desc.getName();
097            if (name == null) {
098                env.merge(desc);
099            } else {
100                for (String envName : name.split(",")) {
101                    if (envDescriptors.containsKey(envName)) {
102                        envDescriptors.get(envName).merge(desc);
103                    } else {
104                        envDescriptors.put(envName, desc);
105                    }
106                }
107            }
108        } else if (EP_CMD.equals(extensionPoint)) {
109            CommandLineDescriptor desc = (CommandLineDescriptor) contribution;
110            String name = desc.getName();
111
112            log.debug("Registering command: " + name);
113
114            if (!desc.isEnabled()) {
115                commandDescriptors.remove(name);
116                log.info("Command configured to not be enabled: " + name);
117                return;
118            }
119
120            String testerName = desc.getTester();
121            if (testerName == null) {
122                testerName = DEFAULT_TESTER;
123                log.debug("Using default tester for command: " + name);
124            }
125
126            CommandTester tester = testers.get(testerName);
127            boolean cmdAvailable = false;
128            if (tester == null) {
129                log.error("Unable to find tester '" + testerName + "', command will not be available: " + name);
130            } else {
131                log.debug("Using tester '" + testerName + "' for command: " + name);
132                CommandTestResult testResult = tester.test(desc);
133                cmdAvailable = testResult.succeed();
134                if (cmdAvailable) {
135                    log.info("Registered command: " + name);
136                } else {
137                    desc.setInstallErrorMessage(testResult.getErrorMessage());
138                    log.warn("Command not available: " + name + " (" + desc.getInstallErrorMessage() + ". "
139                            + desc.getInstallationDirective() + ')');
140                }
141            }
142            desc.setAvailable(cmdAvailable);
143            commandDescriptors.put(name, desc);
144        } else if (EP_CMDTESTER.equals(extensionPoint)) {
145            CommandTesterDescriptor desc = (CommandTesterDescriptor) contribution;
146            CommandTester tester;
147            try {
148                tester = (CommandTester) desc.getTesterClass().newInstance();
149            } catch (ReflectiveOperationException e) {
150                throw new RuntimeException(e);
151            }
152            testers.put(desc.getName(), tester);
153        }
154    }
155
156    @Override
157    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
158    }
159
160    /*
161     * Service interface
162     */
163    @Override
164    public ExecResult execCommand(String commandName, CmdParameters params) throws CommandNotAvailable {
165        CommandAvailability availability = getCommandAvailability(commandName);
166        if (!availability.isAvailable()) {
167            throw new CommandNotAvailable(availability);
168        }
169
170        CommandLineDescriptor cmdDesc = commandDescriptors.get(commandName);
171        Executor executor = executors.get(cmdDesc.getExecutor());
172        EnvironmentDescriptor environment = new EnvironmentDescriptor().merge(env).merge(
173                envDescriptors.getOrDefault(commandName, envDescriptors.get(cmdDesc.getCommand())));
174        return executor.exec(cmdDesc, params, environment);
175    }
176
177    @Override
178    public CommandAvailability getCommandAvailability(String commandName) {
179        if (!commandDescriptors.containsKey(commandName)) {
180            return new CommandAvailability(commandName + " is not a registered command");
181        }
182
183        CommandLineDescriptor desc = commandDescriptors.get(commandName);
184        if (desc.isAvailable()) {
185            return new CommandAvailability();
186        } else {
187            return new CommandAvailability(desc.getInstallationDirective(), desc.getInstallErrorMessage());
188        }
189    }
190
191    @Override
192    public List<String> getRegistredCommands() {
193        List<String> cmds = new ArrayList<>();
194        cmds.addAll(commandDescriptors.keySet());
195        return cmds;
196    }
197
198    @Override
199    public List<String> getAvailableCommands() {
200        List<String> cmds = new ArrayList<>();
201
202        for (String cmdName : commandDescriptors.keySet()) {
203            CommandLineDescriptor cmd = commandDescriptors.get(cmdName);
204            if (cmd.isAvailable()) {
205                cmds.add(cmdName);
206            }
207        }
208        return cmds;
209    }
210
211    @Override
212    public boolean isValidParameter(String parameter) {
213        Pattern VALID_PATTERN;
214        if (SystemUtils.IS_OS_WINDOWS) {
215            VALID_PATTERN = VALID_PARAMETER_PATTERN_WIN;
216        } else {
217            VALID_PATTERN = VALID_PARAMETER_PATTERN;
218        }
219        return VALID_PATTERN.matcher(parameter).matches();
220    }
221
222    @Override
223    public void checkParameter(String parameter) {
224        if (!isValidParameter(parameter)) {
225            Pattern VALID_PATTERN;
226            if (SystemUtils.IS_OS_WINDOWS) {
227                VALID_PATTERN = VALID_PARAMETER_PATTERN_WIN;
228            } else {
229                VALID_PATTERN = VALID_PARAMETER_PATTERN;
230            }
231            throw new IllegalArgumentException(String.format("'%s' contains illegal characters. It should match: %s",
232                    parameter, VALID_PATTERN));
233        }
234    }
235
236    // ******************************************
237    // for testing
238
239    public static CommandLineDescriptor getCommandDescriptor(String commandName) {
240        return commandDescriptors.get(commandName);
241    }
242
243    @Override
244    public CmdParameters getDefaultCmdParameters() {
245        CmdParameters params = new CmdParameters();
246        params.addNamedParameter("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
247        params.addNamedParameter(Environment.NUXEO_TMP_DIR, Framework.getProperty(Environment.NUXEO_TMP_DIR));
248        return params;
249    }
250
251}