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:
parent
363497ee67
commit
9161eda6d5
@ -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) {
|
||||
|
@ -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
18
gerrit-lucene/BUCK
Normal 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'],
|
||||
)
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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',
|
||||
],
|
||||
|
@ -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
|
||||
|
131
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
Normal file
131
gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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() {
|
||||
|
16
lib/BUCK
16
lib/BUCK
@ -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',
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user