Статьи

Документы для поиска?

Elasticsearch – это гибкий и мощный распределенный механизм поиска и анализа в реальном времени для облака, основанный на Apache Lucene, который предоставляет возможности полнотекстового поиска. Он ориентирован на документы и не содержит схем.

Asciidoctor – это чистый процессор Ruby для преобразования исходных файлов и строк AsciiDoc в HTML 5 , DocBook 4.5 и другие форматы. Помимо части Asciidoctor Ruby , существует проект интеграции Asciidoctor-java, который позволяет нам вызывать функции Asciidoctor из Java, не замечая, что выполняется код Ruby .

В этом посте мы увидим, как мы можем использовать Elasticsearch над документами AsciiDoc, чтобы сделать их доступными для поиска по информации заголовка или по их содержимому.

Давайте добавим необходимые зависимости:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependencies>
     
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.googlecode.lambdaj</groupId>
        <artifactId>lambdaj</artifactId>
        <version>2.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>0.90.1</version>
    </dependency>
    <dependency>
        <groupId>org.asciidoctor</groupId>
        <artifactId>asciidoctor-java-integration</artifactId>
        <version>0.1.3</version>
    </dependency>
         
</dependencies>

Библиотека Lambdaj используется для преобразования файлов AsciiDoc в документы json.

Теперь мы можем запустить экземпляр Elasticsearch, который в нашем случае будет встроенным экземпляром.

1
node = nodeBuilder().local(true).node();

Следующим шагом является анализ заголовка документа AsciiDoc , чтение его содержимого и преобразование его в документ json .

Примером документа json, хранящегося в Elasticsearch, может быть:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
{
   "title":"Asciidoctor Maven plugin 0.1.2 released!",
   "authors":[
      {
         "author":"Jason Porter",
         "email":"example@mail.com"
      }
   ],
   "version":null,
   "content":"= Asciidoctor Maven plugin 0.1.2 released!.....",
   "tags":[
      "release",
      "plugin"
   ]
}

А для преобразования файла AsciiDoc в документ json мы будем использовать класс XContentBuilder , предоставляемый Java API Elasticsearch, для программного создания документов json .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.lordofthejars.asciidoctor;
 
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
 
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Author;
import org.asciidoctor.DocumentHeader;
import org.asciidoctor.internal.IOUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
 
import ch.lambdaj.function.convert.Converter;
 
public class AsciidoctorFileJsonConverter implements Converter<File, XContentBuilder> {
     
    private Asciidoctor asciidoctor;
     
    public AsciidoctorFileJsonConverter() {
        this.asciidoctor = Asciidoctor.Factory.create();
    }
     
