Index modified filenames with Lucene

Add a ChangeIndex interface and an implementation based on Apache
Lucene[1] to provide a secondary index, indexing things we won't or
can't index in the database.

As an example, index the list of modified files in the most recent
patch set of each change. Provide an EqualsFilePredicate for searching
on exact filenames[2], and teach the query builder to rewrite such
predicates as ChangeDataSources returning results from the index.

As this feature is still experimental but we want to avoid prolonged
feature branch development, protect it with an undocumented
index.enabled boolean in gerrit.config.

[1] http://lucene.apache.org/core/

[2] Uses the "file:" operator the same as RegexFilePredicate, but does
    not support regular expressions in the search context.

Change-Id: Ie14ebe062d991eb9626f7b5d78b2d193c1bcb33f
This commit is contained in:
Dave Borowitz 2013-05-17 14:05:25 -07:00
parent 363497ee67
commit 9161eda6d5
28 changed files with 1235 additions and 18 deletions

View File

@ -26,6 +26,7 @@ import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
@ -127,6 +128,11 @@ public class DynamicSet<T> implements Iterable<T> {
return binder.bind(type).annotatedWith(name);
}
public static <T> DynamicSet<T> emptySet() {
return new DynamicSet<T>(
Collections.<AtomicReference<Provider<T>>> emptySet());
}
private final CopyOnWriteArrayList<AtomicReference<Provider<T>>> items;
DynamicSet(Collection<AtomicReference<Provider<T>>> base) {

View File

@ -154,7 +154,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
if (filter != null) {
try {
ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
builder.setAllowFile(true);
builder.setAllowFileRegex(true);
builder.parse(filter);
} catch (QueryParseException badFilter) {
throw new InvalidQueryException(badFilter.getMessage(), filter);

18
gerrit-lucene/BUCK Normal file
View File

@ -0,0 +1,18 @@
java_library(
name = 'lucene',
srcs = glob(['src/main/java/**/*.java']),
deps = [
'//gerrit-antlr:query_exception',
'//gerrit-extension-api:api',
'//gerrit-reviewdb:client',
'//gerrit-server:server',
'//lib:guava',
'//lib:gwtorm',
'//lib:lucene-analyzers-common',
'//lib:lucene-core',
'//lib/guice:guice',
'//lib/jgit:jgit',
'//lib/log:api',
],
visibility = ['PUBLIC'],
)

View File

@ -0,0 +1,276 @@
// 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.git;
package com.google.gerrit.lucene;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_CHANGE;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Secondary index implementation using Apache Lucene.
* <p>
* Writes are managed using a single {@link IndexWriter} per process, committed
* aggressively. Reads use {@link SearcherManager} and periodically refresh,
* though there may be some lag between a committed write and it showing up to
* other threads' searchers.
*/
@Singleton
public class LuceneChangeIndex implements ChangeIndex, LifecycleListener {
private static final Logger log =
LoggerFactory.getLogger(LuceneChangeIndex.class);
private static final Version VERSION = Version.LUCENE_43;
private final FillArgs fillArgs;
private final Directory dir;
private final IndexWriter writer;
private final SearcherManager searcherManager;
@Inject
LuceneChangeIndex(SitePaths sitePaths,
FillArgs fillArgs) throws IOException {
this.fillArgs = fillArgs;
dir = FSDirectory.open(new File(sitePaths.index_dir, "changes"));
IndexWriterConfig writerConfig =
new IndexWriterConfig(VERSION, new StandardAnalyzer(VERSION));
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = new IndexWriter(dir, writerConfig);
searcherManager = new SearcherManager(writer, true, null);
}
@Override
public void start() {
// Do nothing.
}
@Override
public void stop() {
try {
searcherManager.close();
} catch (IOException e) {
log.warn("error closing Lucene searcher", e);
}
try {
writer.close(true);
} catch (IOException e) {
log.warn("error closing Lucene writer", e);
}
try {
dir.close();
} catch (IOException e) {
log.warn("error closing Lucene directory", e);
}
}
@Override
public void insert(ChangeData cd) throws IOException {
writer.addDocument(toDocument(cd));
commit();
}
@Override
public void replace(ChangeData cd) throws IOException {
writer.updateDocument(intTerm(FIELD_CHANGE, cd.getId().get()),
toDocument(cd));
commit();
}
@Override
public ChangeDataSource getSource(IndexPredicate<ChangeData> p)
throws QueryParseException {
if (p.getType() == FieldType.INTEGER) {
return intQuery(p);
} else if (p.getType() == FieldType.EXACT) {
return exactQuery(p);
} else {
throw badFieldType(p.getType());
}
}
public IndexWriter getWriter() {
return writer;
}
private void commit() throws IOException {
writer.commit();
searcherManager.maybeRefresh();
}
private Term intTerm(String name, int value) {
BytesRef bytes = new BytesRef(NumericUtils.BUF_SIZE_INT);
NumericUtils.intToPrefixCodedBytes(value, 0, bytes);
return new Term(name, bytes);
}
private QuerySource intQuery(IndexPredicate<ChangeData> p)
throws QueryParseException {
int value;
try {
// Can't use IntPredicate because it and IndexPredicate are different
// subclasses of OperatorPredicate.
value = Integer.valueOf(p.getValue());
} catch (IllegalArgumentException e) {
throw new QueryParseException("not an integer: " + p.getValue());
}
return new QuerySource(new TermQuery(intTerm(p.getOperator(), value)));
}
private QuerySource exactQuery(IndexPredicate<ChangeData> p) {
return new QuerySource(new TermQuery(
new Term(p.getOperator(), p.getValue())));
}
private class QuerySource implements ChangeDataSource {
// TODO(dborowitz): Push limit down from predicate tree.
private static final int LIMIT = 1000;
private final Query query;
public QuerySource(Query query) {
this.query = query;
}
@Override
public int getCardinality() {
return 10; // TODO(dborowitz): estimate from Lucene?
}
@Override
public boolean hasChange() {
return false;
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
try {
IndexSearcher searcher = searcherManager.acquire();
try {
ScoreDoc[] docs = searcher.search(query, LIMIT).scoreDocs;
List<ChangeData> result = Lists.newArrayListWithCapacity(docs.length);
for (ScoreDoc sd : docs) {
Document doc = searcher.doc(sd.doc);
Number v = doc.getField(FIELD_CHANGE).numericValue();
result.add(new ChangeData(new Change.Id(v.intValue())));
}
final List<ChangeData> r = Collections.unmodifiableList(result);
return new ResultSet<ChangeData>() {
@Override
public Iterator<ChangeData> iterator() {
return r.iterator();
}
@Override
public List<ChangeData> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} finally {
searcherManager.release(searcher);
}
} catch (IOException e) {
throw new OrmException(e);
}
}
}
private Document toDocument(ChangeData cd) throws IOException {
try {
Document result = new Document();
for (FieldDef<ChangeData, ?> f : ChangeField.ALL.values()) {
if (f.isRepeatable()) {
add(result, f, (Iterable<?>) f.get(cd, fillArgs));
} else {
add(result, f, Collections.singleton(f.get(cd, fillArgs)));
}
}
return result;
} catch (OrmException e) {
throw new IOException(e);
}
}
private void add(Document doc, FieldDef<ChangeData, ?> f,
Iterable<?> values) throws OrmException {
if (f.getType() == FieldType.INTEGER) {
for (Object value : values) {
doc.add(new IntField(f.getName(), (Integer) value, store(f)));
}
} else if (f.getType() == FieldType.EXACT) {
for (Object value : values) {
doc.add(new StringField(f.getName(), (String) value, store(f)));
}
} else {
throw badFieldType(f.getType());
}
}
private static Field.Store store(FieldDef<?, ?> f) {
return f.isStored() ? Field.Store.YES : Field.Store.NO;
}
private static IllegalArgumentException badFieldType(FieldType<?> t) {
return new IllegalArgumentException("unknown index field type " + t);
}
}

View File

@ -0,0 +1,39 @@
// 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.git;
package com.google.gerrit.lucene;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.index.ChangeIndexerImpl;
import com.google.inject.Injector;
import com.google.inject.Key;
import org.eclipse.jgit.lib.Config;
public class LuceneIndexModule extends LifecycleModule {
public static boolean isEnabled(Injector injector) {
return injector.getInstance(Key.get(Config.class, GerritServerConfig.class))
.getBoolean("index", null, "enabled", false);
}
@Override
protected void configure() {
bind(ChangeIndex.class).to(LuceneChangeIndex.class);
bind(ChangeIndexer.class).to(ChangeIndexerImpl.class);
listener().to(LuceneChangeIndex.class);
}
}

View File

@ -8,6 +8,7 @@ java_library2(
'//gerrit-extension-api:api',
'//gerrit-gwtexpui:server',
'//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-openid:openid',
'//gerrit-server:common_rules',
'//gerrit-reviewdb:server',
@ -30,6 +31,7 @@ java_library2(
'//lib/jgit:jgit',
'//lib/log:api',
'//lib/log:log4j',
'//lib:lucene-core',
'//lib/mina:sshd',
'//lib/prolog:prolog-cafe',
],

View File

@ -28,6 +28,7 @@ import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.http.jetty.GetUserFilter;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
@ -50,6 +51,7 @@ import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.contact.HttpContactStoreConnection;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.NoIndexModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.IntraLineWorkerPool;
@ -319,6 +321,11 @@ public class Daemon extends SiteProgram {
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
if (LuceneIndexModule.isEnabled(cfgInjector)) {
modules.add(new LuceneIndexModule());
} else {
modules.add(new NoIndexModule());
}
if (httpd) {
modules.add(new CanonicalWebUrlModule() {
@Override

View File

@ -0,0 +1,131 @@
// 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.git;
package com.google.gerrit.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lucene.LuceneChangeIndex;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class Reindex extends SiteProgram {
private final LifecycleManager manager = new LifecycleManager();
private final AtomicReference<ReviewDb> dbRef =
new AtomicReference<ReviewDb>();
private Injector dbInjector;
private Injector sysInjector;
private SitePaths sitePaths;
@Override
public int run() throws Exception {
mustHaveValidSite();
dbInjector = createDbInjector(SINGLE_USER);
if (!LuceneIndexModule.isEnabled(dbInjector)) {
throw die("Secondary index not enabled");
}
sitePaths = dbInjector.getInstance(SitePaths.class);
deleteAll();
sysInjector = createSysInjector();
manager.add(dbInjector);
manager.add(sysInjector);
manager.start();
SchemaFactory<ReviewDb> schema = dbInjector.getInstance(
Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
ReviewDb db = schema.open();
dbRef.set(db);
LuceneChangeIndex index = sysInjector.getInstance(LuceneChangeIndex.class);
Stopwatch sw = new Stopwatch().start();
int i = 0;
for (Change change : db.changes().all()) {
index.insert(new ChangeData(change));
i++;
}
index.getWriter().commit();
double elapsed = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
System.out.format("Reindexed %d changes in %.02fms", i, elapsed);
manager.stop();
return 0;
}
private Injector createSysInjector() {
List<Module> modules = Lists.newArrayList();
modules.add(PatchListCacheImpl.module());
modules.add(new LuceneIndexModule());
modules.add(new AbstractModule() {
@SuppressWarnings("rawtypes")
@Override
protected void configure() {
bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
@Override
public ReviewDb get() {
return dbRef.get();
}
});
// Plugins are not loaded and we're just running through each change
// once, so don't worry about cache removal.
bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
.toInstance(DynamicSet.<CacheRemovalListener> emptySet());
install(new DefaultCacheFactory.Module());
}
});
return dbInjector.createChildInjector(modules);
}
private void deleteAll() throws IOException {
File file = new File(sitePaths.index_dir, "changes");
if (file.exists()) {
Directory dir = FSDirectory.open(file);
try {
for (String name : dir.listAll()) {
dir.deleteFile(name);
}
} finally {
dir.close();
}
}
}
}

View File

@ -29,8 +29,10 @@ import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -52,6 +54,7 @@ public class ChangeInserter {
private final ChangeHooks hooks;
private final ApprovalsUtil approvalsUtil;
private final TrackingFooters trackingFooters;
private final ChangeIndexer indexer;
private final RefControl refControl;
private final Change change;
@ -59,6 +62,7 @@ public class ChangeInserter {
private final RevCommit commit;
private final PatchSetInfo patchSetInfo;
private RequestScopePropagator requestScopePropagator;
private ChangeMessage changeMessage;
private Set<Account.Id> reviewers;
private boolean draft;
@ -70,6 +74,7 @@ public class ChangeInserter {
ChangeHooks hooks,
ApprovalsUtil approvalsUtil,
TrackingFooters trackingFooters,
ChangeIndexer indexer,
@Assisted RefControl refControl,
@Assisted Change change,
@Assisted RevCommit commit) {
@ -78,6 +83,7 @@ public class ChangeInserter {
this.hooks = hooks;
this.approvalsUtil = approvalsUtil;
this.trackingFooters = trackingFooters;
this.indexer = indexer;
this.refControl = refControl;
this.change = change;
this.commit = commit;
@ -98,6 +104,11 @@ public class ChangeInserter {
ChangeUtil.computeSortKey(change);
}
public ChangeInserter setRequestScopePropagator(RequestScopePropagator rsp) {
requestScopePropagator = rsp;
return this;
}
public ChangeInserter setMessage(ChangeMessage changeMessage) {
this.changeMessage = changeMessage;
return this;
@ -140,6 +151,7 @@ public class ChangeInserter {
db.changeMessages().insert(Collections.singleton(changeMessage));
}
indexer.index(change, requestScopePropagator);
gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
ObjectId.zeroId(), commit);
hooks.doPatchsetCreatedHook(change, patchSet, db);

View File

@ -30,6 +30,7 @@ import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.RefControl;
@ -66,6 +67,7 @@ public class PatchSetInserter {
private final IdentifiedUser user;
private final GitReferenceUpdated gitRefUpdated;
private final CommitValidators.Factory commitValidatorsFactory;
private final ChangeIndexer indexer;
private boolean validateForReceiveCommits;
private final Repository git;
@ -87,6 +89,7 @@ public class PatchSetInserter {
IdentifiedUser user,
GitReferenceUpdated gitRefUpdated,
CommitValidators.Factory commitValidatorsFactory,
ChangeIndexer indexer,
@Assisted Repository git,
@Assisted RevWalk revWalk,
@Assisted RefControl refControl,
@ -99,6 +102,7 @@ public class PatchSetInserter {
this.user = user;
this.gitRefUpdated = gitRefUpdated;
this.commitValidatorsFactory = commitValidatorsFactory;
this.indexer = indexer;
this.git = git;
this.revWalk = revWalk;
@ -212,6 +216,7 @@ public class PatchSetInserter {
db.changeMessages().insert(Collections.singleton(changeMessage));
}
indexer.index(change);
hooks.doPatchsetCreatedHook(change, patchSet, db);
} finally {
db.rollback();

View File

@ -40,6 +40,7 @@ public final class SitePaths {
public final File hooks_dir;
public final File static_dir;
public final File themes_dir;
public final File index_dir;
public final File gerrit_sh;
public final File gerrit_war;
@ -77,6 +78,7 @@ public final class SitePaths {
hooks_dir = new File(site_path, "hooks");
static_dir = new File(site_path, "static");
themes_dir = new File(site_path, "themes");
index_dir = new File(site_path, "index");
gerrit_sh = new File(bin_dir, "gerrit.sh");
gerrit_war = new File(bin_dir, "gerrit.war");

View File

@ -1470,7 +1470,8 @@ public class ReceiveCommits {
currentUser.getAccountId(),
magicBranch.dest);
change.setTopic(magicBranch.topic);
ins = changeInserterFactory.create(ctl, change, c);
ins = changeInserterFactory.create(ctl, change, c)
.setRequestScopePropagator(requestScopePropagator);
cmd = new ReceiveCommand(ObjectId.zeroId(), c,
ins.getPatchSet().getRefName());
}

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.git;
package com.google.gerrit.server.index;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gwtorm.server.OrmException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
/**
* Fields indexed on change documents.
* <p>
* Each field corresponds to both a field name supported by
* {@link ChangeQueryBuilder} for querying that field, and a method on
* {@link ChangeData} used for populating the corresponding document fields in
* the secondary index.
* <p>
* Used to generate a schema for index implementations that require one.
*/
public class ChangeField {
/** Legacy change ID. */
public static final FieldDef<ChangeData, Integer> CHANGE_ID =
new FieldDef.Single<ChangeData, Integer>(ChangeQueryBuilder.FIELD_CHANGE,
FieldType.INTEGER, true) {
@Override
public Integer get(ChangeData input, FillArgs args) {
return input.getId().get();
}
};
/** List of filenames modified in the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> FILE =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
return input.currentFilePaths(args.db, args.patchListCache);
}
};
public static final ImmutableMap<String, FieldDef<ChangeData, ?>> ALL;
static {
Map<String, FieldDef<ChangeData, ?>> fields = Maps.newHashMap();
for (Field f : ChangeField.class.getFields()) {
if (Modifier.isPublic(f.getModifiers())
&& Modifier.isStatic(f.getModifiers())
&& Modifier.isFinal(f.getModifiers())
&& FieldDef.class.isAssignableFrom(f.getType())) {
ParameterizedType t = (ParameterizedType) f.getGenericType();
if (t.getActualTypeArguments()[0] == ChangeData.class) {
try {
@SuppressWarnings("unchecked")
FieldDef<ChangeData, ?> fd = (FieldDef<ChangeData, ?>) f.get(null);
fields.put(fd.getName(), fd);
} catch (IllegalArgumentException e) {
throw new ExceptionInInitializerError(e);
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
} else {
throw new ExceptionInInitializerError(
"non-ChangeData ChangeField: " + f);
}
}
}
if (fields.isEmpty()) {
throw new ExceptionInInitializerError("no ChangeFields found");
}
ALL = ImmutableMap.copyOf(fields);
}
}

View File

@ -0,0 +1,92 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import java.io.IOException;
/**
* Secondary index implementation for change documents.
* <p>
* {@link ChangeData} objects are inserted into the index and are queried by
* converting special {@link com.google.gerrit.server.query.Predicate} instances
* into index-aware predicates that use the index search results as a source.
* <p>
* Implementations must be thread-safe and should batch inserts/updates where
* appropriate.
*/
public interface ChangeIndex {
/** Instance indicating secondary index is disabled. */
public static final ChangeIndex DISABLED = new ChangeIndex() {
@Override
public void insert(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public void replace(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public ChangeDataSource getSource(IndexPredicate<ChangeData> p)
throws QueryParseException {
throw new UnsupportedOperationException();
}
};
/**
* Insert a change document into the index.
* <p>
* Results may not be immediately visible to searchers, but should be visible
* within a reasonable amount of time.
*
* @param cd change document with all index fields prepopulated; see
* {@link ChangeData#fillIndexFields}.
*
* @throws IOException if the change could not be inserted.
*/
public void insert(ChangeData cd) throws IOException;
/**
* Update a change document in the index.
* <p>
* Semantically equivalent to removing the document and reinserting it with
* new field values. Results may not be immediately visible to searchers, but
* should be visible within a reasonable amount of time.
*
* @param cd change document with all index fields prepopulated; see
* {@link ChangeData#fillIndexFields}.
*
* @throws IOException
*/
public void replace(ChangeData cd) throws IOException;
/**
* Convert the given operator predicate into a source searching the index and
* returning only the documents matching that predicate.
*
* @param p the predicate to match.
* @return a source of documents matching the predicate.
*
* @throws QueryParseException if the predicate could not be converted to an
* indexed data source.
*/
public ChangeDataSource getSource(IndexPredicate<ChangeData> p)
throws QueryParseException;
}

View File

@ -0,0 +1,57 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.common.util.concurrent.Futures;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.util.RequestScopePropagator;
import java.util.concurrent.Future;
/**
* Helper for (re)indexing a change document.
* <p>
* Indexing is run in the background, as it may require substantial work to
* compute some of the fields and/or update the index.
*/
public interface ChangeIndexer {
/** Instance indicating secondary index is disabled. */
public static final ChangeIndexer DISABLED = new ChangeIndexer() {
@Override
public Future<?> index(Change change) {
return Futures.immediateFuture(null);
}
@Override
public Future<?> index(Change change, RequestScopePropagator prop) {
return Futures.immediateFuture(null);
}
};
/**
* Start indexing a change.
*
* @param change change to index.
*/
public Future<?> index(Change change);
/**
* Start indexing a change.
*
* @param change change to index.
* @param prop propagator to wrap any created runnables in.
*/
public Future<?> index(Change change, RequestScopePropagator prop);
}

View File

@ -0,0 +1,85 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.Future;
/**
* Helper for (re)indexing a change document.
* <p>
* Indexing is run in the background, as it may require substantial work to
* compute some of the fields and/or update the index.
*/
public class ChangeIndexerImpl implements ChangeIndexer {
private static final Logger log =
LoggerFactory.getLogger(ChangeIndexerImpl.class);
private final WorkQueue workQueue;
private final ChangeIndex index;
@Inject
ChangeIndexerImpl(WorkQueue workQueue,
ChangeIndex index) {
this.workQueue = workQueue;
this.index = index;
}
@Override
public Future<?> index(Change change) {
return index(change, null);
}
@Override
public Future<?> index(Change change, RequestScopePropagator prop) {
Runnable task = new Task(change);
if (prop != null) {
task = prop.wrap(task);
}
return workQueue.getDefaultQueue().submit(task);
}
private class Task implements Runnable {
private final Change change;
private Task(Change change) {
this.change = change;
}
@Override
public void run() {
ChangeData cd = new ChangeData(change);
try {
index.replace(cd);
} catch (IOException e) {
log.error("Error indexing change", e);
}
}
@Override
public String toString() {
return "index-change-" + change.getId().get();
}
}
}

View File

@ -0,0 +1,112 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
* Definition of a field stored in the secondary index.
*
* @param I input type from which documents are created and search results are
* returned.
* @param T type that should be extracted from the input object when converting
* to an index document.
*/
public abstract class FieldDef<I, T> {
/** Definition of a single (non-repeatable) field. */
public static abstract class Single<I, T> extends FieldDef<I, T> {
Single(String name, FieldType<T> type, boolean stored) {
super(name, type, stored);
}
@Override
public final boolean isRepeatable() {
return false;
}
}
/** Definition of a repeatable field. */
public static abstract class Repeatable<I, T>
extends FieldDef<I, Iterable<T>> {
Repeatable(String name, FieldType<T> type, boolean stored) {
super(name, type, stored);
}
@Override
public final boolean isRepeatable() {
return true;
}
}
/** Arguments needed to fill in missing data in the input object. */
public static class FillArgs {
final Provider<ReviewDb> db;
final PatchListCache patchListCache;
@Inject
FillArgs(Provider<ReviewDb> db,
PatchListCache patchListCache) {
this.db = db;
this.patchListCache = patchListCache;
}
}
private final String name;
private final FieldType<?> type;
private final boolean stored;
private FieldDef(String name, FieldType<?> type, boolean stored) {
this.name = name;
this.type = type;
this.stored = stored;
}
/** @return name of the field. */
public final String getName() {
return name;
}
/**
* @return type of the field; for repeatable fields, the inner type, not the
* iterable type.
*/
public final FieldType<?> getType() {
return type;
}
/** @return whether the field should be stored in the index. */
public final boolean isStored() {
return stored;
}
/**
* Get the field contents from the input object.
*
* @param input input object.
* @param args arbitrary arguments needed to fill in indexable fields of the
* input object.
* @return the field value(s) to index.
*
* @throws OrmException
*/
public abstract T get(I input, FillArgs args) throws OrmException;
/** @return whether the field is repeatable. */
public abstract boolean isRepeatable();
}

View File

@ -0,0 +1,42 @@
// 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.git;
package com.google.gerrit.server.index;
/** Document field types supported by the secondary index system. */
public class FieldType<T> {
/** A single integer-valued field. */
public static final FieldType<Integer> INTEGER =
new FieldType<Integer>("INTEGER");
/** A string field searched using exact-match semantics. */
public static final FieldType<String> EXACT =
new FieldType<String>("EXACT");
private final String name;
private FieldType(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}

View File

@ -0,0 +1,31 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.gerrit.server.query.OperatorPredicate;
/** Index-aware predicate that includes a field type annotation. */
public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
private final FieldDef<I, ?> def;
public IndexPredicate(FieldDef<I, ?> def, String value) {
super(def.getName(), value);
this.def = def;
}
public FieldType<?> getType() {
return def.getType();
}
}

View File

@ -0,0 +1,29 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.inject.AbstractModule;
public class NoIndexModule extends AbstractModule {
// TODO(dborowitz): This module should go away when the index becomes
// obligatory, as should the interfaces that exist only to support the
// non-index case.
@Override
protected void configure() {
bind(ChangeIndex.class).toInstance(ChangeIndex.DISABLED);
bind(ChangeIndexer.class).toInstance(ChangeIndexer.DISABLED);
}
}

View File

@ -0,0 +1,87 @@
// 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.git;
package com.google.gerrit.server.index;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import java.util.Collection;
/**
* Wrapper combining an {@link IndexPredicate} together with a
* {@link ChangeDataSource} that returns matching results from the index.
* <p>
* Appropriate to return as the rootmost predicate that can be processed using
* the secondary index; such predicates must also implement
* {@link ChangeDataSource} to be chosen by the query processor.
*/
public class PredicateWrapper extends Predicate<ChangeData> implements
ChangeDataSource {
private final IndexPredicate<ChangeData> pred;
private final ChangeDataSource source;
public PredicateWrapper(ChangeIndex index, IndexPredicate<ChangeData> pred)
throws QueryParseException {
this.pred = pred;
this.source = index.getSource(pred);
}
@Override
public int getCardinality() {
return source.getCardinality();
}
@Override
public boolean hasChange() {
return source.hasChange();
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
return source.read();
}
@Override
public Predicate<ChangeData> copy(
Collection<? extends Predicate<ChangeData>> children) {
return this;
}
@Override
public boolean match(ChangeData cd) throws OrmException {
return pred.match(cd);
}
@Override
public int getCost() {
return pred.getCost();
}
@Override
public int hashCode() {
return pred.hashCode();
}
@Override
public boolean equals(Object other) {
return other != null
&& getClass() == other.getClass()
&& pred.equals(((PredicateWrapper) other).pred);
}
}

View File

@ -24,8 +24,8 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
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.IdentifiedUser;
@ -202,7 +202,7 @@ public class ProjectWatch {
}
if (filter != null) {
qb.setAllowFile(true);
qb.setAllowFileRegex(true);
Predicate<ChangeData> filterPredicate = qb.parse(filter);
if (p == null) {
p = filterPredicate;

View File

@ -30,6 +30,8 @@ import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.PredicateWrapper;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
@ -113,6 +115,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
final PatchListCache patchListCache;
final GitRepositoryManager repoManager;
final ProjectCache projectCache;
final ChangeIndex index;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
@ -125,7 +128,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
AllProjectsName allProjectsName,
PatchListCache patchListCache,
GitRepositoryManager repoManager,
ProjectCache projectCache) {
ProjectCache projectCache,
ChangeIndex index) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
@ -137,6 +141,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.patchListCache = patchListCache;
this.repoManager = repoManager;
this.projectCache = projectCache;
this.index = index;
}
}
@ -146,7 +151,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
private final Arguments args;
private final CurrentUser currentUser;
private boolean allowsFile;
private boolean allowFileRegex;
@Inject
ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
@ -155,8 +160,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.currentUser = currentUser;
}
public void setAllowFile(boolean on) {
allowsFile = on;
public void setAllowFileRegex(boolean on) {
allowFileRegex = on;
}
@Operator
@ -284,15 +289,22 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> file(String file) throws QueryParseException {
if (!allowsFile) {
throw error("operator not permitted here: file:" + file);
if (allowFileRegex) {
if (file.startsWith("^")) {
return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
} else {
throw new IllegalArgumentException();
}
} else {
if (!file.startsWith("^") && args.index != ChangeIndex.DISABLED) {
// TODO(dborowitz): Wrap predicates in query rewriter, not here.
return new PredicateWrapper(
args.index,
new EqualsFilePredicate(args.dbProvider, args.patchListCache, file));
} else {
throw error("regular expression not permitted here: file:" + file);
}
}
if (file.startsWith("^")) {
return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
}
throw new IllegalArgumentException();
}
@Operator

View File

@ -40,7 +40,7 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
null, null, null, null, null, //
null, null, null, null), null));
null, null, null, null, null), null));
private final Provider<ReviewDb> dbProvider;

View File

@ -0,0 +1,57 @@
// 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.git;
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
import java.util.Collections;
import java.util.List;
class EqualsFilePredicate extends IndexPredicate<ChangeData> {
private final Provider<ReviewDb> db;
private final PatchListCache cache;
private final String value;
EqualsFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String value) {
super(ChangeField.FILE, value);
this.db = db;
this.cache = plc;
this.value = value;
}
@Override
public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths(db, cache);
if (files != null) {
return Collections.binarySearch(files, value) >= 0;
} else {
// The ChangeData can't do expensive lookups right now. Bypass
// them and include the result anyway. We might be able to do
// a narrow later on to a smaller set.
//
return true;
}
}
@Override
public int getCost() {
return 1;
}
}

View File

@ -5,6 +5,7 @@ java_library2(
'//gerrit-cache-h2:cache-h2',
'//gerrit-extension-api:api',
'//gerrit-httpd:httpd',
'//gerrit-lucene:lucene',
'//gerrit-openid:openid',
'//gerrit-reviewdb:server',
'//gerrit-server:common_rules',

View File

@ -18,11 +18,11 @@ import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.httpd.GerritUiOptions;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
import com.google.gerrit.server.config.AuthConfig;
@ -37,6 +37,7 @@ import com.google.gerrit.server.contact.HttpContactStoreConnection;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.NoIndexModule;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.IntraLineWorkerPool;
@ -236,6 +237,11 @@ public class WebAppInitializer extends GuiceServletContextListener {
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginModule());
if (LuceneIndexModule.isEnabled(cfgInjector)) {
modules.add(new LuceneIndexModule());
} else {
modules.add(new NoIndexModule());
}
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {

View File

@ -246,3 +246,19 @@ maven_jar(
visibility = ['//lib:easymock'],
attach_source = False,
)
maven_jar(
name = 'lucene-core',
id = 'org.apache.lucene:lucene-core:4.3.0',
bin_sha1 = 'd4e40fe5661b8de5d8c66db3d63a47b6b3ecf7f3',
src_sha1 = '86c29288b1930e33ba7ffea1b866af9a52d3d24a',
license = 'Apache2.0',
)
maven_jar(
name = 'lucene-analyzers-common',
id = 'org.apache.lucene:lucene-analyzers-common:4.3.0',
bin_sha1 = 'e7c3976156d292f696016e138b67ab5e6bfc1a56',
src_sha1 = '3606622b3c1f09b4b7cf34070cbf60d414af9b6b',
license = 'Apache2.0',
)