# Задача
Cделать поиск по некому множеству элементов, каждый из которых содержит химические данные. Будем считать, что любые два элемента данного множества имеют одинаковый набор полей, который мы будем называть *схемой*. По комбинации любых полей из схемы необходимо уметь осуществлять фильрацию. По любому из полей необходимо уметь осуществлять группировку. В качестве поискового движка необходимо использовать SOLR. 

# Реализация

## Модель данных
В некотором xml-файле мы можем описать схему данных, опосле чего сгенерировать на основании этого описания класс для Java-представления этих данных. В дальнейшем будем обозначать его *Model*.

## API

Приложение состоит из серверной и клиентской части. Рассмотрим клиентский API. Он состоит из части, отвественной за добавление документов в индекс и части, ответственной за поиск. Первая из нрих сводится к методу
```
void addModel(Model model)
```

Опишем вторую часть. Основная идея в том, что запросы должны быть комбинацией неких базовых операций. В качестве таких операций предлагаются (список 1):
- filter
- project
- group
- result

Нетрудно видеть, что данные операции походят на методы, которые используются в stream API, а также в стандартном dataflow для функционального программирования. Однако, полностью реализовать вышеупонянутные аналоги представляется сложной задачей, поскольку, например, методу *map* для работы требуется класс, в который будет осуществляться отображение. Этот класс может быть произвольным и поэтому его может не быть на сервере, соответственно, цепочка с *map* не сможет отработать. Из этого объяснения также понятно, что все операции, которые мы хотим использовать в API должны быть синхронизированы по типам между сервером и клиентом. Перечисленные выше операции можно составлять в цепочку по следующим правилам:
- любая цепочка начинается с *filter*
- любая цепочка заканчивается *result*
- все операции в цепочке встречаются не более одного раза
- каждая операция кроме *filter* и *result* может быть опущена
- порядок операций в цепочке соответствует порядку в списке 1.
 
Примеры валидных цепочек:

- {filter, result};
- {filter, project, result}.

Примеры не валидных цепочек:
- {filter}
- {group, filter, result}

причем любая из операций за исключением *filter* и *result* может быть опущена. На уровне реализации каждой из операций (кроме *result*!) списка 1 соответствует свой класс. Данный класс задает множество возможных продолжений для операции, кроме того он имеет метод result для возвращения финального результата. Далее подробнее рассмотрим каждую операцию списка 1.

### filter
Семантика данного метода соответствует стандартной семантике метода filter : отбор данных по некому условию. Полная сигнатура метода *filter* :
```java
Filter filter(Condition c)
```

Класс *Condition* строится на основании схемы данных и является java-представлением для условий фильтрации. Подробнее о нем позже. Класс *Filter* содержит следующеие методы:
```java
//Конструктор
Filter (Condition cond);

//возможные продолжения цепочки
Project project(Set<FieldName> fields);
Group<Model> group(FieldName field);
List<Model> result();
void result(Consumer<QueryResponse> resp);

//управляющие методы
Filter setOffset(int offset);
Filter setLimit(int limit);
```
Управляющие методы возвращают объект, на котором вызваны для того, чтобы встраивать их вызов в цепочку, например,
```java
solrSearcher.filter(someCondition).setOffset(0).setLimit(10).result();
```

### project
Часто нам не нужны все поля, которые имеет *Model*, в таком случае разумно не возвращать их с сервера для экономии траффика, возвращая лишь необходимые поля, иными словами, "спроецировать" *Model* на более узкое пространство, заданное множеством полей:
```java
Project project(Set<FieldName> fields, ChainData data)
```
*FieldName* является перечислением, которое генерируется на основании схемы и представляет имена полей, использованых в схеме. Кроме того, конструктору необходим аргемент класса *ChainData* -- такой объект нужен на всех этапах цепочки кроме первого, он аккумулирует ограничения на запрос, которые предыдущие шаги цепочки наложили.
Класс *Project* имеет слудеющие методы:
```java
//конструктор
Project (Set<FieldName> fields);

//возможные продолжения
Group<FieldMap> group(FieldName field);
List<FieldMap> result();
void result(Consumer<QueryResponse> resp);
```
Можно заметить, что по сравнению с классом *Filter* результаты методов *group* и *result* имеют другое возвращаемое значение. Это связано с тем, что мы находимся в той точке цепи, где была произведена проекция, а значит группируюся/возвращаются уже не модели, а проекции. 

