/**
   * This method has the ability to load the data via the HTTP and aggregate the data if the {@link
   * IAggregator} is provided. If the {@link IAggregator} is not provided, the data will be returned
   * not aggregated.
   *
   * <p>This method should be used by all subclasses, because it guards against massive data loading
   * that can make out of memory exceptions on the UI.
   *
   * @param storageIndexQuery Query.
   * @param aggregator {@link IAggregator}
   * @return Return results of a query.
   */
  private List<E> loadData(StorageIndexQuery storageIndexQuery, IAggregator<E> aggregator) {
    List<IStorageDescriptor> descriptors = getIndexingTree().query(storageIndexQuery);
    // sort the descriptors to optimize the number of read operations
    Collections.sort(
        descriptors,
        new Comparator<IStorageDescriptor>() {
          @Override
          public int compare(IStorageDescriptor o1, IStorageDescriptor o2) {
            int channelCompare = Integer.compare(o1.getChannelId(), o2.getChannelId());
            if (channelCompare != 0) {
              return channelCompare;
            } else {
              return Long.compare(o1.getPosition(), o2.getPosition());
            }
          }
        });

    AggregationPerformer<E> aggregationPerformer = null;
    if (null != aggregator) {
      aggregationPerformer = new AggregationPerformer<>(aggregator);
    }
    List<E> returnList = new ArrayList<>();

    int size = 0;
    int count = 0;
    List<IStorageDescriptor> limitedDescriptors = new ArrayList<>();
    for (IStorageDescriptor storageDescriptor : descriptors) {
      // increase count, add descriptor size and update current list
      count++;
      size += storageDescriptor.getSize();
      limitedDescriptors.add(storageDescriptor);

      // if the size is already to big, or we reached end do query
      if ((size > MAX_QUERY_SIZE) || (count == descriptors.size())) {
        // load data and filter with restrictions
        List<E> allData;
        if (localStorageData.isFullyDownloaded()) {
          try {
            allData = dataRetriever.getDataLocally(localStorageData, descriptors);
          } catch (SerializationException e) {
            String msg =
                "Data in the downloaded storage "
                    + localStorageData
                    + " can not be loaded with this version of the inspectIT. Version of the CMR where storage was created is "
                    + localStorageData.getCmrVersion()
                    + ".";
            InspectIT.getDefault().createErrorDialog(msg, e, -1);
            return Collections.emptyList();
          } catch (IOException e) {
            InspectIT.getDefault()
                .createErrorDialog("Exception occurred trying to load the data.", e, -1);
            return Collections.emptyList();
          }
        } else {
          try {
            allData =
                dataRetriever.getDataViaHttp(
                    getCmrRepositoryDefinition(), localStorageData, limitedDescriptors);
          } catch (SerializationException e) {
            String msg =
                "Data in the remote storage "
                    + localStorageData
                    + " can not be loaded with this version of the inspectIT. Version of the CMR where storage was created is "
                    + localStorageData.getCmrVersion()
                    + ".";
            InspectIT.getDefault().createErrorDialog(msg, e, -1);
            return Collections.emptyList();
          } catch (IOException e) {
            InspectIT.getDefault()
                .createErrorDialog("Exception occurred trying to load the data.", e, -1);
            return Collections.emptyList();
          }
        }
        List<E> passedData = getRestrictionsPassedList(allData, storageIndexQuery);

        // if we need to aggregate then do so, otherwise just add to result list
        if (null != aggregationPerformer) {
          aggregationPerformer.processCollection(passedData);
        } else {
          returnList.addAll(passedData);
        }

        // reset the size and current list
        size = 0;
        limitedDescriptors.clear();
      }
    }

    // aggregate if needed
    if (null != aggregator) {
      returnList = aggregationPerformer.getResultList();
    }

    return returnList;
  }
  /**
   * This method executes the query in way that it first checks if wanted data is already cached. If
   * not method has the ability to load the data via the HTTP or locally and aggregate the data if
   * the {@link IAggregator} is provided. If the {@link IAggregator} is not provided, the data will
   * be returned not aggregated.
   *
   * <p>In addition it will try to cache the results if they are not yet cached.
   *
   * <p>This method should be used by all subclasses, because it guards against massive data loading
   * that can make out of memory exceptions on the UI.
   *
   * @param storageIndexQuery Query.
   * @param aggregator {@link IAggregator}
   * @param comparator If supplied the final result list will be sorted by this comparator.
   * @param limit Limit the number of results by given number. Value <code>-1</code> means no limit.
   * @return Return results of a query.
   */
  protected List<E> executeQuery(
      StorageIndexQuery storageIndexQuery,
      IAggregator<E> aggregator,
      Comparator<? super E> comparator,
      int limit) {
    List<E> returnList = null;
    // check if this can be cached
    if (storageManager.canBeCached(storageIndexQuery, aggregator)) {
      int hash = storageManager.getCachedDataHash(storageIndexQuery, aggregator);
      if (!localStorageData.isFullyDownloaded()) {
        // check if it s cached on the CMR
        StorageData storageData = new StorageData(localStorageData);
        try {
          returnList =
              dataRetriever.getCachedDataViaHttp(getCmrRepositoryDefinition(), storageData, hash);
        } catch (BusinessException | IOException | SerializationException e) { // NOPMD // NOCHK
          // ignore cause we can still load results in other way
        }

        if (null == returnList) {
          // if not we load data regular way
          returnList = loadData(storageIndexQuery, aggregator);

          // and cache it on the CMR if we get something
          if (CollectionUtils.isNotEmpty(returnList)) {
            cacheQueryResultOnCmr(getCmrRepositoryDefinition(), storageData, returnList, hash);
          }
        }
      } else {
        try {
          returnList = dataRetriever.getCachedDataLocally(localStorageData, hash);
        } catch (IOException | SerializationException e) { // NOPMD NOCHK
          // ignore cause we can still load results in other way
        }

        if (null == returnList) {
          // if not we load data regular way
          returnList = loadData(storageIndexQuery, aggregator);

          // and cache it locally if we get something
          if (CollectionUtils.isNotEmpty(returnList)) {
            cacheQueryResultLocally(localStorageData, returnList, hash);
          }
        }
      }
    } else {
      returnList = loadData(storageIndexQuery, aggregator);
    }

    // sort if needed
    if (null != comparator) {
      Collections.sort(returnList, comparator);
    }

    // limit the size if needed
    if ((limit > -1) && (returnList.size() > limit)) {
      returnList = returnList.subList(0, limit);
    }

    return returnList;
  }