|  | // 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.sshd; | 
|  |  | 
|  | import com.google.common.collect.Maps; | 
|  | import com.google.gerrit.reviewdb.server.ReviewDb; | 
|  | import com.google.gerrit.server.CurrentUser; | 
|  | import com.google.gerrit.server.IdentifiedUser; | 
|  | import com.google.gerrit.server.RequestCleanup; | 
|  | import com.google.gerrit.server.config.RequestScopedReviewDbProvider; | 
|  | import com.google.gerrit.server.util.RequestContext; | 
|  | import com.google.gerrit.server.util.ThreadLocalRequestContext; | 
|  | import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator; | 
|  | import com.google.gwtorm.server.SchemaFactory; | 
|  | import com.google.inject.Inject; | 
|  | import com.google.inject.Key; | 
|  | import com.google.inject.OutOfScopeException; | 
|  | import com.google.inject.Provider; | 
|  | import com.google.inject.Scope; | 
|  | import com.google.inject.util.Providers; | 
|  |  | 
|  | import java.util.Map; | 
|  |  | 
|  | /** Guice scopes for state during an SSH connection. */ | 
|  | class SshScope { | 
|  | private static final Key<RequestCleanup> RC_KEY = | 
|  | Key.get(RequestCleanup.class); | 
|  |  | 
|  | private static final Key<RequestScopedReviewDbProvider> DB_KEY = | 
|  | Key.get(RequestScopedReviewDbProvider.class); | 
|  |  | 
|  | class Context implements RequestContext { | 
|  | private final RequestCleanup cleanup = new RequestCleanup(); | 
|  | private final Map<Key<?>, Object> map = Maps.newHashMap(); | 
|  | private final SchemaFactory<ReviewDb> schemaFactory; | 
|  | private final SshSession session; | 
|  | private final String commandLine; | 
|  |  | 
|  | final long created; | 
|  | volatile long started; | 
|  | volatile long finished; | 
|  |  | 
|  | private Context(SchemaFactory<ReviewDb> sf, final SshSession s, | 
|  | final String c, final long at) { | 
|  | schemaFactory = sf; | 
|  | session = s; | 
|  | commandLine = c; | 
|  | created = started = finished = at; | 
|  | map.put(RC_KEY, cleanup); | 
|  | map.put(DB_KEY, new RequestScopedReviewDbProvider( | 
|  | schemaFactory, | 
|  | Providers.of(cleanup))); | 
|  | } | 
|  |  | 
|  | private Context(Context p, SshSession s, String c) { | 
|  | this(p.schemaFactory, s, c, p.created); | 
|  | started = p.started; | 
|  | finished = p.finished; | 
|  | } | 
|  |  | 
|  | String getCommandLine() { | 
|  | return commandLine; | 
|  | } | 
|  |  | 
|  | SshSession getSession() { | 
|  | return session; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CurrentUser getCurrentUser() { | 
|  | final CurrentUser user = session.getCurrentUser(); | 
|  | if (user instanceof IdentifiedUser) { | 
|  | return userFactory.create(((IdentifiedUser) user).getAccountId()); | 
|  | } | 
|  | return user; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Provider<ReviewDb> getReviewDbProvider() { | 
|  | return (RequestScopedReviewDbProvider) map.get(DB_KEY); | 
|  | } | 
|  |  | 
|  | synchronized <T> T get(Key<T> key, Provider<T> creator) { | 
|  | @SuppressWarnings("unchecked") | 
|  | T t = (T) map.get(key); | 
|  | if (t == null) { | 
|  | t = creator.get(); | 
|  | map.put(key, t); | 
|  | } | 
|  | return t; | 
|  | } | 
|  |  | 
|  | synchronized Context subContext(SshSession newSession, String newCommandLine) { | 
|  | Context ctx = new Context(this, newSession, newCommandLine); | 
|  | cleanup.add(ctx.cleanup); | 
|  | return ctx; | 
|  | } | 
|  | } | 
|  |  | 
|  | static class ContextProvider implements Provider<Context> { | 
|  | @Override | 
|  | public Context get() { | 
|  | return requireContext(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static class SshSessionProvider implements Provider<SshSession> { | 
|  | @Override | 
|  | public SshSession get() { | 
|  | return requireContext().getSession(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static class Propagator extends ThreadLocalRequestScopePropagator<Context> { | 
|  | private final SshScope sshScope; | 
|  |  | 
|  | @Inject | 
|  | Propagator(SshScope sshScope, ThreadLocalRequestContext local, | 
|  | Provider<RequestScopedReviewDbProvider> dbProviderProvider) { | 
|  | super(REQUEST, current, local, dbProviderProvider); | 
|  | this.sshScope = sshScope; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected Context continuingContext(Context ctx) { | 
|  | // The cleanup is not chained, since the RequestScopePropagator executors | 
|  | // the Context's cleanup when finished executing. | 
|  | return sshScope.newContinuingContext(ctx); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final ThreadLocal<Context> current = | 
|  | new ThreadLocal<Context>(); | 
|  |  | 
|  | private static Context requireContext() { | 
|  | final Context ctx = current.get(); | 
|  | if (ctx == null) { | 
|  | throw new OutOfScopeException("Not in command/request"); | 
|  | } | 
|  | return ctx; | 
|  | } | 
|  |  | 
|  | private final ThreadLocalRequestContext local; | 
|  | private final IdentifiedUser.RequestFactory userFactory; | 
|  |  | 
|  | @Inject | 
|  | SshScope(ThreadLocalRequestContext local, | 
|  | IdentifiedUser.RequestFactory userFactory) { | 
|  | this.local = local; | 
|  | this.userFactory = userFactory; | 
|  | } | 
|  |  | 
|  | Context newContext(SchemaFactory<ReviewDb> sf, SshSession s, String cmd) { | 
|  | return new Context(sf, s, cmd, System.currentTimeMillis()); | 
|  | } | 
|  |  | 
|  | private Context newContinuingContext(Context ctx) { | 
|  | return new Context(ctx, ctx.getSession(), ctx.getCommandLine()); | 
|  | } | 
|  |  | 
|  | Context set(Context ctx) { | 
|  | Context old = current.get(); | 
|  | current.set(ctx); | 
|  | local.setContext(ctx); | 
|  | return old; | 
|  | } | 
|  |  | 
|  | /** Returns exactly one instance per command executed. */ | 
|  | static final Scope REQUEST = new Scope() { | 
|  | public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) { | 
|  | return new Provider<T>() { | 
|  | public T get() { | 
|  | return requireContext().get(key, creator); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return String.format("%s[%s]", creator, REQUEST); | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return "SshScopes.REQUEST"; | 
|  | } | 
|  | }; | 
|  | } |