/** * Get a registered FeatureFlag by name. * * @param name Name of the FeatureFlag to get * * @return The feature flag with the given name * @throws BadApiRequestException if no feature flag has been registered for that name */ public FeatureFlag forName(String name) throws BadApiRequestException { FeatureFlag flag = NAMES_TO_VALUES.get(name.toUpperCase(Locale.ENGLISH)); return flag != null ? flag : Utils.insteadThrowRuntime( new BadApiRequestException("Invalid feature flag: " + name) ); }
@Override public void validateDuplicateMetrics(ArrayNode metricsJsonArray) { Set<String> metricsList = new HashSet<>(); List<String> duplicateMetrics = new ArrayList<>(); for (int i = 0; i < metricsJsonArray.size(); i++) { String metricName = metricsJsonArray.get(i).get("name").asText(); boolean status = metricsList.add(metricName); if (!status) { duplicateMetrics.add(metricName); } } if (!duplicateMetrics.isEmpty()) { LOG.debug(DUPLICATE_METRICS_IN_API_REQUEST.logFormat(duplicateMetrics.toString())); throw new BadApiRequestException(DUPLICATE_METRICS_IN_API_REQUEST.format(duplicateMetrics.toString())); } }
/** * Confirm count size is non negative. * * @param countRequest The value of the count from the request (if any) * @param count The bound value for the count */ protected void validateCount(String countRequest, int count) { // This is the validation part for count that is inlined here because currently it is very brief. if (count < 0) { LOG.debug(INTEGER_INVALID.logFormat(countRequest, "count")); throw new BadApiRequestException(INTEGER_INVALID.logFormat(countRequest, "count")); } } /**
/** * Builds the paginationParameters object, if the request provides both a perPage and page field. * * @param perPage The number of rows per page. * @param page The page to display. * * @return An Optional wrapping a PaginationParameters if both 'perPage' and 'page' exist. * @throws BadApiRequestException if 'perPage' or 'page' is not a positive integer, or if either one is empty * string but not both. */ protected Optional<PaginationParameters> generatePaginationParameters(String perPage, String page) throws BadApiRequestException { try { return "".equals(perPage) && "".equals(page) ? Optional.empty() : Optional.of(new PaginationParameters(perPage, page)); } catch (BadPaginationException invalidParameters) { throw new BadApiRequestException(invalidParameters.getMessage()); } }
/** * Parses the requested input String by converting it to an integer, while treating null as zero. * * @param value The requested integer value as String. * @param parameterName The parameter name that corresponds to the requested integer value. * * @return The integer corresponding to {@code value} or zero if {@code value} is null. * @throws BadApiRequestException if the input String can not be parsed as an integer. */ protected int generateInteger(String value, String parameterName) throws BadApiRequestException { try { return value == null ? 0 : Integer.parseInt(value); } catch (NumberFormatException nfe) { LOG.debug(INTEGER_INVALID.logFormat(value, parameterName), nfe); throw new BadApiRequestException(INTEGER_INVALID.logFormat(value, parameterName), nfe); } }
@Override @Deprecated public Filter getQueryFilter() { try (TimedPhase timer = RequestLog.startTiming("BuildingDruidFilter")) { return filterBuilder.buildFilters(this.apiFilters); } catch (FilterBuilderException e) { LOG.debug(e.getMessage()); throw new BadApiRequestException(e); } }
/** * Given a filter String, generates a Set of ApiJobStoreFilters. This method will throw a BadApiRequestException if * the filter String cannot be parsed into ApiJobStoreFilters successfully. * * @param filterQuery Expects a URL filterQuery String that may contain multiple filters separated by * comma. The format of a filter String is : * (JobField name)-(operation)[(value or comma separated values)]? * * @return A Set of ApiJobStoreFilters */ public LinkedHashSet<JobRowFilter> buildJobStoreFilter(@NotNull String filterQuery) { // split on '],' to get list of filters return Arrays.stream(filterQuery.split(COMMA_AFTER_BRACKET_PATTERN)) .map( filter -> { try { return new JobRowFilter(filter); } catch (BadFilterException e) { throw new BadApiRequestException(e.getMessage(), e); } } ) .collect(Collectors.toCollection(LinkedHashSet<JobRowFilter>::new)); }
/** * Confirm the top N bucket size (if any) is valid. * * @param topNRequest The value of the count from the request (if any) * @param sorts collection of sorted columns * @param topN The bound value for the count */ protected void validateTopN(String topNRequest, int topN, LinkedHashSet<OrderByColumn> sorts) { // This is the validation part for topN that is inlined here because currently it is very brief. if (topN < 0) { LOG.debug(INTEGER_INVALID.logFormat(topNRequest, "topN")); throw new BadApiRequestException(INTEGER_INVALID.logFormat(topNRequest, "topN")); } else if (topN > 0 && this.sorts.isEmpty()) { LOG.debug(TOP_N_UNSORTED.logFormat(topNRequest)); throw new BadApiRequestException(TOP_N_UNSORTED.format(topNRequest)); } }
/** * Extract valid sort direction. * * @param columnWithDirection Column and its sorting direction * * @return Sorting direction. If no direction provided then the default one will be DESC */ protected SortDirection getSortDirection(List<String> columnWithDirection) { try { return columnWithDirection.size() == 2 ? SortDirection.valueOf(columnWithDirection.get(1).toUpperCase(Locale.ENGLISH)) : SortDirection.DESC; } catch (IllegalArgumentException ignored) { String sortDirectionName = columnWithDirection.get(1); LOG.debug(SORT_DIRECTION_INVALID.logFormat(sortDirectionName)); throw new BadApiRequestException(SORT_DIRECTION_INVALID.format(sortDirectionName)); } }
/** * Parses the asyncAfter parameter into a long describing how long the user is willing to wait for the results of a * synchronous request before the request should become asynchronous. * * @param asyncAfterString asyncAfter should be either a string representation of a long, or the String never * * @return A long describing how long the user is willing to wait * * @throws BadApiRequestException if asyncAfterString is neither the string representation of a natural number, nor * {@code never} */ protected long generateAsyncAfter(String asyncAfterString) throws BadApiRequestException { try { return asyncAfterString.equals(SYNCHRONOUS_REQUEST_FLAG) ? SYNCHRONOUS_ASYNC_AFTER_VALUE : asyncAfterString.equals(ASYNCHRONOUS_REQUEST_FLAG) ? ASYNCHRONOUS_ASYNC_AFTER_VALUE : Long.parseLong(asyncAfterString); } catch (NumberFormatException e) { LOG.debug(INVALID_ASYNC_AFTER.logFormat(asyncAfterString), e); throw new BadApiRequestException(INVALID_ASYNC_AFTER.format(asyncAfterString), e); } } }
/** * Bind the table name against a Logical table in the table dictionary. * * @param tableName Name of the logical table from the query * @param table The bound logical table for this query * @param granularity The granularity for this request * @param logicalTableDictionary Dictionary to resolve logical tables against. * * @throws BadApiRequestException if invalid */ protected void validateLogicalTable( String tableName, LogicalTable table, Granularity granularity, LogicalTableDictionary logicalTableDictionary ) throws BadApiRequestException { if (table == null) { LOG.debug(TABLE_UNDEFINED.logFormat(tableName)); throw new BadApiRequestException(TABLE_UNDEFINED.format(tableName)); } }
/** * Generate a Granularity instance based on a path element. * * @param granularity A string representation of the granularity * @param dateTimeZone The time zone to use for this granularity * @param granularityParser The parser for granularity * * @return A granularity instance with time zone information * @throws BadApiRequestException if the string matches no meaningful granularity */ protected Granularity generateGranularity( @NotNull String granularity, @NotNull DateTimeZone dateTimeZone, @NotNull GranularityParser granularityParser ) throws BadApiRequestException { try { return granularityParser.parseGranularity(granularity, dateTimeZone); } catch (GranularityParseException e) { LOG.error(UNKNOWN_GRANULARITY.logFormat(granularity), granularity); throw new BadApiRequestException(e.getMessage()); } }
/** * Method to generate DateTime sort column from the map of columns and its direction. * * @param sortColumns LinkedHashMap of columns and its direction. Using LinkedHashMap to preserve the order * * @return Instance of OrderByColumn for dateTime */ protected Optional<OrderByColumn> bindDateTimeSortColumn(LinkedHashMap<String, SortDirection> sortColumns) { if (sortColumns != null && sortColumns.containsKey(DATE_TIME_STRING)) { if (!isDateTimeFirstSortField(sortColumns)) { LOG.debug(DATE_TIME_SORT_VALUE_INVALID.logFormat()); throw new BadApiRequestException(DATE_TIME_SORT_VALUE_INVALID.format()); } else { return Optional.of(new OrderByColumn(DATE_TIME_STRING, sortColumns.get(DATE_TIME_STRING))); } } else { return Optional.empty(); } }
/** * Ensure all request dimensions are part of the logical table. * * @param requestDimensions The dimensions being requested * @param table The logical table being checked * * @throws BadApiRequestException if any of the dimensions do not match the logical table */ protected void validateRequestDimensions(Set<Dimension> requestDimensions, LogicalTable table) throws BadApiRequestException { // Requested dimensions must lie in the logical table requestDimensions = new HashSet<>(requestDimensions); requestDimensions.removeAll(table.getDimensions()); if (!requestDimensions.isEmpty()) { List<String> dimensionNames = requestDimensions.stream() .map(Dimension::getApiName) .collect(Collectors.toList()); LOG.debug(DIMENSIONS_NOT_IN_TABLE.logFormat(dimensionNames, table.getName())); throw new BadApiRequestException(DIMENSIONS_NOT_IN_TABLE.format(dimensionNames, table.getName())); } }
/** * Generates the format in which the response data is expected. * * @param format Expects a URL format query String. * * @return Response format type (CSV or JSON). * @throws BadApiRequestException if the requested format is not found. */ protected ResponseFormatType generateAcceptFormat(String format) throws BadApiRequestException { try { return format == null ? DefaultResponseFormatType.JSON : DefaultResponseFormatType.valueOf(format.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException e) { LOG.error(ACCEPT_FORMAT_INVALID.logFormat(format), e); throw new BadApiRequestException(ACCEPT_FORMAT_INVALID.format(format)); } }
/** * Get the timezone for the request. * * @param timeZoneId String of the TimeZone ID * @param systemTimeZone TimeZone of the system to use if there is no timeZoneId * * @return the request's TimeZone */ protected DateTimeZone generateTimeZone(String timeZoneId, DateTimeZone systemTimeZone) { try (TimedPhase timer = RequestLog.startTiming("generatingTimeZone")) { if (timeZoneId == null) { return systemTimeZone; } try { return DateTimeZone.forID(timeZoneId); } catch (IllegalArgumentException ignored) { LOG.debug(INVALID_TIME_ZONE.logFormat(timeZoneId)); throw new BadApiRequestException(INVALID_TIME_ZONE.format(timeZoneId)); } } }
/** * Generate a Granularity instance based on a path element. * * @param granularity A string representation of the granularity * @param granularityParser The parser for granularity * * @return A granularity instance without time zone information * @throws BadApiRequestException if the string matches no meaningful granularity */ protected Granularity generateGranularity(String granularity, GranularityParser granularityParser) throws BadApiRequestException { try { return granularityParser.parseGranularity(granularity); } catch (GranularityParseException e) { LOG.error(UNKNOWN_GRANULARITY.logFormat(granularity), granularity); throw new BadApiRequestException(e.getMessage(), e); } }
/** * Extracts a specific logical table object given a valid table name and a valid granularity. * * @param tableName logical table corresponding to the table name specified in the URL * @param granularity logical table corresponding to the table name specified in the URL * @param logicalTableDictionary Logical table dictionary contains the map of valid table names and table objects. * * @return Set of logical table objects. * @throws BadApiRequestException Invalid table exception if the table dictionary returns a null. */ protected LogicalTable generateTable( String tableName, Granularity granularity, LogicalTableDictionary logicalTableDictionary ) throws BadApiRequestException { LogicalTable generated = logicalTableDictionary.get(new TableIdentifier(tableName, granularity)); // check if requested logical table grain pair exists if (generated == null) { String msg = TABLE_GRANULARITY_MISMATCH.logFormat(granularity, tableName); LOG.error(msg); throw new BadApiRequestException(msg); } LOG.trace("Generated logical table: {} with granularity {}", generated, granularity); return generated; }
/** * Get datetime from the given input text based on granularity. * * @param now current datetime to compute the floored date based on granularity * @param granularity granularity to truncate the given date to. * @param dateText start/end date text which could be actual date or macros * @param timeFormatter a time zone adjusted date time formatter * * @return joda datetime of the given start/end date text or macros * * @throws BadApiRequestException if the granularity is "all" and a macro is used */ public static DateTime getAsDateTime( DateTime now, Granularity granularity, String dateText, DateTimeFormatter timeFormatter ) throws BadApiRequestException { //If granularity is all and dateText is macro, then throw an exception TimeMacro macro = TimeMacro.forName(dateText); if (macro != null) { if (granularity instanceof AllGranularity) { LOG.debug(INVALID_INTERVAL_GRANULARITY.logFormat(macro, dateText)); throw new BadApiRequestException(INVALID_INTERVAL_GRANULARITY.format(macro, dateText)); } return macro.getDateTime(now, (TimeGrain) granularity); } return DateTime.parse(dateText, timeFormatter); }
/** * Throw an exception if any of the intervals are not accepted by this granularity. * * @param granularity The granularity whose alignment is being tested. * @param intervals The intervals being tested. * * @throws BadApiRequestException if the granularity does not align to the intervals */ protected static void validateTimeAlignment( Granularity granularity, List<Interval> intervals ) throws BadApiRequestException { if (!granularity.accepts(intervals)) { String alignmentDescription = granularity.getAlignmentDescription(); LOG.debug(TIME_ALIGNMENT.logFormat(intervals, granularity, alignmentDescription)); throw new BadApiRequestException(TIME_ALIGNMENT.format(intervals, granularity, alignmentDescription)); } }