/**
   * Retrieves data from a table, which matches the primary key values batch.
   *
   * @param tableName The name of the table to search on
   * @param numPartitionsHint The best effort number of splits this should return
   * @param columns The list of columns to required in results, null if all needs to be returned
   * @param valuesBatchBeans The values batch containing the key values of primary keys to match
   * @return An array of {@link RecordBean} objects, which represents individual data sets in their
   *     local location
   * @throws AnalyticsWebServiceException
   */
  public RecordBean[] getWithKeyValues(
      String tableName, int numPartitionsHint, String[] columns, ValuesBatchBean[] valuesBatchBeans)
      throws AnalyticsWebServiceException {

    try {
      List<String> columnList = null;
      if (columns != null && columns.length != 0) {
        columnList = Arrays.asList(columns);
      }
      List<Map<String, Object>> valuesBatch =
          Utils.getValuesBatch(
              valuesBatchBeans, analyticsDataAPI.getTableSchema(getUsername(), tableName));
      List<Record> records =
          AnalyticsDataServiceUtils.listRecords(
              analyticsDataAPI,
              analyticsDataAPI.getWithKeyValues(
                  getUsername(), tableName, numPartitionsHint, columnList, valuesBatch));
      List<RecordBean> recordBeans = Utils.createRecordBeans(records);
      RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
      return recordBeans.toArray(resultRecordBeans);
    } catch (Exception e) {
      logger.error(
          "Unable to get records from table[" + tableName + "] due to " + e.getMessage(), e);
      throw new AnalyticsWebServiceException(
          "Unable to get record from table[" + tableName + "] due to " + e.getMessage(), e);
    }
  }
 /**
  * Returns the drill down results of a search query, given AnalyticsDrillDownRequestBean
  *
  * @param drillDownRequest The drilldown object which contains the drilldown information
  * @return An arrays of {@link RecordBean}s
  * @throws AnalyticsWebServiceException
  */
 public RecordBean[] drillDownSearch(AnalyticsDrillDownRequestBean drillDownRequest)
     throws AnalyticsWebServiceException {
   try {
     List<SearchResultEntry> searchResults =
         analyticsDataAPI.drillDownSearch(
             getUsername(), getAnalyticsDrillDownRequest(drillDownRequest));
     List<String> recordIds = Utils.getRecordIds(searchResults);
     List<Record> records =
         AnalyticsDataServiceUtils.listRecords(
             analyticsDataAPI,
             analyticsDataAPI.get(
                 getUsername(),
                 drillDownRequest.getTableName(),
                 DEFAULT_NUM_PARTITIONS_HINT,
                 null,
                 recordIds));
     Map<String, RecordBean> recordBeanMap = Utils.createRecordBeansKeyedWithIds(records);
     List<RecordBean> recordBeans = Utils.getSortedRecordBeans(recordBeanMap, searchResults);
     RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
     return recordBeans.toArray(resultRecordBeans);
   } catch (Exception e) {
     logger.error("Unable to get drill down information due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get drill down information due to " + e.getMessage(), e);
   }
 }
 /**
  * Returns the subcategories of a facet field, given CategoryDrillDownRequestBean
  *
  * @param drillDownRequest The category drilldown object which contains the category drilldown
  *     information
  * @return SubCategoriesBean instance that contains the immediate category information
  * @throws AnalyticsWebServiceException
  */
 public SubCategoriesBean drillDownCategories(CategoryDrillDownRequestBean drillDownRequest)
     throws AnalyticsWebServiceException {
   SubCategoriesBean subCategoriesBean = new SubCategoriesBean();
   try {
     SubCategories subCategories =
         analyticsDataAPI.drillDownCategories(
             getUsername(), getCategoryDrillDownRequest(drillDownRequest));
     subCategoriesBean.setPath(subCategories.getPath());
     if (subCategories.getCategories() != null) {
       subCategoriesBean.setCategories(getSearchResultEntryBeans(subCategories));
     }
   } catch (Exception e) {
     logger.error(
         "Unable to get drill down category information for table["
             + drillDownRequest.getTableName()
             + "] and "
             + "field["
             + drillDownRequest.getFieldName()
             + "] due to "
             + e.getMessage(),
         e);
     throw new AnalyticsWebServiceException(
         "Unable to get drill down category information for "
             + "table["
             + drillDownRequest.getTableName()
             + "] and "
             + "field["
             + drillDownRequest.getFieldName()
             + "] due to "
             + e.getMessage(),
         e);
   }
   return subCategoriesBean;
 }
  /**
   * Retrieves data from a table with given ids.
   *
   * @param tableName The name of the table to search on
   * @param numPartitionsHint The best effort number of splits this should return
   * @param columns The list of columns to required in results, null if all needs to be returned
   * @param ids The list of ids of the records to be read
   * @return An array of {@link RecordBean} objects, which contains individual data sets in their
   *     local location
   * @throws AnalyticsWebServiceException
   */
  public RecordBean[] getById(
      String tableName, int numPartitionsHint, String[] columns, String[] ids)
      throws AnalyticsWebServiceException {
    try {
      List<String> columnList = null;
      if (columns != null) {
        columnList = Arrays.asList(columns);
      }
      List<String> idList = null;
      if (ids != null) {
        idList = Arrays.asList(ids);
      }

      List<Record> records =
          AnalyticsDataServiceUtils.listRecords(
              analyticsDataAPI,
              analyticsDataAPI.get(
                  getUsername(), tableName, numPartitionsHint, columnList, idList));
      List<RecordBean> recordBeans = Utils.createRecordBeans(records);
      RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
      return recordBeans.toArray(resultRecordBeans);
    } catch (Exception e) {
      logger.error(
          "Unable to get records from table[" + tableName + "] due to " + e.getMessage(), e);
      throw new AnalyticsWebServiceException(
          "Unable to get record from table[" + tableName + "] due to " + e.getMessage(), e);
    }
  }
 /**
  * Destroys and frees any resources taken up by the analytics data service implementation.
  *
  * @throws AnalyticsWebServiceException
  */
 public void destroy() throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.destroy();
   } catch (Exception e) {
     logger.error("An exception occurred: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("An exception occurred: " + e.getMessage(), e);
   }
 }
 /**
  * This method waits until the current indexing operations for the system is done.
  *
  * @param maxWait Maximum amount of time in milliseconds, -getUsername() for infinity
  * @throws AnalyticsWebServiceException
  */
 public void waitForIndexing(long maxWait) throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.waitForIndexing(maxWait);
   } catch (Exception e) {
     logger.error("An exception occurred: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("An exception occurred: " + e.getMessage(), e);
   }
 }
 /**
  * Check whether under laying data layer implementation support for pagination or not.
  *
  * @return boolean true or false based on under laying data layer implementation
  */
 public boolean isPaginationSupported(String recordStoreName) throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.isPaginationSupported(recordStoreName);
   } catch (Exception e) {
     logger.error("An exception occurred: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("An exception occurred: " + e.getMessage(), e);
   }
 }
 /**
  * Given the start time and end time, this method will re-index the records of a table.
  *
  * @param tableName the table name of which the records are being re-indexed.
  * @param startTime lowerbound of the timestamp range of records
  * @param endTime upperbound of the timestamp range of records
  * @throws AnalyticsWebServiceException *
  */
 public void reIndex(String tableName, long startTime, long endTime)
     throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.reIndex(getUsername(), tableName, startTime, endTime);
   } catch (Exception e) {
     logger.error("An exception occurred: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("An exception occurred: " + e.getMessage(), e);
   }
 }
 /**
  * This method waits until the current indexing operations for a given table is done.
  *
  * @param tableName table being checked
  * @param maxWait Maximum amount of time in milliseconds, -getUsername() for infinity
  * @throws AnalyticsWebServiceException
  */
 public void waitForIndexingForTable(String tableName, long maxWait)
     throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.waitForIndexing(getUsername(), tableName, maxWait);
   } catch (Exception e) {
     logger.error("An exception occurred: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("An exception occurred: " + e.getMessage(), e);
   }
 }
 /**
  * Lists all the record stores available in the system.
  *
  * @return The list of record store names
  */
 public List<String> listRecordStoreNames() throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.listRecordStoreNames();
   } catch (Exception e) {
     logger.error("Unable to get record store names: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get record store names: " + e.getMessage(), e);
   }
 }
 /**
  * Retrieves the table schema for the given table.
  *
  * @param tableName The table name
  * @return The schema of the table
  * @throws AnalyticsWebServiceException
  */
 public AnalyticsSchemaBean getTableSchema(String tableName) throws AnalyticsWebServiceException {
   try {
     return Utils.createTableSchemaBean(analyticsDataAPI.getTableSchema(getUsername(), tableName));
   } catch (Exception e) {
     logger.error("Unable to get table schema[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get table schema for table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
 /**
  * Checks if the specified table with the given category and name exists.
  *
  * @param tableName The table name
  * @return true if the table exists, false otherwise
  * @throws AnalyticsWebServiceException
  */
 public boolean tableExists(String tableName) throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.tableExists(getUsername(), tableName);
   } catch (Exception e) {
     logger.error("Unable to check table[" + tableName + "] exist due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to check status of the table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
 /**
  * Returns the record store name given the table information.
  *
  * @param tableName The table name
  * @return The record store name
  * @throws AnalyticsWebServiceException
  */
 public String getRecordStoreNameByTable(String tableName) throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.getRecordStoreNameByTable(getUsername(), tableName);
   } catch (Exception e) {
     logger.error(
         "Unable to get record store name for table[" + tableName + "]: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get record store name for table[" + tableName + "]: " + e.getMessage(), e);
   }
 }
 /**
  * Returns the search count of results of a given search query.
  *
  * @param tableName The table name
  * @param query The search query
  * @return The count of results
  * @throws AnalyticsWebServiceException
  */
 public int searchCount(String tableName, String query) throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.searchCount(getUsername(), tableName, query);
   } catch (Exception e) {
     logger.error(
         "Unable to get search count for table[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get search count from table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
 /**
  * Clears all the indices for the given table.
  *
  * @param tableName The table name
  * @throws AnalyticsWebServiceException
  */
 public void clearIndices(String tableName) throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.clearIndexData(getUsername(), tableName);
   } catch (Exception e) {
     logger.error(
         "Unable to clear indices from table[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to clear indices from table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
 /**
  * Delete data in a table with given ids.
  *
  * @param tableName The name of the table to search on
  * @param ids The list of ids of the records to be deleted
  * @throws AnalyticsWebServiceException
  */
 public void deleteByIds(String tableName, String[] ids) throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.delete(getUsername(), tableName, Arrays.asList(ids));
   } catch (Exception e) {
     logger.error(
         "Unable to delete records from table[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to delete record from table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
  /**
   * Retrieves data from a table, with a given range.
   *
   * @param tableName The name of the table to search on
   * @param numPartitionsHint The best effort number of splits this should return
   * @param columns The list of columns to required in results, null if all needs to be returned
   * @param timeFrom The starting time to get records from, inclusive, relatively to epoch,
   *     Long.MIN_VALUE should signal, this restriction to be disregarded
   * @param timeTo The ending time to get records to, non-inclusive, relatively to epoch,
   *     Long.MAX_VALUE should signal, this restriction to be disregarded
   * @param recordsFrom The paginated index from value, zero based, inclusive
   * @param recordsCount The paginated records count to be read, -getUsername() for infinity
   * @return An array of {@link RecordBean} objects, which represents individual data sets in their
   *     local location
   * @throws AnalyticsWebServiceException
   */
  public RecordBean[] getByRange(
      String tableName,
      int numPartitionsHint,
      String[] columns,
      long timeFrom,
      long timeTo,
      int recordsFrom,
      int recordsCount)
      throws AnalyticsWebServiceException {

    try {
      List<String> columnList = null;
      if (columns != null && columns.length != 0) {
        columnList = new ArrayList<>(Arrays.asList(columns));
      }
      int originalFrom = recordsFrom;
      if (!isPaginationSupported(getRecordStoreNameByTable(tableName))) {
        recordsCount = recordsFrom + recordsCount;
        recordsFrom = 0;
      }
      AnalyticsDataResponse response =
          analyticsDataAPI.get(
              getUsername(),
              tableName,
              numPartitionsHint,
              columnList,
              timeFrom,
              timeTo,
              recordsFrom,
              recordsCount);
      List<Record> records;
      if (!isPaginationSupported(getRecordStoreNameByTable(tableName))) {
        Iterator<Record> itr =
            AnalyticsDataServiceUtils.responseToIterator(analyticsDataAPI, response);
        records = new ArrayList<>();
        for (int i = 0; i < originalFrom && itr.hasNext(); i++) {
          itr.next();
        }
        for (int i = 0; i < recordsCount && itr.hasNext(); i++) {
          records.add(itr.next());
        }
      } else {
        records = AnalyticsDataServiceUtils.listRecords(analyticsDataAPI, response);
      }
      List<RecordBean> recordBeans = Utils.createRecordBeans(records);
      RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
      return recordBeans.toArray(resultRecordBeans);
    } catch (Exception e) {
      logger.error(
          "Unable to get records from table[" + tableName + "] due to " + e.getMessage(), e);
      throw new AnalyticsWebServiceException(
          "Unable to get record from table[" + tableName + "] due to " + e.getMessage(), e);
    }
  }
 /**
  * Returns the count of results of a search query, given AnalyticsDrillDownRequestBean
  *
  * @param drillDownRequest The drilldown object which contains the drilldown information
  * @return the count of the records which match the drilldown query
  * @throws AnalyticsWebServiceException
  */
 public double drillDownSearchCount(AnalyticsDrillDownRequestBean drillDownRequest)
     throws AnalyticsWebServiceException {
   try {
     return analyticsDataAPI.drillDownSearchCount(
         getUsername(), getAnalyticsDrillDownRequest(drillDownRequest));
   } catch (Exception e) {
     logger.error("Unable to get drill down search count information due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get drill down search count information due to " + e.getMessage(), e);
   }
 }
 /**
  * Searches the data with a given search query.
  *
  * @param tableName The table name
  * @param query The search query
  * @param start The start location of the result, 0 based
  * @param count The maximum number of result entries to be returned
  * @return An arrays of {@link RecordBean}s
  * @throws AnalyticsWebServiceException
  */
 public RecordBean[] search(String tableName, String query, int start, int count)
     throws AnalyticsWebServiceException {
   try {
     List<SearchResultEntry> searchResults =
         analyticsDataAPI.search(getUsername(), tableName, query, start, count, null);
     List<String> recordIds = Utils.getRecordIds(searchResults);
     List<Record> records =
         AnalyticsDataServiceUtils.listRecords(
             analyticsDataAPI,
             analyticsDataAPI.get(
                 getUsername(), tableName, DEFAULT_NUM_PARTITIONS_HINT, null, recordIds));
     Map<String, RecordBean> recordBeanMap = Utils.createRecordBeansKeyedWithIds(records);
     List<RecordBean> recordBeans = Utils.getSortedRecordBeans(recordBeanMap, searchResults);
     RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
     return recordBeans.toArray(resultRecordBeans);
   } catch (Exception e) {
     logger.error(
         "Unable to get search result for table[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get search result from table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
  /**
   * Sets the table schema for a table
   *
   * @param schemaBean The schema bean representing the schema
   * @throws AnalyticsWebServiceException
   */
  public void setTableSchema(String tableName, AnalyticsSchemaBean schemaBean)
      throws AnalyticsWebServiceException {
    try {

      AnalyticsSchema schema = Utils.getAnalyticsSchema(schemaBean);

      analyticsDataAPI.setTableSchema(getUsername(), tableName, schema);
    } catch (Exception e) {
      logger.error("unable to set the schema for table: " + tableName + ", " + e.getMessage(), e);
      throw new AnalyticsWebServiceException(
          "unable to set the schema: " + tableName + ", " + e.getMessage(), e);
    }
  }
 /**
  * Returns a list of records containing the aggregate values computed over the given fields map ,
  * grouped by a predefined FACET field for multiple tables.
  *
  * @param requests The Array of AnalyticsAggregateRequests representing multiple tables
  *     groupByField is used to group the records. It should be a facet field created by the
  *     grouping fields. fields attribute represents the record fields and the respective aggregate
  *     function. aliases represents the output field names for aggregated values over the fields.
  * @return Array of AggregatedObjects containing arrays of records of which the record values will
  *     be the aggregate values of the given fields
  */
 public AggregateResponse[] searchMultiTablesWithAggregates(AnalyticsAggregateRequest[] requests)
     throws AnalyticsWebServiceException {
   try {
     AggregateRequest[] aggregateRequest = Utils.getAggregateRequests(requests);
     List<AnalyticsIterator<Record>> iterators =
         analyticsDataAPI.searchWithAggregates(getUsername(), aggregateRequest);
     AggregateResponse[] responses = Utils.createAggregateResponses(iterators);
     return responses;
   } catch (Exception e) {
     logger.error("unable to search with aggregates for  multiple tables: " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "unable to search with aggregates for multiple tables: " + e.getMessage(), e);
   }
 }
 /**
  * Lists all the current tables with the given category.
  *
  * @return The list of table names
  * @throws AnalyticsWebServiceException
  */
 public String[] listTables() throws AnalyticsWebServiceException {
   try {
     List<String> tables = analyticsDataAPI.listTables(getUsername());
     if (tables != null) {
       String[] tableNameArray = new String[tables.size()];
       return tables.toArray(tableNameArray);
     } else {
       return new String[0];
     }
   } catch (Exception e) {
     logger.error("Unable to get table name list due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException("Unable to list tables due to " + e.getMessage(), e);
   }
 }
 /**
  * Returns the number of records in the table with the given category and name.
  *
  * @param tableName The name of the table to get the count from
  * @param timeFrom The starting time to consider the count from, inclusive, relatively to epoch,
  *     Long.MIN_VALUE should signal, this restriction to be disregarded
  * @param timeTo The ending time to consider the count to, non-inclusive, relatively to epoch,
  *     Long.MAX_VALUE should signal, this restriction to be disregarded
  * @return The record count
  * @throws AnalyticsWebServiceException
  */
 public long getRecordCount(String tableName, long timeFrom, long timeTo)
     throws AnalyticsWebServiceException {
   try {
     long recordCount =
         analyticsDataAPI.getRecordCount(getUsername(), tableName, timeFrom, timeTo);
     if (recordCount == -1) {
       if (logger.isDebugEnabled()) {
         logger.debug("Retrieving record count is not supported for table:" + tableName);
       }
     }
     return recordCount;
   } catch (Exception e) {
     logger.error(
         "Unable to get record count for table[" + tableName + "] due to " + e.getMessage(), e);
     throw new AnalyticsWebServiceException(
         "Unable to get record count for table[" + tableName + "] due to " + e.getMessage(), e);
   }
 }
  /**
   * Returns a list of records containing the aggregate values computed over the given fields map ,
   * grouped by a predefined FACET field.
   *
   * @param request The inputs required for performing aggregation. groupByField is used to group
   *     the records. It should be a facet field created by the grouping fields. fields attribute
   *     represents the record fields and the respective aggregate function. aliases represents the
   *     output field names for aggregated values over the fields.
   * @return List of records of which the record values will be the aggregate values of the given
   *     fields
   */
  public RecordBean[] searchWithAggregates(AnalyticsAggregateRequest request)
      throws AnalyticsWebServiceException {
    try {

      AggregateRequest aggregateRequest = Utils.getAggregateRequest(request);
      AnalyticsIterator<Record> iterator =
          analyticsDataAPI.searchWithAggregates(getUsername(), aggregateRequest);
      List<RecordBean> recordBeans = Utils.createRecordBeans(Utils.createList(iterator));
      RecordBean[] resultRecordBeans = new RecordBean[recordBeans.size()];
      return recordBeans.toArray(resultRecordBeans);
    } catch (Exception e) {
      logger.error(
          "unable to search with aggregates for table: "
              + request.getTableName()
              + ", "
              + e.getMessage(),
          e);
      throw new AnalyticsWebServiceException(
          "unable to search with aggregates: " + request.getTableName() + ", " + e.getMessage(), e);
    }
  }
 /**
  * Creates a table, if not already there, where the columns are not defined here, but can contain
  * any arbitrary number of columns when data is added. The table names are not case sensitive.
  *
  * @param recordStoreName The name of the target record store to store the table at
  * @param tableName The name of the table to be created
  * @throws AnalyticsWebServiceException
  */
 public void createTable(String recordStoreName, String tableName)
     throws AnalyticsWebServiceException {
   try {
     analyticsDataAPI.createTable(getUsername(), recordStoreName, tableName);
   } catch (Exception e) {
     logger.error(
         "Unable to create table["
             + tableName
             + "] in record store["
             + recordStoreName
             + "]: "
             + e.getMessage(),
         e);
     throw new AnalyticsWebServiceException(
         "Unable to create table["
             + tableName
             + "] in record store["
             + recordStoreName
             + "]: "
             + e.getMessage(),
         e);
   }
 }