Dissolve gerrit-server top-level directory

Change-Id: I538512dfe0f1bea774c01fdd45fa410a45634011
This commit is contained in:
David Ostrovsky
2017-09-21 08:37:42 +02:00
committed by Dave Borowitz
parent 472396c797
commit 376a7bbb64
1549 changed files with 342 additions and 335 deletions

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2017 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.util;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.BadRequestException;
/** Utility functions to manipulate commit messages. */
public class CommitMessageUtil {
private CommitMessageUtil() {}
/**
* Checks for null or empty commit messages and appends a newline character to the commit message.
*
* @throws BadRequestException if the commit message is null or empty
* @returns the trimmed message with a trailing newline character
*/
public static String checkAndSanitizeCommitMessage(String commitMessage)
throws BadRequestException {
String wellFormedMessage = Strings.nullToEmpty(commitMessage).trim();
if (wellFormedMessage.isEmpty()) {
throw new BadRequestException("Commit message cannot be null or empty");
}
wellFormedMessage = wellFormedMessage + "\n";
return wellFormedMessage;
}
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2012 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.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
/**
* The default RequestContext to use when not in a request scope e.g. ThreadLocalRequestContext is
* not set.
*/
@Singleton
public class FallbackRequestContext implements RequestContext {
private final AnonymousUser user;
@Inject
FallbackRequestContext(AnonymousUser user) {
this.user = user;
}
@Override
public CurrentUser getUser() {
return user;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return new Provider<ReviewDb>() {
@Override
public ReviewDb get() {
throw new ProvisionException("Automatic ReviewDb only available in request scope");
}
};
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (C) 2012 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.util;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.servlet.ServletScopes;
import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
/** Propagator for Guice's built-in servlet scope. */
public class GuiceRequestScopePropagator extends RequestScopePropagator {
private final String url;
private final SocketAddress peer;
@Inject
GuiceRequestScopePropagator(
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
@RemotePeer Provider<SocketAddress> remotePeerProvider,
ThreadLocalRequestContext local,
Provider<RequestScopedReviewDbProvider> dbProviderProvider) {
super(ServletScopes.REQUEST, local, dbProviderProvider);
this.url = urlProvider != null ? urlProvider.get() : null;
this.peer = remotePeerProvider.get();
}
/** @see RequestScopePropagator#wrap(Callable) */
// ServletScopes#continueRequest is deprecated, but it's not obvious their
// recommended replacement is an appropriate drop-in solution; see
// https://gerrit-review.googlesource.com/83971
@SuppressWarnings("deprecation")
@Override
protected <T> Callable<T> wrapImpl(Callable<T> callable) {
Map<Key<?>, Object> seedMap = new HashMap<>();
// Request scopes appear to use specific keys in their map, instead of only
// providers. Add bindings for both the key to the instance directly and the
// provider to the instance to be safe.
seedMap.put(Key.get(typeOfProvider(String.class), CanonicalWebUrl.class), Providers.of(url));
seedMap.put(Key.get(String.class, CanonicalWebUrl.class), url);
seedMap.put(Key.get(typeOfProvider(SocketAddress.class), RemotePeer.class), Providers.of(peer));
seedMap.put(Key.get(SocketAddress.class, RemotePeer.class), peer);
return ServletScopes.continueRequest(callable, seedMap);
}
private ParameterizedType typeOfProvider(Type type) {
return Types.newParameterizedType(Provider.class, type);
}
}

View File

@@ -0,0 +1,46 @@
// 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.util;
import java.security.AccessController;
import java.security.PrivilegedAction;
public final class HostPlatform {
private static final boolean win32 = compute("windows");
private static final boolean mac = compute("mac");
/** @return true if this JVM is running on a Windows platform. */
public static boolean isWin32() {
return win32;
}
public static boolean isMac() {
return mac;
}
private static boolean compute(String platform) {
final String osDotName =
AccessController.doPrivileged(
new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty("os.name");
}
});
return osDotName != null && osDotName.toLowerCase().contains(platform);
}
private HostPlatform() {}
}

View File

@@ -0,0 +1,99 @@
// 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.util;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
/** Simple class to produce 4 billion keys randomly distributed. */
@Singleton
public class IdGenerator {
/** Format an id created by this class as a hex string. */
public static String format(int id) {
final char[] r = new char[8];
for (int p = 7; 0 <= p; p--) {
final int h = id & 0xf;
r[p] = h < 10 ? (char) ('0' + h) : (char) ('a' + (h - 10));
id >>= 4;
}
return new String(r);
}
private final AtomicInteger gen;
@Inject
IdGenerator() {
gen = new AtomicInteger(new Random().nextInt());
}
/** Produce the next identifier. */
public int next() {
return mix(gen.getAndIncrement());
}
private static final int salt = 0x9e3779b9;
static int mix(int in) {
return mix(salt, in);
}
/** A very simple bit permutation to mask a simple incrementer. */
public static int mix(int salt, int in) {
short v0 = hi16(in);
short v1 = lo16(in);
v0 += ((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1;
v1 += ((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3;
return result(v0, v1);
}
/* For testing only. */
static int unmix(int in) {
short v0 = hi16(in);
short v1 = lo16(in);
v1 -= ((v0 << 2) + 2 ^ v0) + (salt ^ (v0 >>> 3)) + 3;
v0 -= ((v1 << 2) + 0 ^ v1) + (salt ^ (v1 >>> 3)) + 1;
return result(v0, v1);
}
private static short hi16(int in) {
return (short)
( //
((in >>> 24 & 0xff))
| //
((in >>> 16 & 0xff) << 8) //
);
}
private static short lo16(int in) {
return (short)
( //
((in >>> 8 & 0xff))
| //
((in & 0xff) << 8) //
);
}
private static int result(short v0, short v1) {
return ((v0 & 0xff) << 24)
| //
(((v0 >>> 8) & 0xff) << 16)
| //
((v1 & 0xff) << 8)
| //
((v1 >>> 8) & 0xff);
}
}

View File

@@ -0,0 +1,91 @@
// 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.google.gerrit.server.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.LabelType;
/** A single vote on a label, consisting of a label name and a value. */
@AutoValue
public abstract class LabelVote {
public static LabelVote parse(String text) {
checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
if (text.charAt(0) == '-') {
return create(text.substring(1), (short) 0);
}
short sign = 0;
int i;
for (i = text.length() - 1; i >= 0; i--) {
int c = text.charAt(i);
if (c == '-') {
sign = (short) -1;
break;
} else if (c == '+') {
sign = (short) 1;
break;
} else if (!('0' <= c && c <= '9')) {
break;
}
}
if (sign == 0) {
return create(text, (short) 1);
}
return create(text.substring(0, i), (short) (sign * Short.parseShort(text.substring(i + 1))));
}
public static LabelVote parseWithEquals(String text) {
checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
int e = text.lastIndexOf('=');
checkArgument(e >= 0, "Label vote missing '=': %s", text);
return create(text.substring(0, e), Short.parseShort(text.substring(e + 1)));
}
public static StringBuilder appendTo(StringBuilder sb, String label, short value) {
if (value == (short) 0) {
return sb.append('-').append(label);
} else if (value < 0) {
return sb.append(label).append(value);
}
return sb.append(label).append('+').append(value);
}
public static LabelVote create(String label, short value) {
return new AutoValue_LabelVote(LabelType.checkNameInternal(label), value);
}
public abstract String label();
public abstract short value();
public String format() {
// Max short string length is "-32768".length() == 6.
return appendTo(new StringBuilder(label().length() + 6), label(), value()).toString();
}
public String formatWithEquals() {
if (value() <= (short) 0) {
return label() + '=' + value();
}
return label() + "=+" + value();
}
@Override
public String toString() {
return format();
}
}

View File

@@ -0,0 +1,115 @@
// Copyright (C) 2011 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.util;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.reviewdb.client.Project;
import java.io.IOException;
import java.util.Map;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class MagicBranch {
private static final Logger log = LoggerFactory.getLogger(MagicBranch.class);
public static final String NEW_CHANGE = "refs/for/";
// TODO(xchangcheng): remove after 'repo' supports private/wip changes.
public static final String NEW_DRAFT_CHANGE = "refs/drafts/";
// TODO(xchangcheng): remove after migrating tools which are using this magic branch.
public static final String NEW_PUBLISH_CHANGE = "refs/publish/";
/** Extracts the destination from a ref name */
public static String getDestBranchName(String refName) {
String magicBranch = NEW_CHANGE;
if (refName.startsWith(NEW_DRAFT_CHANGE)) {
magicBranch = NEW_DRAFT_CHANGE;
} else if (refName.startsWith(NEW_PUBLISH_CHANGE)) {
magicBranch = NEW_PUBLISH_CHANGE;
}
return refName.substring(magicBranch.length());
}
/** Checks if the supplied ref name is a magic branch */
public static boolean isMagicBranch(String refName) {
return refName.startsWith(NEW_DRAFT_CHANGE)
|| refName.startsWith(NEW_PUBLISH_CHANGE)
|| refName.startsWith(NEW_CHANGE);
}
/** Returns the ref name prefix for a magic branch, {@code null} if the branch is not magic */
public static String getMagicRefNamePrefix(String refName) {
if (refName.startsWith(NEW_DRAFT_CHANGE)) {
return NEW_DRAFT_CHANGE;
}
if (refName.startsWith(NEW_PUBLISH_CHANGE)) {
return NEW_PUBLISH_CHANGE;
}
if (refName.startsWith(NEW_CHANGE)) {
return NEW_CHANGE;
}
return null;
}
/**
* Checks if a (magic branch)/branch_name reference exists in the destination repository and only
* returns Capable.OK if it does not match any.
*
* <p>These block the client from being able to even send us a pack file, as it is very unlikely
* the user passed the --force flag and the new commit is probably not going to fast-forward the
* branch.
*/
public static Capable checkMagicBranchRefs(Repository repo, Project project) {
Capable result = checkMagicBranchRef(NEW_CHANGE, repo, project);
if (result != Capable.OK) {
return result;
}
result = checkMagicBranchRef(NEW_DRAFT_CHANGE, repo, project);
if (result != Capable.OK) {
return result;
}
result = checkMagicBranchRef(NEW_PUBLISH_CHANGE, repo, project);
if (result != Capable.OK) {
return result;
}
return Capable.OK;
}
private static Capable checkMagicBranchRef(String branchName, Repository repo, Project project) {
Map<String, Ref> blockingFors;
try {
blockingFors = repo.getRefDatabase().getRefs(branchName);
} catch (IOException err) {
String projName = project.getName();
log.warn("Cannot scan refs in '" + projName + "'", err);
return new Capable("Server process cannot read '" + projName + "'");
}
if (!blockingFors.isEmpty()) {
String projName = project.getName();
log.error(
"Repository '"
+ projName
+ "' needs the following refs removed to receive changes: "
+ blockingFors.keySet());
return new Capable("One or more " + branchName + " names blocks change upload");
}
return Capable.OK;
}
private MagicBranch() {}
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2015 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.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Provider;
import com.google.inject.util.Providers;
/** Closeable version of a {@link RequestContext} with manually-specified providers. */
public class ManualRequestContext implements RequestContext, AutoCloseable {
private final Provider<CurrentUser> userProvider;
private final Provider<ReviewDb> db;
private final ThreadLocalRequestContext requestContext;
private final RequestContext old;
public ManualRequestContext(
CurrentUser user,
SchemaFactory<ReviewDb> schemaFactory,
ThreadLocalRequestContext requestContext)
throws OrmException {
this(Providers.of(user), schemaFactory, requestContext);
}
public ManualRequestContext(
Provider<CurrentUser> userProvider,
SchemaFactory<ReviewDb> schemaFactory,
ThreadLocalRequestContext requestContext)
throws OrmException {
this.userProvider = userProvider;
this.db = Providers.of(schemaFactory.open());
this.requestContext = requestContext;
old = requestContext.setContext(this);
}
@Override
public CurrentUser getUser() {
return userProvider.get();
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return db;
}
@Override
public void close() {
requestContext.setContext(old);
db.get().close();
}
}

View File

@@ -0,0 +1,118 @@
// Copyright (C) 2011 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.util;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.server.project.RefPattern;
import java.util.Comparator;
import org.apache.commons.lang.StringUtils;
/**
* Order the Ref Pattern by the most specific. This sort is done by:
*
* <ul>
* <li>1 - The minor value of Levenshtein string distance between the branch name and the regex
* string shortest example. A shorter distance is a more specific match.
* <li>2 - Finites first, infinities after.
* <li>3 - Number of transitions. More transitions is more specific.
* <li>4 - Length of the expression text.
* </ul>
*
* Levenshtein distance is a measure of the similarity between two strings. The distance is the
* number of deletions, insertions, or substitutions required to transform one string into another.
*
* <p>For example, if given refs/heads/m* and refs/heads/*, the distances are 5 and 6. It means that
* refs/heads/m* is more specific because it's closer to refs/heads/master than refs/heads/*.
*
* <p>Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the distances are both 6. Both
* are infinite, but refs/heads/[a-zA-Z]* has more transitions, which after all turns it more
* specific.
*/
public final class MostSpecificComparator implements Comparator<RefConfigSection> {
private final String refName;
public MostSpecificComparator(String refName) {
this.refName = refName;
}
@Override
public int compare(RefConfigSection a, RefConfigSection b) {
return compare(a.getName(), b.getName());
}
public int compare(String pattern1, String pattern2) {
int cmp = distance(pattern1) - distance(pattern2);
if (cmp == 0) {
boolean p1_finite = finite(pattern1);
boolean p2_finite = finite(pattern2);
if (p1_finite && !p2_finite) {
cmp = -1;
} else if (!p1_finite && p2_finite) {
cmp = 1;
} else /* if (f1 == f2) */ {
cmp = 0;
}
}
if (cmp == 0) {
cmp = transitions(pattern2) - transitions(pattern1);
}
if (cmp == 0) {
cmp = pattern2.length() - pattern1.length();
}
return cmp;
}
private int distance(String pattern) {
String example;
if (RefPattern.isRE(pattern)) {
example = RefPattern.shortestExample(pattern);
} else if (pattern.endsWith("/*")) {
example = pattern;
} else if (pattern.equals(refName)) {
return 0;
} else {
return Math.max(pattern.length(), refName.length());
}
return StringUtils.getLevenshteinDistance(example, refName);
}
private boolean finite(String pattern) {
if (RefPattern.isRE(pattern)) {
return RefPattern.toRegExp(pattern).toAutomaton().isFinite();
} else if (pattern.endsWith("/*")) {
return false;
} else {
return true;
}
}
private int transitions(String pattern) {
if (RefPattern.isRE(pattern)) {
return RefPattern.toRegExp(pattern).toAutomaton().getNumberOfTransitions();
} else if (pattern.endsWith("/*")) {
return pattern.length();
} else {
return pattern.length();
}
}
}

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2015 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.util;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Helper to create one-off request contexts.
*
* <p>Each call to {@link #open()} opens a new {@link ReviewDb}, so this class should only be used
* in a bounded try/finally block.
*
* <p>The user in the request context is {@link InternalUser} or the {@link IdentifiedUser}
* associated to the userId passed as parameter.
*/
@Singleton
public class OneOffRequestContext {
private final InternalUser.Factory userFactory;
private final SchemaFactory<ReviewDb> schemaFactory;
private final ThreadLocalRequestContext requestContext;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
@Inject
OneOffRequestContext(
InternalUser.Factory userFactory,
SchemaFactory<ReviewDb> schemaFactory,
ThreadLocalRequestContext requestContext,
IdentifiedUser.GenericFactory identifiedUserFactory) {
this.userFactory = userFactory;
this.schemaFactory = schemaFactory;
this.requestContext = requestContext;
this.identifiedUserFactory = identifiedUserFactory;
}
public ManualRequestContext open() throws OrmException {
return new ManualRequestContext(userFactory.create(), schemaFactory, requestContext);
}
public ManualRequestContext openAs(Account.Id userId) throws OrmException {
return new ManualRequestContext(
identifiedUserFactory.create(userId), schemaFactory, requestContext);
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2015 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.util;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import org.apache.log4j.AsyncAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
public abstract class PluginLogFile implements LifecycleListener {
private final SystemLog systemLog;
private final ServerInformation serverInfo;
private final String logName;
private final Layout layout;
public PluginLogFile(
SystemLog systemLog, ServerInformation serverInfo, String logName, Layout layout) {
this.systemLog = systemLog;
this.serverInfo = serverInfo;
this.logName = logName;
this.layout = layout;
}
@Override
public void start() {
AsyncAppender asyncAppender = systemLog.createAsyncAppender(logName, layout);
Logger logger = LogManager.getLogger(logName);
logger.removeAppender(logName);
logger.addAppender(asyncAppender);
logger.setAdditivity(false);
}
@Override
public void stop() {
// stop is called when plugin is unloaded or when the server shutdown.
// Only clean up when the server is shutting down to prevent issue when a
// plugin is reloaded. The issue is that gerrit load the new plugin and then
// unload the old one so because loggers are static, the unload of the old
// plugin would remove the appenders just created by the new plugin.
if (serverInfo.getState() == ServerInformation.State.SHUTDOWN) {
LogManager.getLogger(logName).removeAllAppenders();
}
}
}

View File

@@ -0,0 +1,45 @@
// 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.google.gerrit.server.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PluginUser;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
/** RequestContext active while plugins load or unload. */
public class PluginRequestContext implements RequestContext {
private final PluginUser user;
public PluginRequestContext(PluginUser user) {
this.user = user;
}
@Override
public CurrentUser getUser() {
return user;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return new Provider<ReviewDb>() {
@Override
public ReviewDb get() {
throw new ProvisionException("Automatic ReviewDb only available in request scope");
}
};
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (C) 2014 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.util;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Chars;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import java.util.Collections;
import java.util.List;
/** Helper to search sorted lists for elements matching a regex. */
public abstract class RegexListSearcher<T> implements Function<T, String> {
public static RegexListSearcher<String> ofStrings(String re) {
return new RegexListSearcher<String>(re) {
@Override
public String apply(String in) {
return in;
}
};
}
private final RunAutomaton pattern;
private final String prefixBegin;
private final String prefixEnd;
private final int prefixLen;
private final boolean prefixOnly;
public RegexListSearcher(String re) {
if (re.startsWith("^")) {
re = re.substring(1);
}
if (re.endsWith("$") && !re.endsWith("\\$")) {
re = re.substring(0, re.length() - 1);
}
Automaton automaton = new RegExp(re).toAutomaton();
prefixBegin = automaton.getCommonPrefix();
prefixLen = prefixBegin.length();
if (0 < prefixLen) {
char max = Chars.checkedCast(prefixBegin.charAt(prefixLen - 1) + 1);
prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
prefixOnly = re.equals(prefixBegin + ".*");
} else {
prefixEnd = "";
prefixOnly = false;
}
pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
public Iterable<T> search(List<T> list) {
checkNotNull(list);
int begin;
int end;
if (0 < prefixLen) {
// Assumes many consecutive elements may have the same prefix, so the cost
// of two binary searches is less than iterating to find the endpoints.
begin = find(list, prefixBegin);
end = find(list, prefixEnd);
} else {
begin = 0;
end = list.size();
}
if (prefixOnly) {
return begin < end ? list.subList(begin, end) : ImmutableList.<T>of();
}
return Iterables.filter(list.subList(begin, end), x -> pattern.run(apply(x)));
}
public boolean hasMatch(List<T> list) {
return !Iterables.isEmpty(search(list));
}
private int find(List<T> list, String p) {
int r = Collections.binarySearch(Lists.transform(list, this), p);
return r < 0 ? -(r + 1) : r;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2012 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.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Provider;
/**
* The RequestContext is an interface exposing the fields that are needed by the GerritGlobalModule
* scope.
*/
public interface RequestContext {
CurrentUser getUser();
Provider<ReviewDb> getReviewDbProvider();
}

View File

@@ -0,0 +1,70 @@
// Copyright (C) 2016 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.util;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import java.net.InetAddress;
import java.net.UnknownHostException;
/** Unique identifier for an end-user request, used in logs and similar. */
public class RequestId {
private static final String MACHINE_ID;
static {
String id;
try {
id = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
id = "unknown";
}
MACHINE_ID = id;
}
public static RequestId forChange(Change c) {
return new RequestId(c.getId().toString());
}
public static RequestId forProject(Project.NameKey p) {
return new RequestId(p.toString());
}
private final String str;
private RequestId(String resourceId) {
Hasher h = Hashing.murmur3_128().newHasher();
h.putLong(Thread.currentThread().getId()).putUnencodedChars(MACHINE_ID);
str =
"["
+ resourceId
+ "-"
+ TimeUtil.nowTs().getTime()
+ "-"
+ h.hash().toString().substring(0, 8)
+ "]";
}
@Override
public String toString() {
return str;
}
public String toStringForStorage() {
return str.substring(1, str.length() - 1);
}
}

View File

@@ -0,0 +1,218 @@
// Copyright (C) 2012 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.util;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Throwables;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.gerrit.server.git.ProjectRunnable;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.servlet.ServletScopes;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* Base class for propagating request-scoped data between threads.
*
* <p>Request scopes are typically linked to a {@link ThreadLocal}, which is only available to the
* current thread. In order to allow background work involving RequestScoped data, the ThreadLocal
* data must be copied from the request thread to the new background thread.
*
* <p>Every type of RequestScope must provide an implementation of RequestScopePropagator. See
* {@link #wrap(Callable)} for details on the implementation, usage, and restrictions.
*
* @see ThreadLocalRequestScopePropagator
*/
public abstract class RequestScopePropagator {
private final Scope scope;
private final ThreadLocalRequestContext local;
private final Provider<RequestScopedReviewDbProvider> dbProviderProvider;
protected RequestScopePropagator(
Scope scope,
ThreadLocalRequestContext local,
Provider<RequestScopedReviewDbProvider> dbProviderProvider) {
this.scope = scope;
this.local = local;
this.dbProviderProvider = dbProviderProvider;
}
/**
* Ensures that the current request state is available when the passed in Callable is invoked.
*
* <p>If needed wraps the passed in Callable in a new {@link Callable} that propagates the current
* request state when the returned Callable is invoked. The method must be called in a request
* scope and the returned Callable may only be invoked in a thread that is not already in a
* request scope or is in the same request scope. The returned Callable will inherit toString()
* from the passed in Callable. A {@link ScheduledThreadPoolExecutor} does not accept a Callable,
* so there is no ProjectCallable implementation. Implementations of this method must be
* consistent with Guice's {@link ServletScopes#continueRequest(Callable, java.util.Map)}.
*
* <p>There are some limitations:
*
* <ul>
* <li>Derived objects (i.e. anything marked created in a request scope) will not be
* transported.
* <li>State changes to the request scoped context after this method is called will not be seen
* in the continued thread.
* </ul>
*
* @param callable the Callable to wrap.
* @return a new Callable which will execute in the current request scope.
*/
@SuppressWarnings("javadoc") // See GuiceRequestScopePropagator#wrapImpl
public final <T> Callable<T> wrap(Callable<T> callable) {
final RequestContext callerContext = checkNotNull(local.getContext());
final Callable<T> wrapped = wrapImpl(context(callerContext, cleanup(callable)));
return new Callable<T>() {
@Override
public T call() throws Exception {
if (callerContext == local.getContext()) {
return callable.call();
}
return wrapped.call();
}
@Override
public String toString() {
return callable.toString();
}
};
}
/**
* Wraps runnable in a new {@link Runnable} that propagates the current request state when the
* runnable is invoked. The method must be called in a request scope and the returned Runnable may
* only be invoked in a thread that is not already in a request scope. The returned Runnable will
* inherit toString() from the passed in Runnable. Furthermore, if the passed runnable is of type
* {@link ProjectRunnable}, the returned runnable will be of the same type with the methods
* delegated.
*
* <p>See {@link #wrap(Callable)} for details on implementation and usage.
*
* @param runnable the Runnable to wrap.
* @return a new Runnable which will execute in the current request scope.
*/
public final Runnable wrap(Runnable runnable) {
final Callable<Object> wrapped = wrap(Executors.callable(runnable));
if (runnable instanceof ProjectRunnable) {
return new ProjectRunnable() {
@Override
public void run() {
try {
wrapped.call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e); // Not possible.
}
}
@Override
public Project.NameKey getProjectNameKey() {
return ((ProjectRunnable) runnable).getProjectNameKey();
}
@Override
public String getRemoteName() {
return ((ProjectRunnable) runnable).getRemoteName();
}
@Override
public boolean hasCustomizedPrint() {
return ((ProjectRunnable) runnable).hasCustomizedPrint();
}
@Override
public String toString() {
return runnable.toString();
}
};
}
return new Runnable() {
@Override
public void run() {
try {
wrapped.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e); // Not possible.
}
}
@Override
public String toString() {
return runnable.toString();
}
};
}
/** @see #wrap(Callable) */
protected abstract <T> Callable<T> wrapImpl(Callable<T> callable);
protected <T> Callable<T> context(RequestContext context, Callable<T> callable) {
return () -> {
RequestContext old =
local.setContext(
new RequestContext() {
@Override
public CurrentUser getUser() {
return context.getUser();
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return dbProviderProvider.get();
}
});
try {
return callable.call();
} finally {
local.setContext(old);
}
};
}
protected <T> Callable<T> cleanup(Callable<T> callable) {
return () -> {
RequestCleanup cleanup =
scope
.scope(
Key.get(RequestCleanup.class),
new Provider<RequestCleanup>() {
@Override
public RequestCleanup get() {
return new RequestCleanup();
}
})
.get();
try {
return callable.call();
} finally {
cleanup.run();
}
};
}
}

View File

@@ -0,0 +1,47 @@
// 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.google.gerrit.server.util;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
/** RequestContext with an InternalUser making the internals visible. */
public class ServerRequestContext implements RequestContext {
private final InternalUser user;
@Inject
ServerRequestContext(InternalUser.Factory userFactory) {
this.user = userFactory.create();
}
@Override
public CurrentUser getUser() {
return user;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return new Provider<ReviewDb>() {
@Override
public ReviewDb get() {
throw new ProvisionException("Automatic ReviewDb only available in request scope");
}
};
}
}

View File

@@ -0,0 +1,126 @@
// 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.util;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
public final class SocketUtil {
/** True if this InetAddress is a raw IPv6 in dotted quad notation. */
public static boolean isIPv6(InetAddress ip) {
return ip instanceof Inet6Address && ip.getHostName().equals(ip.getHostAddress());
}
/** Get the name or IP address, or {@code *} if this address is a wildcard IP. */
public static String hostname(InetSocketAddress addr) {
if (addr.getAddress() != null) {
if (addr.getAddress().isAnyLocalAddress()) {
return "*";
}
return addr.getAddress().getHostName();
}
return addr.getHostName();
}
/** Format an address string into {@code host:port} or {@code *:port} syntax. */
public static String format(SocketAddress s, int defaultPort) {
if (s instanceof InetSocketAddress) {
final InetSocketAddress addr = (InetSocketAddress) s;
if (addr.getPort() == defaultPort) {
return safeHostname(hostname(addr));
}
return format(hostname(addr), addr.getPort());
}
return s.toString();
}
/** Format an address string into {@code host:port} or {@code *:port} syntax. */
public static String format(String hostname, int port) {
return safeHostname(hostname) + ":" + port;
}
private static String safeHostname(String hostname) {
if (0 <= hostname.indexOf(':')) {
hostname = "[" + hostname + "]";
}
return hostname;
}
/** Parse an address string such as {@code host:port} or {@code *:port}. */
public static InetSocketAddress parse(String desc, int defaultPort) {
String hostStr;
String portStr;
if (desc.startsWith("[")) {
// IPv6, as a raw IP address.
//
final int hostEnd = desc.indexOf(']');
if (hostEnd < 0) {
throw new IllegalArgumentException("invalid IPv6: " + desc);
}
hostStr = desc.substring(1, hostEnd);
portStr = desc.substring(hostEnd + 1);
} else {
// IPv4, or a host name.
//
final int hostEnd = desc.indexOf(':');
hostStr = 0 <= hostEnd ? desc.substring(0, hostEnd) : desc;
portStr = 0 <= hostEnd ? desc.substring(hostEnd) : "";
}
if ("".equals(hostStr)) {
hostStr = "*";
}
if (portStr.startsWith(":")) {
portStr = portStr.substring(1);
}
final int port;
if (portStr.length() > 0) {
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid port: " + desc);
}
} else {
port = defaultPort;
}
if ("*".equals(hostStr)) {
return new InetSocketAddress(port);
}
return InetSocketAddress.createUnresolved(hostStr, port);
}
/** Parse and resolve an address string, looking up the IP address. */
public static InetSocketAddress resolve(String desc, int defaultPort) {
final InetSocketAddress addr = parse(desc, defaultPort);
if (addr.getAddress() != null && addr.getAddress().isAnyLocalAddress()) {
return addr;
}
try {
final InetAddress host = InetAddress.getByName(addr.getHostName());
return new InetSocketAddress(host, addr.getPort());
} catch (UnknownHostException e) {
throw new IllegalArgumentException("unknown host: " + desc, e);
}
}
private SocketUtil() {}
}

View File

@@ -0,0 +1,145 @@
// Copyright (C) 2011 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.util;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
/**
* It parses from a configuration file submodule sections.
*
* <p>Example of submodule sections:
*
* <pre>
* [submodule "project-a"]
* url = http://localhost/a
* path = a
* branch = .
*
* [submodule "project-b"]
* url = http://localhost/b
* path = b
* branch = refs/heads/test
* </pre>
*/
public class SubmoduleSectionParser {
private final Config bbc;
private final String canonicalWebUrl;
private final Branch.NameKey superProjectBranch;
public SubmoduleSectionParser(
Config bbc, String canonicalWebUrl, Branch.NameKey superProjectBranch) {
this.bbc = bbc;
this.canonicalWebUrl = canonicalWebUrl;
this.superProjectBranch = superProjectBranch;
}
public Set<SubmoduleSubscription> parseAllSections() {
Set<SubmoduleSubscription> parsedSubscriptions = new HashSet<>();
for (String id : bbc.getSubsections("submodule")) {
final SubmoduleSubscription subscription = parse(id);
if (subscription != null) {
parsedSubscriptions.add(subscription);
}
}
return parsedSubscriptions;
}
private SubmoduleSubscription parse(String id) {
final String url = bbc.getString("submodule", id, "url");
final String path = bbc.getString("submodule", id, "path");
String branch = bbc.getString("submodule", id, "branch");
try {
if (url != null
&& url.length() > 0
&& path != null
&& path.length() > 0
&& branch != null
&& branch.length() > 0) {
// All required fields filled.
String project;
if (branch.equals(".")) {
branch = superProjectBranch.get();
}
// relative URL
if (url.startsWith("../")) {
// prefix with a slash for easier relative path walks
project = '/' + superProjectBranch.getParentKey().get();
String hostPart = url;
while (hostPart.startsWith("../")) {
int lastSlash = project.lastIndexOf('/');
if (lastSlash < 0) {
// too many levels up, ignore for now
return null;
}
project = project.substring(0, lastSlash);
hostPart = hostPart.substring(3);
}
project = project + "/" + hostPart;
// remove leading '/'
project = project.substring(1);
} else {
// It is actually an URI. It could be ssh://localhost/project-a.
URI targetServerURI = new URI(url);
URI thisServerURI = new URI(canonicalWebUrl);
String thisHost = thisServerURI.getHost();
String targetHost = targetServerURI.getHost();
if (thisHost == null || targetHost == null || !targetHost.equalsIgnoreCase(thisHost)) {
return null;
}
String p1 = targetServerURI.getPath();
String p2 = thisServerURI.getPath();
if (!p1.startsWith(p2)) {
// When we are running the server at
// http://server/my-gerrit/ but the subscription is for
// http://server/other-teams-gerrit/
return null;
}
// skip common part
project = p1.substring(p2.length());
}
while (project.startsWith("/")) {
project = project.substring(1);
}
if (project.endsWith(Constants.DOT_GIT_EXT)) {
project =
project.substring(
0, //
project.length() - Constants.DOT_GIT_EXT.length());
}
Project.NameKey projectKey = new Project.NameKey(project);
return new SubmoduleSubscription(
superProjectBranch, new Branch.NameKey(projectKey, branch), path);
}
} catch (URISyntaxException e) {
// Error in url syntax (in fact it is uri syntax)
}
return null;
}
}

View File

@@ -0,0 +1,130 @@
// Copyright (C) 2014 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.util;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.gerrit.common.Die;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.log4j.Appender;
import org.apache.log4j.AsyncAppender;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.OnlyOnceErrorHandler;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.LoggingEvent;
import org.eclipse.jgit.lib.Config;
import org.slf4j.LoggerFactory;
@Singleton
public class SystemLog {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(SystemLog.class);
public static final String LOG4J_CONFIGURATION = "log4j.configuration";
private final SitePaths site;
private final Config config;
@Inject
public SystemLog(SitePaths site, @GerritServerConfig Config config) {
this.site = site;
this.config = config;
}
public static boolean shouldConfigure() {
return Strings.isNullOrEmpty(System.getProperty(LOG4J_CONFIGURATION));
}
public static Appender createAppender(Path logdir, String name, Layout layout) {
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
dst.setName(name);
dst.setLayout(layout);
dst.setEncoding(UTF_8.name());
dst.setFile(resolve(logdir).resolve(name).toString());
dst.setImmediateFlush(true);
dst.setAppend(true);
dst.setErrorHandler(new DieErrorHandler());
dst.activateOptions();
dst.setErrorHandler(new OnlyOnceErrorHandler());
return dst;
}
public AsyncAppender createAsyncAppender(String name, Layout layout) {
AsyncAppender async = new AsyncAppender();
async.setName(name);
async.setBlocking(true);
async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
async.setLocationInfo(false);
if (shouldConfigure()) {
async.addAppender(createAppender(site.logs_dir, name, layout));
} else {
Appender appender = LogManager.getLogger(name).getAppender(name);
if (appender != null) {
async.addAppender(appender);
} else {
log.warn(
"No appender with the name: " + name + " was found. " + name + " logging is disabled");
}
}
async.activateOptions();
return async;
}
private static Path resolve(Path p) {
try {
return p.toRealPath().normalize();
} catch (IOException e) {
return p.toAbsolutePath().normalize();
}
}
private static final class DieErrorHandler implements ErrorHandler {
@Override
public void error(String message, Exception e, int errorCode, LoggingEvent event) {
error(e != null ? e.getMessage() : message);
}
@Override
public void error(String message, Exception e, int errorCode) {
error(e != null ? e.getMessage() : message);
}
@Override
public void error(String message) {
throw new Die("Cannot open log file: " + message);
}
@Override
public void activateOptions() {}
@Override
public void setAppender(Appender appender) {}
@Override
public void setBackupAppender(Appender appender) {}
@Override
public void setLogger(Logger logger) {}
}
}

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2012 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.util;
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
/**
* ThreadLocalRequestContext manages the current RequestContext using a ThreadLocal. When the
* context is set, the fields exposed by the context are considered in scope. Otherwise, the
* FallbackRequestContext is used.
*/
public class ThreadLocalRequestContext {
private static final String FALLBACK = "FALLBACK";
public static Module module() {
return new AbstractModule() {
@Override
protected void configure() {
bind(ThreadLocalRequestContext.class);
bind(RequestContext.class)
.annotatedWith(Names.named(FALLBACK))
.to(FallbackRequestContext.class);
}
@Provides
RequestContext provideRequestContext(@Named(FALLBACK) RequestContext fallback) {
return MoreObjects.firstNonNull(local.get(), fallback);
}
@Provides
CurrentUser provideCurrentUser(RequestContext ctx) {
return ctx.getUser();
}
@Provides
IdentifiedUser provideCurrentUser(CurrentUser user) {
if (user.isIdentifiedUser()) {
return user.asIdentifiedUser();
}
throw new ProvisionException(NotSignedInException.MESSAGE, new NotSignedInException());
}
@Provides
ReviewDb provideReviewDb(RequestContext ctx) {
return ctx.getReviewDbProvider().get();
}
};
}
private static final ThreadLocal<RequestContext> local = new ThreadLocal<>();
@Inject
ThreadLocalRequestContext() {}
public RequestContext setContext(@Nullable RequestContext ctx) {
RequestContext old = getContext();
local.set(ctx);
return old;
}
@Nullable
public RequestContext getContext() {
return local.get();
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (C) 2012 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.util;
import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import java.util.concurrent.Callable;
/**
* {@link RequestScopePropagator} implementation for request scopes based on a {@link ThreadLocal}
* context.
*
* @param <C> "context" type stored in the {@link ThreadLocal}.
*/
public abstract class ThreadLocalRequestScopePropagator<C> extends RequestScopePropagator {
private final ThreadLocal<C> threadLocal;
protected ThreadLocalRequestScopePropagator(
Scope scope,
ThreadLocal<C> threadLocal,
ThreadLocalRequestContext local,
Provider<RequestScopedReviewDbProvider> dbProviderProvider) {
super(scope, local, dbProviderProvider);
this.threadLocal = threadLocal;
}
/** @see RequestScopePropagator#wrap(Callable) */
@Override
protected final <T> Callable<T> wrapImpl(Callable<T> callable) {
C ctx = continuingContext(requireContext());
return () -> {
C old = threadLocal.get();
threadLocal.set(ctx);
try {
return callable.call();
} finally {
if (old != null) {
threadLocal.set(old);
} else {
threadLocal.remove();
}
}
};
}
private C requireContext() {
C context = threadLocal.get();
if (context == null) {
throw new OutOfScopeException("Cannot access scoped object");
}
return context;
}
/**
* Returns a new context object based on the passed in context that has no request scoped objects
* initialized.
*
* <p>Note that some code paths expect request-scoped objects like {@code CurrentUser} to be
* constructible starting from just the context object returned by this method. For example, in
* the SSH scope, the context includes the {@code SshSession}, which is used by {@code
* SshCurrentUserProvider} to construct a new {@code CurrentUser} in the new thread.
*
* @param ctx the context to continue.
* @return a new context.
*/
protected abstract C continuingContext(C ctx);
}

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2011 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.util;
import java.io.PrintWriter;
import java.util.SortedSet;
public class TreeFormatter {
public interface TreeNode {
String getDisplayName();
boolean isVisible();
SortedSet<? extends TreeNode> getChildren();
}
public static final String NOT_VISIBLE_NODE = "(x)";
private static final String NODE_PREFIX = "|-- ";
private static final String LAST_NODE_PREFIX = "`-- ";
private static final String DEFAULT_TAB_SEPARATOR = "|";
private final PrintWriter stdout;
private String currentTabSeparator = " ";
public TreeFormatter(PrintWriter stdout) {
this.stdout = stdout;
}
public void printTree(SortedSet<? extends TreeNode> rootNodes) {
if (rootNodes.isEmpty()) {
return;
}
if (rootNodes.size() == 1) {
printTree(rootNodes.first());
} else {
currentTabSeparator = DEFAULT_TAB_SEPARATOR;
int i = 0;
final int size = rootNodes.size();
for (TreeNode rootNode : rootNodes) {
final boolean isLastRoot = ++i == size;
if (isLastRoot) {
currentTabSeparator = " ";
}
printTree(rootNode);
}
}
}
public void printTree(TreeNode rootNode) {
printTree(rootNode, 0, true);
}
private void printTree(TreeNode node, int level, boolean isLast) {
printNode(node, level, isLast);
final SortedSet<? extends TreeNode> childNodes = node.getChildren();
int i = 0;
final int size = childNodes.size();
for (TreeNode childNode : childNodes) {
final boolean isLastChild = ++i == size;
printTree(childNode, level + 1, isLastChild);
}
}
private void printIndention(int level) {
if (level > 0) {
stdout.print(String.format("%-" + 4 * level + "s", currentTabSeparator));
}
}
private void printNode(TreeNode node, int level, boolean isLast) {
printIndention(level);
stdout.print(isLast ? LAST_NODE_PREFIX : NODE_PREFIX);
if (node.isVisible()) {
stdout.print(node.getDisplayName());
} else {
stdout.print(NOT_VISIBLE_NODE);
}
stdout.print("\n");
}
}