### group
Метод *group* служит для группировки данных, полученных на предыдущем шаге по некоторому полю. Каждая группа содержит только те элементы, у которых соответствующее поле одинаково. Для данной операции существует класса *Group*, параметризованный типом группируемых элементов:

```java
public class Group<T extends Data> {
//конструктор
Group<T> (T group, ChainData data);

//возможные продолжения
List<Map<String, T>> result();
void result(Consumer<QueryResponse> resp);

//управляющие методы
Group<T extends Data> setMaxGroupSize(int size);
}
```
Здесь *Data* -- интерфейс, который реализует ```Map<FieldName, String>``` а также интерфейс ```FromSolrDoc<Data>```

```java
public interface FromSolrDoc<T> {
    T fromSolrDoc(SolrDocument doc);
}
```
У данного интерфейса есть как минимум две реализации: *Model* и *FieldMap*(обертка над ```Map<FieldName,String>```, умеющая заполняться из *SolrDocument*).

### result
Это терминальный шаг любой цепочки. В нем происходит формирование запроса к SOLR-серверу на основании данных, аккумулированных в *ChainData*(или без *ChainData* если result вызван стазу после первого шага) звеньями, присутствующими в цепочке. Затем запрос отправляется на сервер, после чего результат обрабатывается.
У данного метода два типа сигнатур: 
```java
List<Something> result()
void result(Consumer<QueryResponse> resp)
```
Первый метод используется для получения результатов в виде списка значений (тип значений зависит от этапов, присутствующих в цепочке), второй -- для того, чтобы напрямую (без создания промежуточных структур в памяти) обработать результат, полученный с сервера, например, записать его в файл. 

### Класс Condition
Напомним, что данный класс используется для представления условий, которые могут быть переданы в метод *filter*. *Condition* должен позволять строить типизированное условие на основании некоторой грамматики (типизированность дает большую устойчивость к ошибкам, а также ускоряет разработку благодаря автодополнению). Приведем пример, как примерно может выглядеть такое условие. Допустим, наша схема содержит поля *dateOfDiscovery*, *discoverersName*, *mol*, тогда условие, выбирающее все молекулы, изобретенные человеком "Mendeleev" в неких временных пределах и сходные по структуре с некоторой заданной молекулой может выглядеть так:
```
Condition c = DateOfDiscovery.between(date1, date2).and(DiscoverersName.eq("Mendeleev")).and(Mol.similar(someMol));
```
Видно, что каждому полю соответствует свой класс, который содержит методы формирования условий, возможные для полей данного типа. 
Первый набросок упомянутой грамматики может быть таким (нотация Бэкуса-Наура):
```
<condition> := <stringCondition>           |
               <molCondition>              |
               <numCondition>              |
               <dateCondition>             |
               <condition> and <condition> |
               not <condition>             |
               <condition> or <condition>

<stringCondition> := <stringField> contains <string>   |
                     <stringField> startsWith <string> |
                     <stringField> endsWith <string>   |
                     <stringField> eq <string>      

<molCondition>    := <molField> substructure <molRepresentation> |
                     <molField> similar <molRepresentation>      |
                     <molField> exact <molRepresentation>

<numCondition>   := <numField> less <num>               |
                    <numField> lessOrEqual <num>        |
                    <numField> bigger <num>             |
                    <numField> biggerOrEqual <num>
                    
<dateCondition>   := <dateField> between <date> |
                    <dateField> after <date>    |
                    <dateField> before <date>   
                    
```

## Архитектура

Приложение состоит из следующих частей:

- Сервер
    - SOLR
    - Плагин к SOLR, который позволяет искать по молекулам
- Клиент
    - пакет *core*, в котором определен API
    - пакет *model*, в котором определена java-реализация схемы данных
- Общая часть
    - папка *conf*, в которой находится схема данных (xml-файл)
    - пакет *generators*, содержащий генериторы схема -> модель а также грамматика->Condition.

Заметим, что методы в пакете *core* зависят от классов из пакета *model* (например, метод  *addModel*). Несмтря на это, пакет *core* и *model* разделены. Сделано это по понятным причинам -- при неизменном *core* модель может меняться. В пакете *core* все классы (например, *Model*), зависящие от данных реализованы как dummy-классы, а их реальные реализации загружаются 
из пакета *model*, который в свою очередь генерируется на основании *conf*.








     
