Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

DispatchCommand.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. /*
  2. * Copyright (C) 2009 The Android Open Source Project
  3. * Copyright 2014 gitblit.com.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package com.gitblit.transport.ssh.commands;
  18. import java.io.IOException;
  19. import java.io.StringWriter;
  20. import java.text.MessageFormat;
  21. import java.util.ArrayList;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.TreeSet;
  27. import org.apache.sshd.server.Command;
  28. import org.apache.sshd.server.Environment;
  29. import org.kohsuke.args4j.Argument;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;
  32. import ro.fortsoft.pf4j.ExtensionPoint;
  33. import com.gitblit.models.UserModel;
  34. import com.gitblit.utils.StringUtils;
  35. import com.gitblit.utils.cli.SubcommandHandler;
  36. import com.google.common.base.Charsets;
  37. import com.google.common.base.Joiner;
  38. import com.google.common.base.Strings;
  39. import com.google.common.collect.Maps;
  40. /**
  41. * Parses an SSH command-line and dispatches the command to the appropriate
  42. * BaseCommand instance.
  43. *
  44. * @since 1.5.0
  45. */
  46. public abstract class DispatchCommand extends BaseCommand implements ExtensionPoint {
  47. private Logger log = LoggerFactory.getLogger(getClass());
  48. @Argument(index = 0, required = false, metaVar = "COMMAND", handler = SubcommandHandler.class)
  49. private String commandName;
  50. @Argument(index = 1, multiValued = true, metaVar = "ARG")
  51. private List<String> args = new ArrayList<String>();
  52. private final Set<Class<? extends BaseCommand>> commands;
  53. private final Map<String, DispatchCommand> dispatchers;
  54. private final Map<String, String> aliasToCommand;
  55. private final Map<String, List<String>> commandToAliases;
  56. private final List<BaseCommand> instantiated;
  57. private Map<String, Class<? extends BaseCommand>> map;
  58. protected DispatchCommand() {
  59. commands = new HashSet<Class<? extends BaseCommand>>();
  60. dispatchers = Maps.newHashMap();
  61. aliasToCommand = Maps.newHashMap();
  62. commandToAliases = Maps.newHashMap();
  63. instantiated = new ArrayList<BaseCommand>();
  64. }
  65. @Override
  66. public void destroy() {
  67. super.destroy();
  68. commands.clear();
  69. aliasToCommand.clear();
  70. commandToAliases.clear();
  71. map = null;
  72. for (BaseCommand command : instantiated) {
  73. command.destroy();
  74. }
  75. instantiated.clear();
  76. for (DispatchCommand dispatcher : dispatchers.values()) {
  77. dispatcher.destroy();
  78. }
  79. dispatchers.clear();
  80. }
  81. /**
  82. * Setup this dispatcher. Commands and nested dispatchers are normally
  83. * registered within this method.
  84. *
  85. * @since 1.5.0
  86. */
  87. protected abstract void setup();
  88. /**
  89. * Register a command or a dispatcher by it's class.
  90. *
  91. * @param clazz
  92. */
  93. @SuppressWarnings("unchecked")
  94. protected final void register(Class<? extends BaseCommand> clazz) {
  95. if (DispatchCommand.class.isAssignableFrom(clazz)) {
  96. registerDispatcher((Class<? extends DispatchCommand>) clazz);
  97. return;
  98. }
  99. registerCommand(clazz);
  100. }
  101. /**
  102. * Register a command or a dispatcher instance.
  103. *
  104. * @param cmd
  105. */
  106. protected final void register(BaseCommand cmd) {
  107. if (cmd instanceof DispatchCommand) {
  108. registerDispatcher((DispatchCommand) cmd);
  109. return;
  110. }
  111. registerCommand(cmd);
  112. }
  113. private void registerDispatcher(Class<? extends DispatchCommand> clazz) {
  114. try {
  115. DispatchCommand dispatcher = clazz.newInstance();
  116. registerDispatcher(dispatcher);
  117. } catch (Exception e) {
  118. log.error("failed to instantiate {}", clazz.getName());
  119. }
  120. }
  121. private void registerDispatcher(DispatchCommand dispatcher) {
  122. Class<? extends DispatchCommand> dispatcherClass = dispatcher.getClass();
  123. if (!dispatcherClass.isAnnotationPresent(CommandMetaData.class)) {
  124. throw new RuntimeException(MessageFormat.format("{0} must be annotated with {1}!", dispatcher.getName(),
  125. CommandMetaData.class.getName()));
  126. }
  127. UserModel user = getContext().getClient().getUser();
  128. CommandMetaData meta = dispatcherClass.getAnnotation(CommandMetaData.class);
  129. if (meta.admin() && !user.canAdmin()) {
  130. log.debug(MessageFormat.format("excluding admin dispatcher {0} for {1}",
  131. meta.name(), user.username));
  132. return;
  133. }
  134. try {
  135. dispatcher.setContext(getContext());
  136. dispatcher.setWorkQueue(getWorkQueue());
  137. dispatcher.setup();
  138. if (dispatcher.commands.isEmpty() && dispatcher.dispatchers.isEmpty()) {
  139. log.debug(MessageFormat.format("excluding empty dispatcher {0} for {1}",
  140. meta.name(), user.username));
  141. return;
  142. }
  143. log.debug("registering {} dispatcher", meta.name());
  144. dispatchers.put(meta.name(), dispatcher);
  145. for (String alias : meta.aliases()) {
  146. aliasToCommand.put(alias, meta.name());
  147. if (!commandToAliases.containsKey(meta.name())) {
  148. commandToAliases.put(meta.name(), new ArrayList<String>());
  149. }
  150. commandToAliases.get(meta.name()).add(alias);
  151. }
  152. } catch (Exception e) {
  153. log.error("failed to register {} dispatcher", meta.name());
  154. }
  155. }
  156. /**
  157. * Registers a command as long as the user is permitted to execute it.
  158. *
  159. * @param clazz
  160. */
  161. private void registerCommand(Class<? extends BaseCommand> clazz) {
  162. if (!clazz.isAnnotationPresent(CommandMetaData.class)) {
  163. throw new RuntimeException(MessageFormat.format("{0} must be annotated with {1}!", clazz.getName(),
  164. CommandMetaData.class.getName()));
  165. }
  166. UserModel user = getContext().getClient().getUser();
  167. CommandMetaData meta = clazz.getAnnotation(CommandMetaData.class);
  168. if (meta.admin() && !user.canAdmin()) {
  169. log.debug(MessageFormat.format("excluding admin command {0} for {1}", meta.name(), user.username));
  170. return;
  171. }
  172. commands.add(clazz);
  173. }
  174. /**
  175. * Registers a command as long as the user is permitted to execute it.
  176. *
  177. * @param cmd
  178. */
  179. private void registerCommand(BaseCommand cmd) {
  180. if (!cmd.getClass().isAnnotationPresent(CommandMetaData.class)) {
  181. throw new RuntimeException(MessageFormat.format("{0} must be annotated with {1}!", cmd.getName(),
  182. CommandMetaData.class.getName()));
  183. }
  184. UserModel user = getContext().getClient().getUser();
  185. CommandMetaData meta = cmd.getClass().getAnnotation(CommandMetaData.class);
  186. if (meta.admin() && !user.canAdmin()) {
  187. log.debug(MessageFormat.format("excluding admin command {0} for {1}", meta.name(), user.username));
  188. return;
  189. }
  190. commands.add(cmd.getClass());
  191. instantiated.add(cmd);
  192. }
  193. private Map<String, Class<? extends BaseCommand>> getMap() {
  194. if (map == null) {
  195. map = Maps.newHashMapWithExpectedSize(commands.size());
  196. for (Class<? extends BaseCommand> cmd : commands) {
  197. CommandMetaData meta = cmd.getAnnotation(CommandMetaData.class);
  198. if (map.containsKey(meta.name()) || aliasToCommand.containsKey(meta.name())) {
  199. log.warn("{} already contains the \"{}\" command!", getName(), meta.name());
  200. } else {
  201. map.put(meta.name(), cmd);
  202. }
  203. for (String alias : meta.aliases()) {
  204. if (map.containsKey(alias) || aliasToCommand.containsKey(alias)) {
  205. log.warn("{} already contains the \"{}\" command!", getName(), alias);
  206. } else {
  207. aliasToCommand.put(alias, meta.name());
  208. if (!commandToAliases.containsKey(meta.name())) {
  209. commandToAliases.put(meta.name(), new ArrayList<String>());
  210. }
  211. commandToAliases.get(meta.name()).add(alias);
  212. }
  213. }
  214. }
  215. for (Map.Entry<String, DispatchCommand> entry : dispatchers.entrySet()) {
  216. map.put(entry.getKey(), entry.getValue().getClass());
  217. }
  218. }
  219. return map;
  220. }
  221. @Override
  222. public void start(Environment env) throws IOException {
  223. try {
  224. parseCommandLine();
  225. if (Strings.isNullOrEmpty(commandName)) {
  226. StringWriter msg = new StringWriter();
  227. msg.write(usage());
  228. throw new UnloggedFailure(1, msg.toString());
  229. }
  230. BaseCommand cmd = getCommand();
  231. if (getName().isEmpty()) {
  232. cmd.setName(commandName);
  233. } else {
  234. cmd.setName(getName() + " " + commandName);
  235. }
  236. cmd.setArguments(args.toArray(new String[args.size()]));
  237. provideStateTo(cmd);
  238. // atomicCmd.set(cmd);
  239. cmd.start(env);
  240. } catch (UnloggedFailure e) {
  241. String msg = e.getMessage();
  242. if (!msg.endsWith("\n")) {
  243. msg += "\n";
  244. }
  245. err.write(msg.getBytes(Charsets.UTF_8));
  246. err.flush();
  247. exit.onExit(e.exitCode);
  248. }
  249. }
  250. private BaseCommand getCommand() throws UnloggedFailure {
  251. Map<String, Class<? extends BaseCommand>> map = getMap();
  252. String name = commandName;
  253. if (aliasToCommand.containsKey(commandName)) {
  254. name = aliasToCommand.get(name);
  255. }
  256. if (dispatchers.containsKey(name)) {
  257. return dispatchers.get(name);
  258. }
  259. final Class<? extends BaseCommand> c = map.get(name);
  260. if (c == null) {
  261. String msg = (getName().isEmpty() ? "Gitblit" : getName()) + ": " + commandName + ": not found";
  262. throw new UnloggedFailure(1, msg);
  263. }
  264. for (BaseCommand cmd : instantiated) {
  265. // use an already instantiated command
  266. if (cmd.getClass().equals(c)) {
  267. return cmd;
  268. }
  269. }
  270. BaseCommand cmd = null;
  271. try {
  272. cmd = c.newInstance();
  273. instantiated.add(cmd);
  274. } catch (Exception e) {
  275. throw new UnloggedFailure(1, MessageFormat.format("Failed to instantiate {0} command", commandName));
  276. }
  277. return cmd;
  278. }
  279. private boolean hasVisibleCommands() {
  280. boolean visible = false;
  281. for (Class<? extends BaseCommand> cmd : commands) {
  282. visible |= !cmd.getAnnotation(CommandMetaData.class).hidden();
  283. if (visible) {
  284. return true;
  285. }
  286. }
  287. for (DispatchCommand cmd : dispatchers.values()) {
  288. visible |= cmd.hasVisibleCommands();
  289. if (visible) {
  290. return true;
  291. }
  292. }
  293. return false;
  294. }
  295. public String getDescription() {
  296. return getClass().getAnnotation(CommandMetaData.class).description();
  297. }
  298. @Override
  299. public String usage() {
  300. Set<String> cmds = new TreeSet<String>();
  301. Set<String> dcs = new TreeSet<String>();
  302. Map<String, String> displayNames = Maps.newHashMap();
  303. int maxLength = -1;
  304. Map<String, Class<? extends BaseCommand>> m = getMap();
  305. for (String name : m.keySet()) {
  306. Class<? extends BaseCommand> c = m.get(name);
  307. CommandMetaData meta = c.getAnnotation(CommandMetaData.class);
  308. if (meta.hidden()) {
  309. continue;
  310. }
  311. String displayName = name + (meta.admin() ? "*" : "");
  312. if (commandToAliases.containsKey(meta.name())) {
  313. displayName = name + (meta.admin() ? "*" : "")+ " (" + Joiner.on(',').join(commandToAliases.get(meta.name())) + ")";
  314. }
  315. displayNames.put(name, displayName);
  316. maxLength = Math.max(maxLength, displayName.length());
  317. if (DispatchCommand.class.isAssignableFrom(c)) {
  318. DispatchCommand d = dispatchers.get(name);
  319. if (d.hasVisibleCommands()) {
  320. dcs.add(name);
  321. }
  322. } else {
  323. cmds.add(name);
  324. }
  325. }
  326. String format = "%-" + maxLength + "s %s";
  327. final StringBuilder usage = new StringBuilder();
  328. if (!StringUtils.isEmpty(getName())) {
  329. String title = getName().toUpperCase() + ": " + getDescription();
  330. String b = com.gitblit.utils.StringUtils.leftPad("", title.length() + 2, '═');
  331. usage.append('\n');
  332. usage.append(b).append('\n');
  333. usage.append(' ').append(title).append('\n');
  334. usage.append(b).append('\n');
  335. usage.append('\n');
  336. }
  337. if (!cmds.isEmpty()) {
  338. usage.append("Available commands");
  339. if (!getName().isEmpty()) {
  340. usage.append(" of ");
  341. usage.append(getName());
  342. }
  343. usage.append(" are:\n");
  344. usage.append("\n");
  345. for (String name : cmds) {
  346. final Class<? extends Command> c = m.get(name);
  347. String displayName = displayNames.get(name);
  348. CommandMetaData meta = c.getAnnotation(CommandMetaData.class);
  349. usage.append(" ");
  350. usage.append(String.format(format, displayName, Strings.nullToEmpty(meta.description())));
  351. usage.append("\n");
  352. }
  353. usage.append("\n");
  354. }
  355. if (!dcs.isEmpty()) {
  356. usage.append("Available command dispatchers");
  357. if (!getName().isEmpty()) {
  358. usage.append(" of ");
  359. usage.append(getName());
  360. }
  361. usage.append(" are:\n");
  362. usage.append("\n");
  363. for (String name : dcs) {
  364. final Class<? extends BaseCommand> c = m.get(name);
  365. String displayName = displayNames.get(name);
  366. CommandMetaData meta = c.getAnnotation(CommandMetaData.class);
  367. usage.append(" ");
  368. usage.append(String.format(format, displayName, Strings.nullToEmpty(meta.description())));
  369. usage.append("\n");
  370. }
  371. usage.append("\n");
  372. }
  373. usage.append("See '");
  374. if (!StringUtils.isEmpty(getName())) {
  375. usage.append(getName());
  376. usage.append(' ');
  377. }
  378. usage.append("COMMAND --help' for more information.\n");
  379. usage.append("\n");
  380. return usage.toString();
  381. }
  382. }