Perform per-request cleanup actions at the end of a request
In both HTTP and SSH requests we now perform a custom list of cleanup actions at the end of the request. This permits a request scoped provider to register a cleanup action for when the request is over, like to close a database connection or a JGit repository handle. Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
56
src/main/java/com/google/gerrit/server/RequestCleanup.java
Normal file
56
src/main/java/com/google/gerrit/server/RequestCleanup.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (C) 2009 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.google.gerrit.server;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.RequestScoped;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers cleanup activities to be completed when a scope ends.
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
public class RequestCleanup implements Runnable {
|
||||||
|
private static final Logger log =
|
||||||
|
LoggerFactory.getLogger(RequestCleanup.class);
|
||||||
|
|
||||||
|
private final List<Runnable> cleanup = new LinkedList<Runnable>();
|
||||||
|
|
||||||
|
/** Register a task to be completed after the request ends. */
|
||||||
|
public void add(final Runnable task) {
|
||||||
|
synchronized (cleanup) {
|
||||||
|
cleanup.add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
synchronized (cleanup) {
|
||||||
|
for (final Iterator<Runnable> i = cleanup.iterator(); i.hasNext();) {
|
||||||
|
try {
|
||||||
|
i.next().run();
|
||||||
|
} catch (Throwable err) {
|
||||||
|
log.error("Failed to execute per-request cleanup", err);
|
||||||
|
}
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (C) 2009 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.google.gerrit.server.config;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.server.RequestCleanup;
|
||||||
|
import com.google.inject.servlet.RequestScoped;
|
||||||
|
|
||||||
|
/** Bindings for {@link RequestScoped} entities. */
|
||||||
|
public class GerritRequestModule extends FactoryModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(RequestCleanup.class).in(RequestScoped.class);
|
||||||
|
bind(ReviewDb.class).toProvider(RequestScopedReviewDbProvider.class);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (C) 2009 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.google.gerrit.server.config;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.server.RequestCleanup;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
import com.google.gwtorm.jdbc.Database;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.ProvisionException;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
/** Provides {@link ReviewDb} database handle live only for this request. */
|
||||||
|
@Singleton
|
||||||
|
final class RequestScopedReviewDbProvider implements Provider<ReviewDb> {
|
||||||
|
private final Database<ReviewDb> schema;
|
||||||
|
private final Provider<RequestCleanup> cleanup;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RequestScopedReviewDbProvider(final Database<ReviewDb> schema,
|
||||||
|
final Provider<RequestCleanup> cleanup) {
|
||||||
|
this.schema = schema;
|
||||||
|
this.cleanup = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReviewDb get() {
|
||||||
|
final ReviewDb c;
|
||||||
|
try {
|
||||||
|
c = schema.open();
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new ProvisionException("Cannot open ReviewDb", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cleanup.get().add(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return c;
|
||||||
|
} catch (Error e) {
|
||||||
|
c.close();
|
||||||
|
throw e;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
c.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (C) 2009 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.google.gerrit.server.http;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.RequestCleanup;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
|
||||||
|
/** Executes any pending {@link RequestCleanup} at the end of a request. */
|
||||||
|
@Singleton
|
||||||
|
class RequestCleanupFilter implements Filter {
|
||||||
|
private final Provider<RequestCleanup> cleanup;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RequestCleanupFilter(final Provider<RequestCleanup> r) {
|
||||||
|
cleanup = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(final ServletRequest request,
|
||||||
|
final ServletResponse response, final FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
try {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} finally {
|
||||||
|
cleanup.get().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -44,7 +44,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
|
|
||||||
/** Rewrites Gerrit 1 style URLs to Gerrit 2 style URLs. */
|
/** Rewrites Gerrit 1 style URLs to Gerrit 2 style URLs. */
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UrlRewriteFilter implements Filter {
|
class UrlRewriteFilter implements Filter {
|
||||||
private static final Pattern CHANGE_ID = Pattern.compile("^/(\\d+)/?$");
|
private static final Pattern CHANGE_ID = Pattern.compile("^/(\\d+)/?$");
|
||||||
private static final Pattern REV_ID =
|
private static final Pattern REV_ID =
|
||||||
Pattern.compile("^/r/([0-9a-fA-F]{4," + RevId.LEN + "})/?$");
|
Pattern.compile("^/r/([0-9a-fA-F]{4," + RevId.LEN + "})/?$");
|
||||||
|
@@ -21,6 +21,7 @@ import com.google.gerrit.server.CurrentUser;
|
|||||||
import com.google.gerrit.server.RemotePeer;
|
import com.google.gerrit.server.RemotePeer;
|
||||||
import com.google.gerrit.server.config.FactoryModule;
|
import com.google.gerrit.server.config.FactoryModule;
|
||||||
import com.google.gerrit.server.config.GerritConfigProvider;
|
import com.google.gerrit.server.config.GerritConfigProvider;
|
||||||
|
import com.google.gerrit.server.config.GerritRequestModule;
|
||||||
import com.google.gerrit.server.rpc.UiRpcModule;
|
import com.google.gerrit.server.rpc.UiRpcModule;
|
||||||
import com.google.gerrit.server.ssh.SshInfo;
|
import com.google.gerrit.server.ssh.SshInfo;
|
||||||
import com.google.gwtexpui.server.CacheControlFilter;
|
import com.google.gwtexpui.server.CacheControlFilter;
|
||||||
@@ -45,6 +46,7 @@ class WebModule extends FactoryModule {
|
|||||||
install(new ServletModule() {
|
install(new ServletModule() {
|
||||||
@Override
|
@Override
|
||||||
protected void configureServlets() {
|
protected void configureServlets() {
|
||||||
|
filter("/*").through(RequestCleanupFilter.class);
|
||||||
filter("/*").through(UrlRewriteFilter.class);
|
filter("/*").through(UrlRewriteFilter.class);
|
||||||
|
|
||||||
filter("/*").through(Key.get(CacheControlFilter.class));
|
filter("/*").through(Key.get(CacheControlFilter.class));
|
||||||
@@ -59,6 +61,7 @@ class WebModule extends FactoryModule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
install(new UiRpcModule());
|
install(new UiRpcModule());
|
||||||
|
install(new GerritRequestModule());
|
||||||
|
|
||||||
bind(SshInfo.class).toProvider(sshInfoProvider);
|
bind(SshInfo.class).toProvider(sshInfoProvider);
|
||||||
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
|
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
|
||||||
|
@@ -16,12 +16,13 @@ package com.google.gerrit.server.ssh;
|
|||||||
|
|
||||||
import com.google.gerrit.client.reviewdb.Account;
|
import com.google.gerrit.client.reviewdb.Account;
|
||||||
import com.google.gerrit.pgm.CmdLineParser;
|
import com.google.gerrit.pgm.CmdLineParser;
|
||||||
|
import com.google.gerrit.server.RequestCleanup;
|
||||||
import com.google.gerrit.server.ssh.SshScopes.Context;
|
import com.google.gerrit.server.ssh.SshScopes.Context;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.apache.sshd.common.SshException;
|
import org.apache.sshd.common.SshException;
|
||||||
import org.apache.sshd.server.CommandFactory.Command;
|
import org.apache.sshd.server.CommandFactory.Command;
|
||||||
import org.apache.sshd.server.CommandFactory.ExitCallback;
|
import org.apache.sshd.server.CommandFactory.ExitCallback;
|
||||||
import org.apache.sshd.server.CommandFactory.SessionAware;
|
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
import org.kohsuke.args4j.Argument;
|
import org.kohsuke.args4j.Argument;
|
||||||
import org.kohsuke.args4j.CmdLineException;
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
@@ -36,7 +37,7 @@ import java.io.StringWriter;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class BaseCommand implements Command, SessionAware {
|
public abstract class BaseCommand implements Command {
|
||||||
private static final Logger log = LoggerFactory.getLogger(BaseCommand.class);
|
private static final Logger log = LoggerFactory.getLogger(BaseCommand.class);
|
||||||
|
|
||||||
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
|
@Option(name = "--help", usage = "display this help text", aliases = {"-h"})
|
||||||
@@ -45,8 +46,11 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
protected InputStream in;
|
protected InputStream in;
|
||||||
protected OutputStream out;
|
protected OutputStream out;
|
||||||
protected OutputStream err;
|
protected OutputStream err;
|
||||||
protected ExitCallback exit;
|
|
||||||
protected ServerSession session;
|
private ExitCallback exit;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private RequestCleanup cleanup;
|
||||||
|
|
||||||
/** Text of the command line which lead up to invoking this instance. */
|
/** Text of the command line which lead up to invoking this instance. */
|
||||||
protected String commandPrefix = "";
|
protected String commandPrefix = "";
|
||||||
@@ -70,10 +74,6 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
this.exit = callback;
|
this.exit = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSession(final ServerSession session) {
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCommandPrefix(final String prefix) {
|
public void setCommandPrefix(final String prefix) {
|
||||||
this.commandPrefix = prefix;
|
this.commandPrefix = prefix;
|
||||||
}
|
}
|
||||||
@@ -101,9 +101,6 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
* @param cmd the command that will receive the current state.
|
* @param cmd the command that will receive the current state.
|
||||||
*/
|
*/
|
||||||
protected void provideStateTo(final Command cmd) {
|
protected void provideStateTo(final Command cmd) {
|
||||||
if (cmd instanceof SessionAware) {
|
|
||||||
((SessionAware) cmd).setSession(session);
|
|
||||||
}
|
|
||||||
cmd.setInputStream(in);
|
cmd.setInputStream(in);
|
||||||
cmd.setOutputStream(out);
|
cmd.setOutputStream(out);
|
||||||
cmd.setErrorStream(err);
|
cmd.setErrorStream(err);
|
||||||
@@ -222,15 +219,15 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
*/
|
*/
|
||||||
protected void startThread(final CommandRunnable thunk) {
|
protected void startThread(final CommandRunnable thunk) {
|
||||||
final Context context = SshScopes.getContext();
|
final Context context = SshScopes.getContext();
|
||||||
final List<Command> activeList = session.getAttribute(SshUtil.ACTIVE);
|
final List<Command> active = context.session.getAttribute(SshUtil.ACTIVE);
|
||||||
final Command cmd = this;
|
final Command cmd = this;
|
||||||
new Thread(threadName()) {
|
new Thread(threadName()) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
try {
|
try {
|
||||||
synchronized (activeList) {
|
synchronized (active) {
|
||||||
activeList.add(cmd);
|
active.add(cmd);
|
||||||
}
|
}
|
||||||
SshScopes.current.set(context);
|
SshScopes.current.set(context);
|
||||||
thunk.run();
|
thunk.run();
|
||||||
@@ -247,16 +244,31 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
}
|
}
|
||||||
rc = handleError(e);
|
rc = handleError(e);
|
||||||
} finally {
|
} finally {
|
||||||
synchronized (activeList) {
|
synchronized (active) {
|
||||||
activeList.remove(cmd);
|
active.remove(cmd);
|
||||||
}
|
}
|
||||||
exit.onExit(rc);
|
onExit(rc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate this command and return a result code to the remote client.
|
||||||
|
*<p>
|
||||||
|
* Commands should invoke this at most once. Once invoked, the command may
|
||||||
|
* lose access to request based resources as any callbacks previously
|
||||||
|
* registered with {@link RequestCleanup} will fire.
|
||||||
|
*
|
||||||
|
* @param rc exit code for the remote client.
|
||||||
|
*/
|
||||||
|
protected void onExit(final int rc) {
|
||||||
|
exit.onExit(rc);
|
||||||
|
cleanup.run();
|
||||||
|
}
|
||||||
|
|
||||||
private String threadName() {
|
private String threadName() {
|
||||||
|
final ServerSession session = SshScopes.getContext().session;
|
||||||
final String who = session.getUsername();
|
final String who = session.getUsername();
|
||||||
final Account.Id id = session.getAttribute(SshUtil.CURRENT_ACCOUNT);
|
final Account.Id id = session.getAttribute(SshUtil.CURRENT_ACCOUNT);
|
||||||
return "SSH " + getFullCommandLine() + " / " + who + " " + id;
|
return "SSH " + getFullCommandLine() + " / " + who + " " + id;
|
||||||
@@ -283,6 +295,7 @@ public abstract class BaseCommand implements Command, SessionAware {
|
|||||||
|
|
||||||
if (e instanceof UnloggedFailure) {
|
if (e instanceof UnloggedFailure) {
|
||||||
} else {
|
} else {
|
||||||
|
final ServerSession session = SshScopes.getContext().session;
|
||||||
final StringBuilder m = new StringBuilder();
|
final StringBuilder m = new StringBuilder();
|
||||||
m.append("Internal server error (");
|
m.append("Internal server error (");
|
||||||
m.append("user ");
|
m.append("user ");
|
||||||
|
@@ -14,32 +14,87 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.ssh;
|
package com.google.gerrit.server.ssh;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.ssh.SshScopes.Context;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
import org.apache.sshd.server.CommandFactory;
|
import org.apache.sshd.server.CommandFactory;
|
||||||
|
import org.apache.sshd.server.CommandFactory.Command;
|
||||||
|
import org.apache.sshd.server.CommandFactory.ExitCallback;
|
||||||
|
import org.apache.sshd.server.CommandFactory.SessionAware;
|
||||||
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a CommandFactory using commands registered by {@link CommandModule}.
|
* Creates a CommandFactory using commands registered by {@link CommandModule}.
|
||||||
*/
|
*/
|
||||||
class CommandFactoryProvider implements Provider<CommandFactory> {
|
class CommandFactoryProvider implements Provider<CommandFactory> {
|
||||||
private static final String SERVER = "Gerrit Code Review";
|
|
||||||
private final DispatchCommandProvider dispatcher;
|
private final DispatchCommandProvider dispatcher;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CommandFactoryProvider(final Injector i) {
|
CommandFactoryProvider(
|
||||||
dispatcher = new DispatchCommandProvider(i, SERVER, Commands.ROOT);
|
@CommandName(Commands.ROOT) final DispatchCommandProvider d) {
|
||||||
|
dispatcher = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandFactory get() {
|
public CommandFactory get() {
|
||||||
return new CommandFactory() {
|
return new CommandFactory() {
|
||||||
public Command createCommand(final String requestCommand) {
|
public Command createCommand(final String requestCommand) {
|
||||||
final DispatchCommand c = dispatcher.get();
|
return new Trampoline(requestCommand);
|
||||||
c.setCommandLine(requestCommand);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Trampoline implements Command, SessionAware {
|
||||||
|
private final String commandLine;
|
||||||
|
private InputStream in;
|
||||||
|
private OutputStream out;
|
||||||
|
private OutputStream err;
|
||||||
|
private ExitCallback exit;
|
||||||
|
private ServerSession session;
|
||||||
|
|
||||||
|
Trampoline(final String cmdLine) {
|
||||||
|
commandLine = cmdLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputStream(final InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputStream(final OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorStream(final OutputStream err) {
|
||||||
|
this.err = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExitCallback(final ExitCallback callback) {
|
||||||
|
this.exit = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSession(final ServerSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException {
|
||||||
|
final Context old = SshScopes.current.get();
|
||||||
|
try {
|
||||||
|
SshScopes.current.set(new Context(session));
|
||||||
|
final DispatchCommand c = dispatcher.get();
|
||||||
|
c.setCommandLine(commandLine);
|
||||||
|
c.setInputStream(in);
|
||||||
|
c.setOutputStream(out);
|
||||||
|
c.setErrorStream(err);
|
||||||
|
c.setExitCallback(exit);
|
||||||
|
c.start();
|
||||||
|
} finally {
|
||||||
|
SshScopes.current.set(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,10 @@ import java.lang.annotation.Annotation;
|
|||||||
/** Utilities to support {@link CommandName} construction. */
|
/** Utilities to support {@link CommandName} construction. */
|
||||||
public class Commands {
|
public class Commands {
|
||||||
/** Magic value signaling the top level. */
|
/** Magic value signaling the top level. */
|
||||||
public static final CommandName ROOT = named("");
|
public static final String ROOT = "";
|
||||||
|
|
||||||
|
/** Magic value signaling the top level. */
|
||||||
|
public static final CommandName CMD_ROOT = named(ROOT);
|
||||||
|
|
||||||
public static Key<CommandFactory.Command> key(final String name) {
|
public static Key<CommandFactory.Command> key(final String name) {
|
||||||
return key(named(name));
|
return key(named(name));
|
||||||
@@ -53,7 +56,8 @@ public class Commands {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return value().hashCode();
|
// This is specified in java.lang.Annotation.
|
||||||
|
return (127 * "value".hashCode()) ^ value().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,7 +68,7 @@ public class Commands {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CommandName[" + value() + "]";
|
return "@" + CommandName.class.getName() + "(value=" + value() + ")";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -87,7 +91,7 @@ public class Commands {
|
|||||||
if (name instanceof NestedCommandNameImpl) {
|
if (name instanceof NestedCommandNameImpl) {
|
||||||
return parent.equals(((NestedCommandNameImpl) name).parent);
|
return parent.equals(((NestedCommandNameImpl) name).parent);
|
||||||
}
|
}
|
||||||
if (parent == ROOT) {
|
if (parent == CMD_ROOT) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@@ -14,8 +14,9 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.ssh;
|
package com.google.gerrit.server.ssh;
|
||||||
|
|
||||||
import com.google.gerrit.server.ssh.SshScopes.Context;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
import org.apache.sshd.server.CommandFactory.Command;
|
import org.apache.sshd.server.CommandFactory.Command;
|
||||||
|
|
||||||
@@ -26,12 +27,17 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Command that dispatches to a subcommand from its command table.
|
* Command that dispatches to a subcommand from its command table.
|
||||||
*/
|
*/
|
||||||
public class DispatchCommand extends BaseCommand {
|
class DispatchCommand extends BaseCommand {
|
||||||
|
interface Factory {
|
||||||
|
DispatchCommand create(String prefix, Map<String, Provider<Command>> map);
|
||||||
|
}
|
||||||
|
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final Map<String, Provider<Command>> commands;
|
private final Map<String, Provider<Command>> commands;
|
||||||
|
|
||||||
public DispatchCommand(final String pfx,
|
@Inject
|
||||||
final Map<String, Provider<Command>> all) {
|
DispatchCommand(@Assisted final String pfx,
|
||||||
|
@Assisted final Map<String, Provider<Command>> all) {
|
||||||
prefix = pfx;
|
prefix = pfx;
|
||||||
commands = all;
|
commands = all;
|
||||||
}
|
}
|
||||||
@@ -58,30 +64,22 @@ public class DispatchCommand extends BaseCommand {
|
|||||||
|
|
||||||
final Provider<Command> p = commands.get(name);
|
final Provider<Command> p = commands.get(name);
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
final Context old = SshScopes.current.get();
|
final Command cmd = p.get();
|
||||||
try {
|
provideStateTo(cmd);
|
||||||
if (old == null) {
|
if (cmd instanceof BaseCommand) {
|
||||||
SshScopes.current.set(new Context(session));
|
final BaseCommand bc = (BaseCommand) cmd;
|
||||||
}
|
if (commandPrefix.isEmpty())
|
||||||
final Command cmd = p.get();
|
bc.setCommandPrefix(name);
|
||||||
provideStateTo(cmd);
|
else
|
||||||
if (cmd instanceof BaseCommand) {
|
bc.setCommandPrefix(commandPrefix + " " + name);
|
||||||
final BaseCommand bc = (BaseCommand) cmd;
|
bc.setCommandLine(args);
|
||||||
if (commandPrefix.isEmpty())
|
|
||||||
bc.setCommandPrefix(name);
|
|
||||||
else
|
|
||||||
bc.setCommandPrefix(commandPrefix + " " + name);
|
|
||||||
bc.setCommandLine(args);
|
|
||||||
}
|
|
||||||
cmd.start();
|
|
||||||
} finally {
|
|
||||||
SshScopes.current.set(old);
|
|
||||||
}
|
}
|
||||||
|
cmd.start();
|
||||||
} else {
|
} else {
|
||||||
final String msg = prefix + ": " + name + ": not found\n";
|
final String msg = prefix + ": " + name + ": not found\n";
|
||||||
err.write(msg.getBytes("UTF-8"));
|
err.write(msg.getBytes("UTF-8"));
|
||||||
err.flush();
|
err.flush();
|
||||||
exit.onExit(127);
|
onExit(127);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +96,6 @@ public class DispatchCommand extends BaseCommand {
|
|||||||
usage.append("\n");
|
usage.append("\n");
|
||||||
err.write(usage.toString().getBytes("UTF-8"));
|
err.write(usage.toString().getBytes("UTF-8"));
|
||||||
err.flush();
|
err.flush();
|
||||||
exit.onExit(1);
|
onExit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,6 +36,9 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
@Inject
|
@Inject
|
||||||
private Injector injector;
|
private Injector injector;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DispatchCommand.Factory factory;
|
||||||
|
|
||||||
private final String dispatcherName;
|
private final String dispatcherName;
|
||||||
private final CommandName parent;
|
private final CommandName parent;
|
||||||
|
|
||||||
@@ -51,16 +54,9 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
this.parent = cn;
|
this.parent = cn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DispatchCommandProvider(final Injector i, final String dispatcherName,
|
|
||||||
final CommandName cn) {
|
|
||||||
this.injector = i;
|
|
||||||
this.dispatcherName = dispatcherName;
|
|
||||||
this.parent = cn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DispatchCommand get() {
|
public DispatchCommand get() {
|
||||||
return new DispatchCommand(dispatcherName, getMap());
|
return factory.create(dispatcherName, getMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Provider<Command>> getMap() {
|
private Map<String, Provider<Command>> getMap() {
|
||||||
@@ -83,7 +79,7 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
final Annotation annotation = b.getKey().getAnnotation();
|
final Annotation annotation = b.getKey().getAnnotation();
|
||||||
if (annotation instanceof CommandName) {
|
if (annotation instanceof CommandName) {
|
||||||
final CommandName n = (CommandName) annotation;
|
final CommandName n = (CommandName) annotation;
|
||||||
if (Commands.isChild(parent, n)) {
|
if (!Commands.CMD_ROOT.equals(n) && Commands.isChild(parent, n)) {
|
||||||
m.put(n.value(), (Provider<Command>) b.getProvider());
|
m.put(n.value(), (Provider<Command>) b.getProvider());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,10 @@ import static com.google.inject.Scopes.SINGLETON;
|
|||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.RemotePeer;
|
import com.google.gerrit.server.RemotePeer;
|
||||||
|
import com.google.gerrit.server.RequestCleanup;
|
||||||
|
import com.google.gerrit.server.config.FactoryModule;
|
||||||
|
import com.google.gerrit.server.config.GerritRequestModule;
|
||||||
import com.google.gerrit.server.ssh.commands.DefaultCommandModule;
|
import com.google.gerrit.server.ssh.commands.DefaultCommandModule;
|
||||||
import com.google.inject.AbstractModule;
|
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.servlet.RequestScoped;
|
import com.google.inject.servlet.RequestScoped;
|
||||||
import com.google.inject.servlet.SessionScoped;
|
import com.google.inject.servlet.SessionScoped;
|
||||||
@@ -29,14 +31,12 @@ import org.apache.sshd.common.session.AbstractSession;
|
|||||||
import org.apache.sshd.server.CommandFactory;
|
import org.apache.sshd.server.CommandFactory;
|
||||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
/** Configures standard dependencies for {@link SshDaemon}. */
|
/** Configures standard dependencies for {@link SshDaemon}. */
|
||||||
public class SshDaemonModule extends AbstractModule {
|
public class SshDaemonModule extends FactoryModule {
|
||||||
static final Logger log = LoggerFactory.getLogger(SshDaemonModule.class);
|
private static final String NAME = "Gerrit Code Review";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
@@ -47,8 +47,13 @@ public class SshDaemonModule extends AbstractModule {
|
|||||||
configureRequestScope();
|
configureRequestScope();
|
||||||
|
|
||||||
bind(SshInfo.class).to(SshDaemon.class).in(SINGLETON);
|
bind(SshInfo.class).to(SshDaemon.class).in(SINGLETON);
|
||||||
|
factory(DispatchCommand.Factory.class);
|
||||||
|
|
||||||
|
bind(DispatchCommandProvider.class).annotatedWith(Commands.CMD_ROOT)
|
||||||
|
.toInstance(new DispatchCommandProvider(NAME, Commands.CMD_ROOT));
|
||||||
bind(CommandFactoryProvider.class);
|
bind(CommandFactoryProvider.class);
|
||||||
bind(CommandFactory.class).toProvider(CommandFactoryProvider.class);
|
bind(CommandFactory.class).toProvider(CommandFactoryProvider.class);
|
||||||
|
|
||||||
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
|
bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
|
||||||
|
|
||||||
install(new DefaultCommandModule());
|
install(new DefaultCommandModule());
|
||||||
@@ -74,6 +79,7 @@ public class SshDaemonModule extends AbstractModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configureRequestScope() {
|
private void configureRequestScope() {
|
||||||
|
install(new GerritRequestModule());
|
||||||
bind(IdentifiedUser.class).toProvider(SshCurrentUserProvider.class).in(
|
bind(IdentifiedUser.class).toProvider(SshCurrentUserProvider.class).in(
|
||||||
SshScopes.REQUEST);
|
SshScopes.REQUEST);
|
||||||
bind(CurrentUser.class).to(IdentifiedUser.class);
|
bind(CurrentUser.class).to(IdentifiedUser.class);
|
||||||
|
Reference in New Issue
Block a user