]> source.dussan.org Git - gitblit.git/commitdiff
Revise dispatchers and move command classes
authorJames Moger <james.moger@gitblit.com>
Tue, 18 Mar 2014 02:27:58 +0000 (22:27 -0400)
committerJames Moger <james.moger@gitblit.com>
Thu, 10 Apr 2014 22:58:09 +0000 (18:58 -0400)
25 files changed:
src/main/java/com/gitblit/transport/ssh/CommandMetaData.java [deleted file]
src/main/java/com/gitblit/transport/ssh/SshCommandContext.java [deleted file]
src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java [deleted file]
src/main/java/com/gitblit/transport/ssh/SshDaemon.java
src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java
src/main/java/com/gitblit/transport/ssh/commands/CommandMetaData.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/commands/SshCommandContext.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/commands/SshCommandFactory.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/git/GitDispatchCommand.java [deleted file]
src/main/java/com/gitblit/transport/ssh/git/GitDispatcher.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/git/Receive.java
src/main/java/com/gitblit/transport/ssh/git/Upload.java
src/main/java/com/gitblit/transport/ssh/gitblit/AddKeyCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java
src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatchCommand.java [deleted file]
src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java [new file with mode: 0644]
src/main/java/com/gitblit/transport/ssh/gitblit/LsCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/LsUsersCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/RemoveKeyCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/ReviewCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java
src/main/java/com/gitblit/transport/ssh/gitblit/VersionCommand.java

