Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.documentation; 16 : 17 : import com.google.common.collect.ImmutableMap; 18 : import com.google.common.flogger.FluentLogger; 19 : import com.google.gerrit.common.Nullable; 20 : import com.google.inject.Inject; 21 : import com.google.inject.Singleton; 22 : import java.io.IOException; 23 : import java.io.InputStream; 24 : import java.util.ArrayList; 25 : import java.util.List; 26 : import java.util.zip.ZipEntry; 27 : import java.util.zip.ZipInputStream; 28 : import org.apache.lucene.analysis.standard.StandardAnalyzer; 29 : import org.apache.lucene.document.Document; 30 : import org.apache.lucene.index.DirectoryReader; 31 : import org.apache.lucene.index.IndexReader; 32 : import org.apache.lucene.queryparser.simple.SimpleQueryParser; 33 : import org.apache.lucene.search.IndexSearcher; 34 : import org.apache.lucene.search.Query; 35 : import org.apache.lucene.search.ScoreDoc; 36 : import org.apache.lucene.search.TopDocs; 37 : import org.apache.lucene.store.ByteBuffersDirectory; 38 : import org.apache.lucene.store.Directory; 39 : import org.apache.lucene.store.IndexOutput; 40 : 41 : @Singleton 42 : public class QueryDocumentationExecutor { 43 149 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 44 : 45 149 : private static final ImmutableMap<String, Float> WEIGHTS = 46 149 : ImmutableMap.of( 47 149 : Constants.TITLE_FIELD, 2.0f, 48 149 : Constants.DOC_FIELD, 1.0f); 49 : 50 : private IndexSearcher searcher; 51 : private SimpleQueryParser parser; 52 : 53 0 : public static class DocResult { 54 : public String title; 55 : public String url; 56 : public String content; 57 : } 58 : 59 : @Inject 60 149 : public QueryDocumentationExecutor() { 61 : try { 62 149 : Directory dir = readIndexDirectory(); 63 149 : if (dir == null) { 64 149 : searcher = null; 65 149 : parser = null; 66 149 : return; 67 : } 68 0 : IndexReader reader = DirectoryReader.open(dir); 69 0 : searcher = new IndexSearcher(reader); 70 0 : parser = new SimpleQueryParser(new StandardAnalyzer(), WEIGHTS); 71 0 : } catch (IOException e) { 72 0 : logger.atSevere().withCause(e).log("Cannot initialize documentation full text index"); 73 0 : searcher = null; 74 0 : parser = null; 75 0 : } 76 0 : } 77 : 78 : public List<DocResult> doQuery(String q) throws DocQueryException { 79 0 : if (!isAvailable()) { 80 0 : throw new DocQueryException("Documentation search not available"); 81 : } 82 0 : Query query = parser.parse(q); 83 : try { 84 : // We don't have much documentation, so we just use MAX_VALUE here and skip paging. 85 0 : TopDocs results = searcher.search(query, Integer.MAX_VALUE); 86 0 : ScoreDoc[] hits = results.scoreDocs; 87 0 : long totalHits = results.totalHits; 88 : 89 0 : List<DocResult> out = new ArrayList<>(); 90 0 : for (int i = 0; i < totalHits; i++) { 91 0 : DocResult result = new DocResult(); 92 0 : Document doc = searcher.doc(hits[i].doc); 93 0 : result.url = doc.get(Constants.URL_FIELD); 94 0 : result.title = doc.get(Constants.TITLE_FIELD); 95 0 : out.add(result); 96 : } 97 0 : return out; 98 0 : } catch (IOException e) { 99 0 : throw new DocQueryException(e); 100 : } 101 : } 102 : 103 : @Nullable 104 : protected Directory readIndexDirectory() throws IOException { 105 149 : Directory dir = new ByteBuffersDirectory(); 106 149 : byte[] buffer = new byte[4096]; 107 149 : InputStream index = getClass().getResourceAsStream(Constants.INDEX_ZIP); 108 149 : if (index == null) { 109 149 : logger.atWarning().log("No index available"); 110 149 : return null; 111 : } 112 : 113 0 : try (ZipInputStream zip = new ZipInputStream(index)) { 114 : ZipEntry entry; 115 0 : while ((entry = zip.getNextEntry()) != null) { 116 0 : try (IndexOutput out = dir.createOutput(entry.getName(), null)) { 117 : int count; 118 0 : while ((count = zip.read(buffer)) != -1) { 119 0 : out.writeBytes(buffer, count); 120 : } 121 : } 122 : } 123 : } 124 : // We must NOT call dir.close() here, as DirectoryReader.open() expects an opened directory. 125 0 : return dir; 126 : } 127 : 128 : public boolean isAvailable() { 129 3 : return parser != null && searcher != null; 130 : } 131 : 132 : public static class DocQueryException extends Exception { 133 : private static final long serialVersionUID = 1L; 134 : 135 0 : DocQueryException() {} 136 : 137 : DocQueryException(String msg) { 138 0 : super(msg); 139 0 : } 140 : 141 : DocQueryException(String msg, Throwable e) { 142 0 : super(msg, e); 143 0 : } 144 : 145 : DocQueryException(Throwable e) { 146 0 : super(e); 147 0 : } 148 : } 149 : }