Java tutorial
// 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.googlesource.gerrit.plugins.uploadvalidator; import static com.googlesource.gerrit.plugins.uploadvalidator.PatternCacheModule.CACHE_NAME; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; import com.google.gerrit.extensions.annotations.Exports; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.config.ProjectConfigEntry; import com.google.gerrit.server.events.CommitReceivedEvent; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.validators.CommitValidationException; import com.google.gerrit.server.git.validators.CommitValidationListener; import com.google.gerrit.server.git.validators.CommitValidationMessage; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.name.Named; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; public class BlockedKeywordValidator implements CommitValidationListener { private static final String KEY_CHECK_BLOCKED_KEYWORD = "blockedKeyword"; private static final String KEY_CHECK_BLOCKED_KEYWORD_PATTERN = KEY_CHECK_BLOCKED_KEYWORD + "Pattern"; public static AbstractModule module() { return new AbstractModule() { @Override protected void configure() { DynamicSet.bind(binder(), CommitValidationListener.class).to(BlockedKeywordValidator.class); bind(ProjectConfigEntry.class).annotatedWith(Exports.named(KEY_CHECK_BLOCKED_KEYWORD_PATTERN)) .toInstance(new ProjectConfigEntry("Blocked Keyword Pattern", null, ProjectConfigEntryType.ARRAY, null, false, "Pushes of commits that contain files or commit messages with " + "blocked keywords will be rejected.")); } }; } private final String pluginName; private final PluginConfigFactory cfgFactory; private final GitRepositoryManager repoManager; private final LoadingCache<String, Pattern> patternCache; private final ContentTypeUtil contentTypeUtil; private final ValidatorConfig validatorConfig; @Inject BlockedKeywordValidator(@PluginName String pluginName, ContentTypeUtil contentTypeUtil, @Named(CACHE_NAME) LoadingCache<String, Pattern> patternCache, PluginConfigFactory cfgFactory, GitRepositoryManager repoManager, ValidatorConfig validatorConfig) { this.pluginName = pluginName; this.patternCache = patternCache; this.cfgFactory = cfgFactory; this.repoManager = repoManager; this.contentTypeUtil = contentTypeUtil; this.validatorConfig = validatorConfig; } static boolean isActive(PluginConfig cfg) { return cfg.getStringList(KEY_CHECK_BLOCKED_KEYWORD_PATTERN).length > 0; } @Override public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent) throws CommitValidationException { try { PluginConfig cfg = cfgFactory.getFromProjectConfigWithInheritance(receiveEvent.project.getNameKey(), pluginName); if (isActive(cfg) && validatorConfig.isEnabledForRef(receiveEvent.user, receiveEvent.getProjectNameKey(), receiveEvent.getRefName(), KEY_CHECK_BLOCKED_KEYWORD)) { ImmutableMap<String, Pattern> blockedKeywordPatterns = patternCache .getAll(Arrays.asList(cfg.getStringList(KEY_CHECK_BLOCKED_KEYWORD_PATTERN))); try (Repository repo = repoManager.openRepository(receiveEvent.project.getNameKey())) { List<CommitValidationMessage> messages = performValidation(repo, receiveEvent.commit, receiveEvent.revWalk, blockedKeywordPatterns.values(), cfg); if (!messages.isEmpty()) { throw new CommitValidationException("includes files containing blocked keywords", messages); } } } } catch (NoSuchProjectException | IOException | ExecutionException e) { throw new CommitValidationException("failed to check on blocked keywords", e); } return Collections.emptyList(); } @VisibleForTesting List<CommitValidationMessage> performValidation(Repository repo, RevCommit c, RevWalk revWalk, ImmutableCollection<Pattern> blockedKeywordPartterns, PluginConfig cfg) throws IOException, ExecutionException { List<CommitValidationMessage> messages = new LinkedList<>(); checkCommitMessageForBlockedKeywords(blockedKeywordPartterns, messages, c.getFullMessage()); Map<String, ObjectId> content = CommitUtils.getChangedContent(repo, c, revWalk); for (String path : content.keySet()) { ObjectLoader ol = revWalk.getObjectReader().open(content.get(path)); try (InputStream in = ol.openStream()) { if (RawText.isBinary(in) || contentTypeUtil.isBlacklistedBinaryContentType(ol, path, cfg)) { continue; } } checkFileForBlockedKeywords(blockedKeywordPartterns, messages, path, ol); } return messages; } private static void checkCommitMessageForBlockedKeywords(ImmutableCollection<Pattern> blockedKeywordPatterns, List<CommitValidationMessage> messages, String commitMessage) { int line = 0; for (String l : commitMessage.split("[\r\n]+")) { line++; checkLineForBlockedKeywords(blockedKeywordPatterns, messages, Patch.COMMIT_MSG, line, l); } } private static void checkFileForBlockedKeywords(ImmutableCollection<Pattern> blockedKeywordPartterns, List<CommitValidationMessage> messages, String path, ObjectLoader ol) throws IOException { try (BufferedReader br = new BufferedReader( new InputStreamReader(ol.openStream(), StandardCharsets.UTF_8))) { int line = 0; for (String l = br.readLine(); l != null; l = br.readLine()) { line++; checkLineForBlockedKeywords(blockedKeywordPartterns, messages, path, line, l); } } } private static void checkLineForBlockedKeywords(ImmutableCollection<Pattern> blockedKeywordPartterns, List<CommitValidationMessage> messages, String path, int lineNumber, String line) { List<String> found = new ArrayList<>(); for (Pattern p : blockedKeywordPartterns) { Matcher matcher = p.matcher(line); while (matcher.find()) { found.add(matcher.group()); } } if (!found.isEmpty()) { messages.add(new CommitValidationMessage( MessageFormat.format("blocked keyword(s) found in: {0} (Line: {1}) (found: {2})", path, lineNumber, Joiner.on(", ").join(found)), true)); } } }