diff --git a/src/main/java/com/gitblit/transport/ssh/CommandMetaData.java b/src/main/java/com/gitblit/transport/ssh/CommandMetaData.java
deleted file mode 100644 (file)
index 0d39f33..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-//Copyright (C) 2013 The Android Open Source Project
-//
-//Licensed 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 com.gitblit.transport.ssh;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
-* Annotation tagged on a concrete Command to describe what it is doing
-*/
-@Target({ElementType.TYPE})
-@Retention(RUNTIME)
-public @interface CommandMetaData {
-String name();
-String description() default "";
-boolean admin() default false;
-boolean hidden() default false;
-}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshCommandContext.java b/src/main/java/com/gitblit/transport/ssh/SshCommandContext.java
deleted file mode 100644 (file)
index 163d079..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2014 gitblit.com.
- *
- * Licensed 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 com.gitblit.transport.ssh;
-
-import com.gitblit.manager.IGitblit;
-
-public class SshCommandContext {
-
-       private final IGitblit gitblit;
-       private final SshDaemonClient client;
-       private final String commandLine;
-
-       public SshCommandContext(IGitblit gitblit, SshDaemonClient client, String commandLine) {
-               this.gitblit = gitblit;
-               this.client = client;
-               this.commandLine = commandLine;
-       }
-
-       public IGitblit getGitblit() {
-               return gitblit;
-       }
-
-       public SshDaemonClient getClient() {
-               return client;
-       }
-
-       public String getCommandLine() {
-               return commandLine;
-       }
-}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java
deleted file mode 100644 (file)
index 2b2093e..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright 2014 gitblit.com.
- *
- * Licensed 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 com.gitblit.transport.ssh;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
-import org.apache.sshd.server.session.ServerSession;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.manager.IGitblit;
-import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.commands.DispatchCommand;
-import com.gitblit.transport.ssh.git.GitDispatchCommand;
-import com.gitblit.transport.ssh.gitblit.GitblitDispatchCommand;
-import com.gitblit.utils.IdGenerator;
-import com.gitblit.utils.WorkQueue;
-import com.google.common.util.concurrent.Atomics;
-
-/**
- *
- * @author Eric Myhre
- *
- */
-public class SshCommandFactory implements CommandFactory {
-       private static final Logger logger = LoggerFactory.getLogger(SshCommandFactory.class);
-
-       private final IGitblit gitblit;
-       private final ScheduledExecutorService startExecutor;
-
-       public SshCommandFactory(IGitblit gitblit, IdGenerator idGenerator) {
-               this.gitblit = gitblit;
-
-               int threads = 2;// cfg.getInt("sshd","commandStartThreads", 2);
-               WorkQueue workQueue = new WorkQueue(idGenerator);
-               startExecutor = workQueue.createQueue(threads, "SshCommandStart");
-       }
-
-       /**
-        * Creates the root dispatcher command which builds up the available commands.
-        *
-        * @param the client
-        * @param the command line
-        * @return the root dispatcher command
-        */
-       protected DispatchCommand createRootDispatcher(SshDaemonClient client, String cmdLine) {
-               final UserModel user = client.getUser();
-
-               DispatchCommand root = new DispatchCommand() {
-               };
-               root.setContext(new SshCommandContext(gitblit, client, cmdLine));
-
-               // TODO convert these dispatchers to plugin extension points
-               root.registerDispatcher(user, GitblitDispatchCommand.class);
-               root.registerDispatcher(user, GitDispatchCommand.class);
-
-               return root;
-       }
-
-       @Override
-       public Command createCommand(final String commandLine) {
-               return new Trampoline(commandLine);
-       }
-
-       private class Trampoline implements Command, SessionAware {
-               private final String[] argv;
-               private ServerSession session;
-               private InputStream in;
-               private OutputStream out;
-               private OutputStream err;
-               private ExitCallback exit;
-               private Environment env;
-               private String cmdLine;
-               private DispatchCommand cmd;
-               private final AtomicBoolean logged;
-               private final AtomicReference<Future<?>> task;
-
-               Trampoline(String line) {
-                       if (line.startsWith("git-")) {
-                               line = "git " + line;
-                       }
-                       cmdLine = line;
-                       argv = split(line);
-                       logged = new AtomicBoolean();
-                       task = Atomics.newReference();
-               }
-
-               @Override
-               public void setSession(ServerSession session) {
-                       this.session = session;
-               }
-
-               @Override
-               public void setInputStream(final InputStream in) {
-                       this.in = in;
-               }
-
-               @Override
-               public void setOutputStream(final OutputStream out) {
-                       this.out = out;
-               }
-
-               @Override
-               public void setErrorStream(final OutputStream err) {
-                       this.err = err;
-               }
-
-               @Override
-               public void setExitCallback(final ExitCallback callback) {
-                       this.exit = callback;
-               }
-
-               @Override
-               public void start(final Environment env) throws IOException {
-                       this.env = env;
-                       task.set(startExecutor.submit(new Runnable() {
-                               @Override
-                               public void run() {
-                                       try {
-                                               onStart();
-                                       } catch (Exception e) {
-                                               logger.warn("Cannot start command ", e);
-                                       }
-                               }
-
-                               @Override
-                               public String toString() {
-                                       return "start (user " + session.getUsername() + ")";
-                               }
-                       }));
-               }
-
-               private void onStart() throws IOException {
-                       synchronized (this) {
-                               SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
-                               try {
-                                       cmd = createRootDispatcher(client, cmdLine);
-                                       cmd.setArguments(argv);
-                                       cmd.setInputStream(in);
-                                       cmd.setOutputStream(out);
-                                       cmd.setErrorStream(err);
-                                       cmd.setExitCallback(new ExitCallback() {
-                                               @Override
-                                               public void onExit(int rc, String exitMessage) {
-                                                       exit.onExit(translateExit(rc), exitMessage);
-                                                       log(rc);
-                                               }
-
-                                               @Override
-                                               public void onExit(int rc) {
-                                                       exit.onExit(translateExit(rc));
-                                                       log(rc);
-                                               }
-                                       });
-                                       cmd.start(env);
-                               } finally {
-                                       client = null;
-                               }
-                       }
-               }
-
-               private int translateExit(final int rc) {
-                       return rc;
-                       //
-                       // switch (rc) {
-                       // case BaseCommand.STATUS_NOT_ADMIN:
-                       // return 1;
-                       //
-                       // case BaseCommand.STATUS_CANCEL:
-                       // return 15 /* SIGKILL */;
-                       //
-                       // case BaseCommand.STATUS_NOT_FOUND:
-                       // return 127 /* POSIX not found */;
-                       //
-                       // default:
-                       // return rc;
-                       // }
-
-               }
-
-               private void log(final int rc) {
-                       if (logged.compareAndSet(false, true)) {
-                               // log.onExecute(cmd, rc);
-                               logger.info("onExecute: {} exits with: {}", cmd.getClass().getSimpleName(), rc);
-                       }
-               }
-
-               @Override
-               public void destroy() {
-                       Future<?> future = task.getAndSet(null);
-                       if (future != null) {
-                               future.cancel(true);
-                               // destroyExecutor.execute(new Runnable() {
-                               // @Override
-                               // public void run() {
-                               // onDestroy();
-                               // }
-                               // });
-                       }
-               }
-
-               @SuppressWarnings("unused")
-               private void onDestroy() {
-                       synchronized (this) {
-                               if (cmd != null) {
-                                       // final Context old = sshScope.set(ctx);
-                                       try {
-                                               cmd.destroy();
-                                               // log(BaseCommand.STATUS_CANCEL);
-                                       } finally {
-                                               // ctx = null;
-                                               cmd = null;
-                                               // sshScope.set(old);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /** Split a command line into a string array. */
-       static public String[] split(String commandLine) {
-               final List<String> list = new ArrayList<String>();
-               boolean inquote = false;
-               boolean inDblQuote = false;
-               StringBuilder r = new StringBuilder();
-               for (int ip = 0; ip < commandLine.length();) {
-                       final char b = commandLine.charAt(ip++);
-                       switch (b) {
-                       case '\t':
-                       case ' ':
-                               if (inquote || inDblQuote)
-                                       r.append(b);
-                               else if (r.length() > 0) {
-                                       list.add(r.toString());
-                                       r = new StringBuilder();
-                               }
-                               continue;
-                       case '\"':
-                               if (inquote)
-                                       r.append(b);
-                               else
-                                       inDblQuote = !inDblQuote;
-                               continue;
-                       case '\'':
-                               if (inDblQuote)
-                                       r.append(b);
-                               else
-                                       inquote = !inquote;
-                               continue;
-                       case '\\':
-                               if (inquote || ip == commandLine.length())
-                                       r.append(b); // literal within a quote
-                               else
-                                       r.append(commandLine.charAt(ip++));
-                               continue;
-                       default:
-                               r.append(b);
-                               continue;
-                       }
-               }
-               if (r.length() > 0) {
-                       list.add(r.toString());
-               }
-               return list.toArray(new String[list.size()]);
-       }
-}
index da9a37266c8da6e6bd11a3a0c2523f48c28aa0d4..aeb6ce516d4d407e040d6821319876dcf6a10a59 100644 (file)
@@ -34,6 +34,7 @@ import com.gitblit.Constants;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.manager.IGitblit;
+import com.gitblit.transport.ssh.commands.SshCommandFactory;
 import com.gitblit.utils.IdGenerator;
 import com.gitblit.utils.StringUtils;
 
index 819028c51049c29fb04f029078460d53151bb499..ccf2586bb902b785f1afe6abe6313b638015c456 100644 (file)
@@ -35,6 +35,7 @@ import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.models.UserModel;
 import com.gitblit.transport.ssh.commands.DispatchCommand;
+import com.gitblit.transport.ssh.commands.SshCommandFactory;
 import com.gitblit.utils.StringUtils;
 
 /**
index a3df787ca9a891bb19f2ac9662bf00e31d9e8ae3..7088fefaa4c660c6d924ce7cbc5a399b404c923f 100644 (file)
@@ -37,7 +37,6 @@ import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.gitblit.transport.ssh.SshCommandContext;
 import com.gitblit.utils.IdGenerator;
 import com.gitblit.utils.WorkQueue;
 import com.gitblit.utils.WorkQueue.CancelableRunnable;
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/CommandMetaData.java b/src/main/java/com/gitblit/transport/ssh/commands/CommandMetaData.java
new file mode 100644 (file)
index 0000000..133b9cb
--- /dev/null
@@ -0,0 +1,33 @@
+//Copyright (C) 2013 The Android Open Source Project
+//
+//Licensed 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 com.gitblit.transport.ssh.commands;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+* Annotation tagged on a concrete Command to describe what it is doing
+*/
+@Target({ElementType.TYPE})
+@Retention(RUNTIME)
+public @interface CommandMetaData {
+String name();
+String description() default "";
+boolean admin() default false;
+boolean hidden() default false;
+}
index dd581f4dc46d1aafe130459ebeb8cae54117daa4..f7c78d28ad3ea821a995d301234ac8a7f2cdb0fb 100644 (file)
@@ -31,7 +31,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.cli.SubcommandHandler;
 import com.google.common.base.Charsets;
@@ -48,15 +47,15 @@ public abstract class DispatchCommand extends BaseCommand {
        @Argument(index = 1, multiValued = true, metaVar = "ARG")
        private List<String> args = new ArrayList<String>();
 
-       private Set<Class<? extends BaseCommand>> commands;
+       private final Set<Class<? extends BaseCommand>> commands;
        private Map<String, Class<? extends BaseCommand>> map;
        private Map<String, BaseCommand> dispatchers;
 
-       public DispatchCommand() {
+       protected DispatchCommand() {
                commands = new HashSet<Class<? extends BaseCommand>>();
        }
 
-       public void registerDispatcher(UserModel user, Class<? extends DispatchCommand> cmd) {
+       protected void registerDispatcher(UserModel user, Class<? extends DispatchCommand> cmd) {
                if (!cmd.isAnnotationPresent(CommandMetaData.class)) {
                        throw new RuntimeException(MessageFormat.format("{0} must be annotated with {1}!", cmd.getName(),
                                        CommandMetaData.class.getName()));
@@ -80,8 +79,7 @@ public abstract class DispatchCommand extends BaseCommand {
                }
        }
 
-       protected void registerCommands(UserModel user) {
-       }
+       protected abstract void registerCommands(UserModel user);
 
 
        /**
@@ -90,7 +88,7 @@ public abstract class DispatchCommand extends BaseCommand {
         * @param user
         * @param cmd
         */
-       public void registerCommand(UserModel user, Class<? extends BaseCommand> cmd) {
+       protected void registerCommand(UserModel user, Class<? extends BaseCommand> cmd) {
                if (!cmd.isAnnotationPresent(CommandMetaData.class)) {
                        throw new RuntimeException(MessageFormat.format("{0} must be annotated with {1}!", cmd.getName(),
                                        CommandMetaData.class.getName()));
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java b/src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java
new file mode 100644 (file)
index 0000000..5d9eb19
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.transport.ssh.commands;
+
+import com.gitblit.manager.IGitblit;
+import com.gitblit.models.UserModel;
+import com.gitblit.transport.ssh.SshDaemonClient;
+import com.gitblit.transport.ssh.git.GitDispatcher;
+import com.gitblit.transport.ssh.gitblit.GitblitDispatcher;
+
+/**
+ * The root dispatcher is the dispatch command that handles registering all
+ * other commands.
+ *
+ */
+public class RootDispatcher extends DispatchCommand {
+
+       public RootDispatcher(IGitblit gitblit, SshDaemonClient client, String cmdLine) {
+               super();
+               setContext(new SshCommandContext(gitblit, client, cmdLine));
+
+               final UserModel user = client.getUser();
+               registerDispatcher(user, GitblitDispatcher.class);
+               registerDispatcher(user, GitDispatcher.class);
+
+               // TODO register plugin dispatchers here
+       }
+
+       @Override
+       protected final void registerCommands(UserModel user) {
+       }
+
+       @Override
+       protected final void registerCommand(UserModel user, Class<? extends BaseCommand> cmd) {
+               throw new RuntimeException("The root dispatcher does not accept commands, only dispatchers!");
+       }
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommandContext.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommandContext.java
new file mode 100644 (file)
index 0000000..15f7a8f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.transport.ssh.commands;
+
+import com.gitblit.manager.IGitblit;
+import com.gitblit.transport.ssh.SshDaemonClient;
+
+public class SshCommandContext {
+
+       private final IGitblit gitblit;
+       private final SshDaemonClient client;
+       private final String commandLine;
+
+       public SshCommandContext(IGitblit gitblit, SshDaemonClient client, String commandLine) {
+               this.gitblit = gitblit;
+               this.client = client;
+               this.commandLine = commandLine;
+       }
+
+       public IGitblit getGitblit() {
+               return gitblit;
+       }
+
+       public SshDaemonClient getClient() {
+               return client;
+       }
+
+       public String getCommandLine() {
+               return commandLine;
+       }
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommandFactory.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommandFactory.java
new file mode 100644 (file)
index 0000000..3eefcae
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.transport.ssh.commands;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.manager.IGitblit;
+import com.gitblit.transport.ssh.SshDaemonClient;
+import com.gitblit.utils.IdGenerator;
+import com.gitblit.utils.WorkQueue;
+import com.google.common.util.concurrent.Atomics;
+
+/**
+ *
+ * @author Eric Myhre
+ *
+ */
+public class SshCommandFactory implements CommandFactory {
+       private static final Logger logger = LoggerFactory.getLogger(SshCommandFactory.class);
+
+       private final IGitblit gitblit;
+       private final ScheduledExecutorService startExecutor;
+
+       public SshCommandFactory(IGitblit gitblit, IdGenerator idGenerator) {
+               this.gitblit = gitblit;
+
+               int threads = 2;// cfg.getInt("sshd","commandStartThreads", 2);
+               WorkQueue workQueue = new WorkQueue(idGenerator);
+               startExecutor = workQueue.createQueue(threads, "SshCommandStart");
+       }
+
+       public RootDispatcher createRootDispatcher(SshDaemonClient client, String commandLine) {
+               return new RootDispatcher(gitblit, client, commandLine);
+       }
+
+       @Override
+       public Command createCommand(final String commandLine) {
+               return new Trampoline(commandLine);
+       }
+
+       private class Trampoline implements Command, SessionAware {
+               private final String[] argv;
+               private ServerSession session;
+               private InputStream in;
+               private OutputStream out;
+               private OutputStream err;
+               private ExitCallback exit;
+               private Environment env;
+               private String cmdLine;
+               private DispatchCommand cmd;
+               private final AtomicBoolean logged;
+               private final AtomicReference<Future<?>> task;
+
+               Trampoline(String line) {
+                       if (line.startsWith("git-")) {
+                               line = "git " + line;
+                       }
+                       cmdLine = line;
+                       argv = split(line);
+                       logged = new AtomicBoolean();
+                       task = Atomics.newReference();
+               }
+
+               @Override
+               public void setSession(ServerSession session) {
+                       this.session = session;
+               }
+
+               @Override
+               public void setInputStream(final InputStream in) {
+                       this.in = in;
+               }
+
+               @Override
+               public void setOutputStream(final OutputStream out) {
+                       this.out = out;
+               }
+
+               @Override
+               public void setErrorStream(final OutputStream err) {
+                       this.err = err;
+               }
+
+               @Override
+               public void setExitCallback(final ExitCallback callback) {
+                       this.exit = callback;
+               }
+
+               @Override
+               public void start(final Environment env) throws IOException {
+                       this.env = env;
+                       task.set(startExecutor.submit(new Runnable() {
+                               @Override
+                               public void run() {
+                                       try {
+                                               onStart();
+                                       } catch (Exception e) {
+                                               logger.warn("Cannot start command ", e);
+                                       }
+                               }
+
+                               @Override
+                               public String toString() {
+                                       return "start (user " + session.getUsername() + ")";
+                               }
+                       }));
+               }
+
+               private void onStart() throws IOException {
+                       synchronized (this) {
+                               SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
+                               try {
+                                       cmd = createRootDispatcher(client, cmdLine);
+                                       cmd.setArguments(argv);
+                                       cmd.setInputStream(in);
+                                       cmd.setOutputStream(out);
+                                       cmd.setErrorStream(err);
+                                       cmd.setExitCallback(new ExitCallback() {
+                                               @Override
+                                               public void onExit(int rc, String exitMessage) {
+                                                       exit.onExit(translateExit(rc), exitMessage);
+                                                       log(rc);
+                                               }
+
+                                               @Override
+                                               public void onExit(int rc) {
+                                                       exit.onExit(translateExit(rc));
+                                                       log(rc);
+                                               }
+                                       });
+                                       cmd.start(env);
+                               } finally {
+                                       client = null;
+                               }
+                       }
+               }
+
+               private int translateExit(final int rc) {
+                       return rc;
+                       //
+                       // switch (rc) {
+                       // case BaseCommand.STATUS_NOT_ADMIN:
+                       // return 1;
+                       //
+                       // case BaseCommand.STATUS_CANCEL:
+                       // return 15 /* SIGKILL */;
+                       //
+                       // case BaseCommand.STATUS_NOT_FOUND:
+                       // return 127 /* POSIX not found */;
+                       //
+                       // default:
+                       // return rc;
+                       // }
+
+               }
+
+               private void log(final int rc) {
+                       if (logged.compareAndSet(false, true)) {
+                               // log.onExecute(cmd, rc);
+                               logger.info("onExecute: {} exits with: {}", cmd.getClass().getSimpleName(), rc);
+                       }
+               }
+
+               @Override
+               public void destroy() {
+                       Future<?> future = task.getAndSet(null);
+                       if (future != null) {
+                               future.cancel(true);
+                               // destroyExecutor.execute(new Runnable() {
+                               // @Override
+                               // public void run() {
+                               // onDestroy();
+                               // }
+                               // });
+                       }
+               }
+
+               @SuppressWarnings("unused")
+               private void onDestroy() {
+                       synchronized (this) {
+                               if (cmd != null) {
+                                       // final Context old = sshScope.set(ctx);
+                                       try {
+                                               cmd.destroy();
+                                               // log(BaseCommand.STATUS_CANCEL);
+                                       } finally {
+                                               // ctx = null;
+                                               cmd = null;
+                                               // sshScope.set(old);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /** Split a command line into a string array. */
+       static public String[] split(String commandLine) {
+               final List<String> list = new ArrayList<String>();
+               boolean inquote = false;
+               boolean inDblQuote = false;
+               StringBuilder r = new StringBuilder();
+               for (int ip = 0; ip < commandLine.length();) {
+                       final char b = commandLine.charAt(ip++);
+                       switch (b) {
+                       case '\t':
+                       case ' ':
+                               if (inquote || inDblQuote)
+                                       r.append(b);
+                               else if (r.length() > 0) {
+                                       list.add(r.toString());
+                                       r = new StringBuilder();
+                               }
+                               continue;
+                       case '\"':
+                               if (inquote)
+                                       r.append(b);
+                               else
+                                       inDblQuote = !inDblQuote;
+                               continue;
+                       case '\'':
+                               if (inDblQuote)
+                                       r.append(b);
+                               else
+                                       inquote = !inquote;
+                               continue;
+                       case '\\':
+                               if (inquote || ip == commandLine.length())
+                                       r.append(b); // literal within a quote
+                               else
+                                       r.append(commandLine.charAt(ip++));
+                               continue;
+                       default:
+                               r.append(b);
+                               continue;
+                       }
+               }
+               if (r.length() > 0) {
+                       list.add(r.toString());
+               }
+               return list.toArray(new String[list.size()]);
+       }
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/git/GitDispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/git/GitDispatchCommand.java
deleted file mode 100644 (file)
index adeace5..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2014 gitblit.com.
- *
- * Licensed 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 com.gitblit.transport.ssh.git;
-
-import com.gitblit.git.GitblitReceivePackFactory;
-import com.gitblit.git.GitblitUploadPackFactory;
-import com.gitblit.git.RepositoryResolver;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
-import com.gitblit.transport.ssh.SshCommandContext;
-import com.gitblit.transport.ssh.SshDaemonClient;
-import com.gitblit.transport.ssh.commands.BaseCommand;
-import com.gitblit.transport.ssh.commands.DispatchCommand;
-
-@CommandMetaData(name = "git", description="Dispatcher for git receive and upload commands", hidden = true)
-public class GitDispatchCommand extends DispatchCommand {
-
-       protected RepositoryResolver<SshDaemonClient> repositoryResolver;
-       protected GitblitUploadPackFactory<SshDaemonClient> uploadPackFactory;
-       protected GitblitReceivePackFactory<SshDaemonClient> receivePackFactory;
-
-       @Override
-       public void setContext(SshCommandContext context) {
-               super.setContext(context);
-
-               IGitblit gitblit = context.getGitblit();
-               repositoryResolver = new RepositoryResolver<SshDaemonClient>(gitblit);
-               uploadPackFactory = new GitblitUploadPackFactory<SshDaemonClient>(gitblit);
-               receivePackFactory = new GitblitReceivePackFactory<SshDaemonClient>(gitblit);
-       }
-
-       @Override
-       protected void registerCommands(UserModel user) {
-               registerCommand(user, Upload.class);
-               registerCommand(user, Receive.class);
-       }
-
-       @Override
-       protected void provideStateTo(final BaseCommand cmd) {
-               super.provideStateTo(cmd);
-
-               BaseGitCommand a = (BaseGitCommand) cmd;
-               a.setRepositoryResolver(repositoryResolver);
-               a.setUploadPackFactory(uploadPackFactory);
-               a.setReceivePackFactory(receivePackFactory);
-       }
-}
diff --git a/src/main/java/com/gitblit/transport/ssh/git/GitDispatcher.java b/src/main/java/com/gitblit/transport/ssh/git/GitDispatcher.java
new file mode 100644 (file)
index 0000000..fa1dfbd
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.transport.ssh.git;
+
+import com.gitblit.git.GitblitReceivePackFactory;
+import com.gitblit.git.GitblitUploadPackFactory;
+import com.gitblit.git.RepositoryResolver;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.models.UserModel;
+import com.gitblit.transport.ssh.SshDaemonClient;
+import com.gitblit.transport.ssh.commands.BaseCommand;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
+import com.gitblit.transport.ssh.commands.DispatchCommand;
+import com.gitblit.transport.ssh.commands.SshCommandContext;
+
+@CommandMetaData(name = "git", description="Dispatcher for git receive and upload commands", hidden = true)
+public class GitDispatcher extends DispatchCommand {
+
+       protected RepositoryResolver<SshDaemonClient> repositoryResolver;
+       protected GitblitUploadPackFactory<SshDaemonClient> uploadPackFactory;
+       protected GitblitReceivePackFactory<SshDaemonClient> receivePackFactory;
+
+       @Override
+       public void setContext(SshCommandContext context) {
+               super.setContext(context);
+
+               IGitblit gitblit = context.getGitblit();
+               repositoryResolver = new RepositoryResolver<SshDaemonClient>(gitblit);
+               uploadPackFactory = new GitblitUploadPackFactory<SshDaemonClient>(gitblit);
+               receivePackFactory = new GitblitReceivePackFactory<SshDaemonClient>(gitblit);
+       }
+
+       @Override
+       protected void registerCommands(UserModel user) {
+               registerCommand(user, Upload.class);
+               registerCommand(user, Receive.class);
+       }
+
+       @Override
+       protected void provideStateTo(final BaseCommand cmd) {
+               super.provideStateTo(cmd);
+
+               BaseGitCommand a = (BaseGitCommand) cmd;
+               a.setRepositoryResolver(repositoryResolver);
+               a.setUploadPackFactory(uploadPackFactory);
+               a.setReceivePackFactory(receivePackFactory);
+       }
+}
index 4089f1dff5c05e41862667f612e1ff6d8d94bca6..9597eb4d599f31dcd5878576bbddf79e27a27c49 100644 (file)
@@ -17,7 +17,7 @@ package com.gitblit.transport.ssh.git;
 
 import org.eclipse.jgit.transport.ReceivePack;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 @CommandMetaData(name = "git-receive-pack", description = "Receives pushes from a client")
 public class Receive extends BaseGitCommand {
index 5793c3e68af9a180c2f8e7faaea7c94aceb6ea0c..5de6b4d28f5426ea3d6e66626b1c396103cf2a73 100644 (file)
@@ -17,7 +17,7 @@ package com.gitblit.transport.ssh.git;
 
 import org.eclipse.jgit.transport.UploadPack;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 @CommandMetaData(name = "git-upload-pack", description = "Sends packs to a client for clone and fetch")
 public class Upload extends BaseGitCommand {
index 6d5c85c7ad9ea003368ac1b3b0c9323664412cb8..ae24dfb3a301287c55f33779c8f6dd3b61b6794b 100644 (file)
@@ -23,7 +23,7 @@ import org.kohsuke.args4j.Argument;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 /**
  * Add a key to the current user's authorized keys list.
index b2e1b1b0367eeded0e8f1b0a6985be7685d0be2c..2917b6d258bf5ca94b4cf749c4965e55d20c4ff0 100644 (file)
@@ -18,7 +18,7 @@ package com.gitblit.transport.ssh.gitblit;
 
 import org.kohsuke.args4j.Option;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 import com.gitblit.transport.ssh.commands.SshCommand;
 
 @CommandMetaData(name = "create-repository", description = "Create new GIT repository", admin = true, hidden = true)
diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatchCommand.java
deleted file mode 100644 (file)
index 9eff9bd..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2014 gitblit.com.
- *
- * Licensed 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 com.gitblit.transport.ssh.gitblit;
-
-import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
-import com.gitblit.transport.ssh.commands.DispatchCommand;
-
-@CommandMetaData(name = "gitblit", description = "Gitblit server commands")
-public class GitblitDispatchCommand extends DispatchCommand {
-
-       @Override
-       protected void registerCommands(UserModel user) {
-               // normal usage commands
-               registerCommand(user, VersionCommand.class);
-               registerCommand(user, AddKeyCommand.class);
-               registerCommand(user, RemoveKeyCommand.class);
-               registerCommand(user, LsCommand.class);
-               registerCommand(user, ReviewCommand.class);
-
-               // administrative commands
-               registerCommand(user, LsUsersCommand.class);
-               registerCommand(user, CreateRepository.class);
-               registerCommand(user, SetAccountCommand.class);
-       }
-}
diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java
new file mode 100644 (file)
index 0000000..eb3bb0c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.transport.ssh.gitblit;
+
+import com.gitblit.models.UserModel;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
+import com.gitblit.transport.ssh.commands.DispatchCommand;
+
+@CommandMetaData(name = "gitblit", description = "Gitblit server commands")
+public class GitblitDispatcher extends DispatchCommand {
+
+       @Override
+       protected void registerCommands(UserModel user) {
+               // normal usage commands
+               registerCommand(user, VersionCommand.class);
+               registerCommand(user, AddKeyCommand.class);
+               registerCommand(user, RemoveKeyCommand.class);
+               registerCommand(user, LsCommand.class);
+               registerCommand(user, ReviewCommand.class);
+
+               // administrative commands
+               registerCommand(user, LsUsersCommand.class);
+               registerCommand(user, CreateRepository.class);
+               registerCommand(user, SetAccountCommand.class);
+       }
+}
index cf50a2eece17e967c96934ce9779ecf6703d7930..75cb2d83e83477f7a8b4a9bd7a0070c694823c34 100644 (file)
@@ -27,7 +27,7 @@ import com.gitblit.manager.IGitblit;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 import com.gitblit.transport.ssh.commands.SshCommand;
 
 @CommandMetaData(name = "ls", description = "List repositories or projects")
index dd2692133a16ab293db34db8a46df7e5b9ca9066..752afaf626126236100877b72a56d96b2281ad51 100644 (file)
@@ -24,7 +24,7 @@ import org.parboiled.common.StringUtils;
 
 import com.gitblit.manager.IGitblit;
 import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 import com.gitblit.transport.ssh.commands.SshCommand;
 
 @CommandMetaData(name = "ls-users", description = "List users", admin = true)
index 7c9abfd44693d50939e23aba7fdae0d6982d6b76..af3073033b9d7ebf53357460ab7f5ae69a502eb1 100644 (file)
@@ -23,7 +23,7 @@ import org.kohsuke.args4j.Argument;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 
 /**
index 9e4d8ba7098e45cd65f1dbd1f7d1aca8f70b32fe..b3691cbb6d37d1675351ec992641debfed374512 100644 (file)
@@ -25,7 +25,7 @@ import com.gitblit.models.TicketModel.Change;
 import com.gitblit.models.TicketModel.Patchset;
 import com.gitblit.models.TicketModel.Score;
 import com.gitblit.models.UserModel;
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 import com.gitblit.transport.ssh.commands.SshCommand;
 import com.gitblit.wicket.GitBlitWebSession;
 
index 28ac9e19ff64296cc73f493cb1a726d35a9cbda9..aebe3b1fee28c019a270da98568268423facc6c7 100644 (file)
@@ -22,7 +22,7 @@ import java.util.List;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 
 /** Set a user's account settings. **/
 @CommandMetaData(name = "set-account", description = "Change an account's settings", admin = true)
index 513f6d96a576f1be1bdf7d546ca5522896a84c77..384c6ce45497efbb537e5191beea2ee964c02077 100644 (file)
@@ -17,7 +17,7 @@
 package com.gitblit.transport.ssh.gitblit;
 
 import com.gitblit.Constants;
-import com.gitblit.transport.ssh.CommandMetaData;
+import com.gitblit.transport.ssh.commands.CommandMetaData;
 import com.gitblit.transport.ssh.commands.SshCommand;
 
 @CommandMetaData(name="version", description = "Display the Gitblit version")