All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.operate.store.elasticsearch.dao.GenericDAO Maven / Gradle / Ivy

/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.operate.store.elasticsearch.dao;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.operate.entities.OperateEntity;
import io.camunda.operate.exceptions.OperateRuntimeException;
import io.camunda.operate.schema.indices.IndexDescriptor;
import io.camunda.operate.store.elasticsearch.dao.response.AggregationResponse;
import io.camunda.operate.store.elasticsearch.dao.response.InsertResponse;
import io.camunda.operate.store.elasticsearch.dao.response.SearchResponse;
import io.camunda.operate.util.ElasticsearchUtil;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GenericDAO {

  private static final Logger LOGGER = LoggerFactory.getLogger(GenericDAO.class);
  private RestHighLevelClient esClient;
  private ObjectMapper objectMapper;
  private I index;
  private Class typeOfEntity;

  private GenericDAO() {
    // No constructor, only Builder class should be used
  }

  /**
   * Constructor package accessible for implementations to access
   *
   * @param objectMapper
   * @param index
   * @param esClient
   */
  @SuppressWarnings("unchecked")
  GenericDAO(ObjectMapper objectMapper, I index, RestHighLevelClient esClient) {
    if (objectMapper == null) {
      throw new IllegalStateException("ObjectMapper can't be null");
    }
    if (index == null) {
      throw new IllegalStateException("Index can't be null");
    }
    if (esClient == null) {
      throw new IllegalStateException("ES Client can't be null");
    }

    this.objectMapper = objectMapper;
    this.index = index;
    this.esClient = esClient;
    this.typeOfEntity =
        (Class)
            ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
  }

  /**
   * Returns the Elasticsearch IndexRequest. This is for a very specific use case. We'd rather leave
   * this DAO class clean and not return any elastic class
   *
   * @param entity
   * @return insert request
   */
  public IndexRequest buildESIndexRequest(T entity) {
    try {
      return new IndexRequest(index.getFullQualifiedName())
          .id(entity.getId())
          .source(objectMapper.writeValueAsString(entity), XContentType.JSON);
    } catch (JsonProcessingException e) {
      throw new OperateRuntimeException("error building Index/InserRequest");
    }
  }

  public InsertResponse insert(T entity) {
    try {
      final IndexRequest request = buildESIndexRequest(entity);
      final IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);
      if (response.status() != RestStatus.CREATED) {
        return InsertResponse.failure();
      }

      return InsertResponse.success();
    } catch (IOException e) {
      LOGGER.error(e.getMessage(), e);
    }

    throw new OperateRuntimeException("Error while trying to upsert entity: " + entity);
  }

  public SearchResponse search(Query query) {
    final SearchSourceBuilder source =
        SearchSourceBuilder.searchSource()
            .query(query.getQueryBuilder())
            .aggregation(query.getAggregationBuilder());
    final SearchRequest searchRequest =
        new SearchRequest(index.getFullQualifiedName())
            .indicesOptions(IndicesOptions.lenientExpandOpen())
            .source(source);
    try {
      final List hits =
          ElasticsearchUtil.scroll(searchRequest, typeOfEntity, objectMapper, esClient);
      return new SearchResponse<>(false, hits);
    } catch (IOException e) {
      LOGGER.error("Error searching at index: " + index, e);
    }
    return new SearchResponse<>(true);
  }

  public AggregationResponse searchWithAggregation(Query query) {
    final SearchSourceBuilder source =
        SearchSourceBuilder.searchSource()
            .query(query.getQueryBuilder())
            .aggregation(query.getAggregationBuilder());
    final SearchRequest searchRequest =
        new SearchRequest(index.getFullQualifiedName())
            .indicesOptions(IndicesOptions.lenientExpandOpen())
            .source(source);
    try {

      final Aggregations aggregations =
          esClient.search(searchRequest, RequestOptions.DEFAULT).getAggregations();
      if (aggregations == null) {
        throw new OperateRuntimeException("Search with aggregation returned no aggregation");
      }

      final Aggregation group = aggregations.get(query.getGroupName());
      if (!(group instanceof ParsedStringTerms)) {
        throw new OperateRuntimeException("Unexpected response for aggregations");
      }

      final ParsedStringTerms terms = (ParsedStringTerms) group;
      final List buckets =
          (List) terms.getBuckets();

      final List values =
          buckets.stream()
              .map(
                  it ->
                      new AggregationResponse.AggregationValue(
                          String.valueOf(it.getKey()), it.getDocCount()))
              .collect(Collectors.toList());

      final long sumOfOtherDocCounts =
          ((ParsedStringTerms) group).getSumOfOtherDocCounts(); // size of documents not in result
      final long total = sumOfOtherDocCounts + values.size(); // size of result + other docs
      return new AggregationResponse(false, values, total);
    } catch (IOException e) {
      LOGGER.error("Error searching at index: " + index, e);
    }
    return new AggregationResponse(true);
  }

  /**
   * Mostly used for test purposes
   *
   * @param  TasklistEntity - Elastic Search doc
   * @param  IndexDescriptor - which index to persist the doc
   */
  public static class Builder {
    private ObjectMapper objectMapper;
    private RestHighLevelClient esClient;
    private I index;

    public Builder() {}

    public Builder objectMapper(ObjectMapper objectMapper) {
      this.objectMapper = objectMapper;
      return this;
    }

    public Builder index(I index) {
      this.index = index;
      return this;
    }

    public Builder esClient(RestHighLevelClient esClient) {
      this.esClient = esClient;
      return this;
    }

    public GenericDAO build() {
      if (objectMapper == null) {
        throw new IllegalStateException("ObjectMapper can't be null");
      }
      if (index == null) {
        throw new IllegalStateException("Index can't be null");
      }
      if (esClient == null) {
        throw new IllegalStateException("ES Client can't be null");
      }

      return new GenericDAO<>(objectMapper, index, esClient);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy