LCOV - code coverage report
Current view: top level - server/documentation - QueryDocumentationExecutor.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 18 57 31.6 %
Date: 2022-11-19 15:00:39 Functions: 4 10 40.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750