001/*
002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 */
020
021package org.nuxeo.ecm.platform.commandline.executor.service;
022
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
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 = "DefaultCommandTester";
060
061    public static final String DEFAULT_EXECUTOR = "ShellExecutor";
062
063    protected Map<String, CommandLineDescriptor> commandDescriptors = new HashMap<>();
064
065    protected EnvironmentDescriptor env = new EnvironmentDescriptor();
066
067    protected Map<String, EnvironmentDescriptor> envDescriptors = new HashMap<>();
068
069    protected Map<String, CommandTester> testers = new HashMap<>();
070
071    protected 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().getDeclaredConstructor().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 CommandLineDescriptor getCommandLineDescriptor(String commandName) {
213        return commandDescriptors.get(commandName);
214    }
215
216    // ******************************************
217    // for testing
218
219    /** @deprecated since 11.4, use instance method {@link #getCommandLineDescriptor} instead */
220    @Deprecated
221    public static CommandLineDescriptor getCommandDescriptor(String commandName) {
222        return Framework.getService(CommandLineExecutorService.class).getCommandLineDescriptor(commandName);
223    }
224
225    @Override
226    public CmdParameters getDefaultCmdParameters() {
227        CmdParameters params = new CmdParameters();
228        params.addNamedParameter("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
229        params.addNamedParameter(Environment.NUXEO_TMP_DIR, Environment.getDefault().getTemp().getPath());
230        return params;
231    }
232
233}