blob: 20f083a64de0630bdf76a200e6d41c413bd36b22 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.cli.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.sling.cli.impl.release.ReleaseCLIGroup;
import org.jetbrains.annotations.NotNull;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE;
import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC;
@CommandLine.Command(
name = "docker run -it --env-file=./docker-env apache/sling-cli",
description = "Apache Sling Committers CLI"
)
@Component(service = CommandProcessor.class)
public class CommandProcessor {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final String EXEC_ARGS = "exec.args";
private BundleContext ctx;
private static final Map<String, Class> CLI_GROUPS;
static {
CLI_GROUPS = new HashMap<>();
CLI_GROUPS.put("release", ReleaseCLIGroup.class);
}
private Map<String, TreeSet<CommandWithProps>> commands = new ConcurrentHashMap<>();
@Activate
private void activate(BundleContext ctx) {
this.ctx = ctx;
}
@Reference(service = Command.class, cardinality = MULTIPLE, policy = DYNAMIC)
protected void bindCommand(Command cmd, Map<String, ?> props) {
CommandWithProps commandWithProps = CommandWithProps.of(cmd, props);
Set<CommandWithProps> bucket = commands.computeIfAbsent(commandWithProps.group, key -> new TreeSet<>());
bucket.add(commandWithProps);
}
protected void unbindCommand(Command cmd, Map<String, ?> props) {
CommandWithProps commandWithProps = CommandWithProps.of(cmd, props);
Set<CommandWithProps> bucket = commands.get(commandWithProps.group);
if (bucket != null) {
bucket.remove(commandWithProps);
if (bucket.isEmpty()) {
commands.remove(commandWithProps.group);
}
}
}
void runCommand() {
System.setProperty("picocli.usage.width", "140");
CommandLine commandLine = new CommandLine(this);
commandLine.addSubcommand(CommandLine.HelpCommand.class);
for (Map.Entry<String, TreeSet<CommandWithProps>> entry : commands.entrySet()) {
String group = entry.getKey();
Class<?> groupClass = CLI_GROUPS.get(group);
if (groupClass != null) {
CommandLine secondary = new CommandLine(groupClass);
for (CommandWithProps command : entry.getValue()) {
secondary.addSubcommand(command.name, command.cmd);
}
secondary.addSubcommand(CommandLine.HelpCommand.class);
commandLine.addSubcommand(group, secondary);
} else {
for (CommandWithProps command : entry.getValue()) {
commandLine.addSubcommand(command.group, command.cmd);
}
}
}
int commandExitCode;
try {
String[] arguments = arguments(ctx.getProperty(EXEC_ARGS));
commandExitCode = commandLine.execute(arguments);
} catch (CommandLine.ParameterException e) {
commandLine.getErr().println(e.getMessage());
if (!CommandLine.UnmatchedArgumentException.printSuggestions(e, commandLine.getErr())) {
e.getCommandLine().usage(commandLine.getErr());
}
commandExitCode = commandLine.getCommandSpec().exitCodeOnInvalidInput();
} catch (Exception e) {
logger.warn("Failed running command.", e);
commandExitCode = 1;
} finally {
try {
ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(Framework.class).stop();
} catch (BundleException e) {
logger.error("Failed shutting down framework, forcing exit", e);
System.exit(1);
}
}
System.exit(commandExitCode);
}
private String[] arguments(String cliSpec) {
if (cliSpec == null) {
return new String[0];
}
return cliSpec.split(" ");
}
static class CommandWithProps implements Comparable<CommandWithProps> {
private final String group;
private final String name;
private final Command cmd;
static CommandWithProps of(Command cmd, Map<String, ?> props) {
return new CommandWithProps(
cmd,
(String) props.get(Command.PROPERTY_NAME_COMMAND_GROUP),
(String) props.get(Command.PROPERTY_NAME_COMMAND_NAME)
);
}
CommandWithProps(Command cmd, String group, String name) {
this.cmd = cmd;
this.group = group;
this.name = name;
}
@Override
public int compareTo(@NotNull CommandProcessor.CommandWithProps o) {
if (!group.equals(o.group)) {
return group.compareTo(o.group);
} else {
return name.compareTo(o.name);
}
}
@Override
public int hashCode() {
return Objects.hash(group, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof CommandWithProps) {
CommandWithProps other = (CommandWithProps) obj;
return Objects.equals(group, other.group) && Objects.equals(name, other.name);
}
return false;
}
}
}