    public XContentBuilder convert(File asciidoctor) {
         
        DocumentHeader documentHeader = this.asciidoctor.readDocumentHeader(asciidoctor);
         
        XContentBuilder jsonContent = null;
        try {
            jsonContent = jsonBuilder()
                    .startObject()
                    .field("title", documentHeader.getDocumentTitle())
                    .startArray("authors");
                     
                    Author mainAuthor = documentHeader.getAuthor();
             
                    jsonContent.startObject()
                                .field("author", mainAuthor.getFullName())
                                .field("email", mainAuthor.getEmail())
                                .endObject();
                     
                    List<Author> authors = documentHeader.getAuthors();
                     
                    for (Author author : authors) {
                        jsonContent.startObject()
                                .field("author", author.getFullName())
                                .field("email", author.getEmail())
                                .endObject();
                    }
                     
                    jsonContent.endArray()
                            .field("version", documentHeader.getRevisionInfo().getNumber())
                            .field("content", readContent(asciidoctor))
                            .array("tags", parseTags((String)documentHeader.getAttributes().get("tags")))
                    .endObject();
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
         
        return jsonContent;
    }
 
    private String[] parseTags(String tags) {
        tags = tags.substring(1, tags.length()-1);
        return tags.split(", ");
    }
     
    private String readContent(File content) throws FileNotFoundException {
        return IOUtils.readFull(new FileInputStream(content));
    }
     
}

По сути, мы создаем документ json , вызывая методы startObject для запуска нового объекта, метод полей для добавления новых полей и startArray для запуска массива. Затем этот конструктор будет использоваться для визуализации эквивалентного объекта в формате json . Обратите внимание, что мы используем метод readDocumentHeader из класса Asciidoctor, который возвращает атрибуты заголовка из файла AsciiDoc без чтения и рендеринга всего документа. И, наконец, поле содержимого устанавливается со всем содержимым документа.

И теперь мы готовы начать индексацию документов. Обратите внимание, что метод populateData получает в качестве параметра объект Client . Этот объект из Elasticsearch Java API и представляет собой соединение с базой данных Elasticsearch .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import static ch.lambdaj.Lambda.convert;
//....
 
private void populateData(Client client) throws IOException {
        List<File> asciidoctorFiles = new ArrayList<File>() {{
            add(new File("target/test-classes/java_release.adoc"));
            add(new File("target/test-classes/maven_release.adoc"));
        }};
         
        List<XContentBuilder> jsonDocuments = convertAsciidoctorFilesToJson(asciidoctorFiles);
         
        for (int i=0; i < jsonDocuments.size(); i++) {
            client.prepareIndex("docs",
                                         "asciidoctor", Integer.toString(i)).setSource(jsonDocuments.get(i)).execute().actionGet();
             
        }
                 
                client.admin().indices().refresh(new RefreshRequest("docs")).actionGet();
}
 
private List<XContentBuilder> convertAsciidoctorFilesToJson(List<File> asciidoctorFiles) {
        return convert(asciidoctorFiles, new AsciidoctorFileJsonConverter());
}

Важно отметить, что первая часть алгоритма конвертирует все наши файлы AsciiDoc (в нашем случае два) в экземпляры XContentBuilder с использованием предыдущего класса конвертера и метода convert проекта Lambdaj .

Если вы хотите, вы можете взглянуть на оба документа, использованных в этом примере, по адресу https://github.com/asciidoctor/asciidoctor.github.com/blob/develop/news/asciidoctor-java-integration-0-1-3- release.adoc и https://github.com/asciidoctor/asciidoctor.github.com/blob/develop/news/asciidoctor-maven-plugin-0-1-2-released.adoc .

Следующая часть – вставка документов в один указатель. Это делается с помощью метода prepareIndex , который требует имя индекса ( docs ), тип индекса ( asciidoctor ) и идентификатор вставляемого документа. Затем мы вызываем метод setSource, который преобразует объект XContentBuilder в json , и, наконец, вызывая execute (). ActionGet () , данные отправляются в базу данных.

Последний шаг необходим только потому, что мы используем встроенный экземпляр Elasticsearch (в производстве эта часть не требуется), который обновляет индексы, вызывая метод refresh .

После этого мы можем начать запрашивать Elasticsearch для получения информации из наших документов AsciiDoc .

Давайте начнем с очень простого примера, который возвращает все вставленные документы:

1
SearchResponse response = client.prepareSearch().execute().actionGet();

Далее мы будем искать все документы, которые были написаны Алексом Сото, который в нашем случае является одним.

1
2
3
4
5
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
//....
QueryBuilder matchQuery =  matchQuery("author", "Alex Soto");
 
QueryBuilder matchQuery =  matchQuery("author", "Alexander Soto");

Обратите внимание, что я ищу автора поля строку Другой документ написан Джейсоном . Но интересно сказать, что если вы ищете Александра Сото , тот же документ будет возвращен; Elasticsearch достаточно умен, чтобы знать, что Алекс и Александр очень похожи, поэтому он также возвращает документ.

Еще вопросы, как насчет поиска документов, написанных кем-то, кого зовут Алекс , но не Сото .

1
2
3
4
5
import static org.elasticsearch.index.query.QueryBuilders.fieldQuery;
 
//....
 
QueryBuilder matchQuery =  fieldQuery("author", "+Alex -Soto");

И, конечно, в этом случае результаты не возвращаются. Обратите внимание, что в этом случае мы используем запрос поля вместо термина запроса, и мы используем символы + и – для исключения и включения слов.

Также вы можете найти все документы, которые содержат слово, выпущенное в заголовке .

1
2
3
4
5
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 
//....
 
QueryBuilder matchQuery =  matchQuery("title", "released");

И, наконец, давайте найдем все документы, в которых говорится о выпуске 0.1.2, в данном случае об этом говорит только один документ, а в другом – 0.1.3.

1
QueryBuilder matchQuery = matchQuery("content", "0.1.2");

Теперь нам осталось только отправить запрос в базу данных Elasticsearch , что делается с помощью метода prepareSearch .

01
02
03
04
05
06
07
08
09
10
11
SearchResponse response = client.prepareSearch("docs")
              .setTypes("asciidoctor")
              .setQuery(matchQuery)
              .execute()
              .actionGet();
         
SearchHits hits = response.getHits();
     
for (SearchHit searchHit : hits) {
      System.out.println(searchHit.getSource().get("content"));
}

Обратите внимание, что в этом случае мы печатаем содержимое AsciiDoc через консоль, но вы можете использовать метод asciidoctor.render (String content, Options options) для отображения содержимого в требуемом формате.

Итак, в этом посте мы увидели, как индексировать документы с помощью Elasticsearch , как получить некоторую важную информацию из файлов AsciiDoc с помощью проекта интеграции Asciidoctor-java и, наконец, как выполнить некоторые запросы к вставленным документам. Конечно, в Elasticsearch есть и другие запросы, но цель этого поста не в том, чтобы изучить все возможности Elasticsearch .

Также, как следствие, отметьте, насколько важно использовать формат AsciiDoc для написания ваших документов. Без особых усилий вы можете создать поисковую систему для вашей документации. С другой стороны, представьте весь код, который потребуется для его реализации, используя любой проприетарный двоичный формат, такой как Microsoft Word . Таким образом, мы показали другую причину использовать AsciiDoc вместо других форматов.