/** * Constructor. * Uses the given dimension's name for column name. * * @param dimension The column's corresponding dimension */ public DimensionColumn(@NotNull Dimension dimension) { super(dimension.getApiName()); this.dimension = dimension; }
@Override public String toString() { return String.format("%s|%s-%s%s", dimension.getApiName(), dimensionField, operation, values); } }
/** * Adds the specified element to the dictionary if it is not already present. * * @param dimension element to add to dictionary * * @return <tt>true</tt> if the dictionary did not already contain the specified dimension * @see Set#add(Object) */ public boolean add(Dimension dimension) { if (apiNameToDimension.containsKey(dimension.getApiName())) { return false; } Dimension oldDimension = apiNameToDimension.put(dimension.getApiName(), dimension); if (oldDimension != null) { // should never happen unless multiple loaders are running in race-condition ConcurrentModificationException e = new ConcurrentModificationException(); LOG.error("Multiple loaders updating DimensionDictionary", e); throw e; } return true; }
/** * A default implementation of the converter method which uses underscore as a separator. * * @return an instance of DimensionFieldNameMapper */ static DimensionFieldNameMapper underscoreSeparatedConverter () { return (dimension, dimensionField) -> (dimension.getApiName() + "_" + dimensionField.getName()).toUpperCase(Locale.ENGLISH); } }
/** * Get the URL of the dimension values collection. * * @param dimension Dimension to get the URL of * @param uriInfo URI Info for the request * * @return The absolute URL for the dimension */ public static String getDimensionValuesUrl(Dimension dimension, final UriInfo uriInfo) { return uriInfo.getBaseUriBuilder() .path(DimensionsServlet.class) .path(DimensionsServlet.class, "getDimensionRows") .build(dimension.getApiName()) .toASCIIString(); }
/** * Get the URL of the dimension. * * @param dimension Dimension to get the URL of * @param uriInfo URI Info for the request * * @return The absolute URL for the dimension */ public static String getDimensionUrl(Dimension dimension, final UriInfo uriInfo) { return uriInfo.getBaseUriBuilder() .path(DimensionsServlet.class) .path(DimensionsServlet.class, "getDimension") .build(dimension.getApiName()) .toASCIIString(); }
/** * Retrieve dimension column name from cache, or build it and cache it. * * @param dimension The dimension for the column name * @param dimensionField The dimensionField for the column name * * @return The name for the dimension and column as it will appear in the response document */ public static String getDimensionColumnName(Dimension dimension, DimensionField dimensionField) { Map<DimensionField, String> columnNamesForDimensionFields; columnNamesForDimensionFields = DIMENSION_FIELD_COLUMN_NAMES.computeIfAbsent( dimension, (key) -> new ConcurrentHashMap() ); return columnNamesForDimensionFields.computeIfAbsent( dimensionField, (field) -> dimension.getApiName() + "|" + field.getName() ); } }
/** * Constructor. * * @param resourceDictionaries The dictionaries to use for request mapping. * @param dimension The dimension whose roles are being matched * @param roleApiFilters ApiFilters by role for a given dimension * @param next The next request mapper to process this ApiRequest */ public RoleDimensionApiFilterRequestMapper( final ResourceDictionaries resourceDictionaries, Dimension dimension, Map<String, Set<ApiFilter>> roleApiFilters, RequestMapper<DataApiRequest> next ) { super(resourceDictionaries, next); this.dimension = dimension; this.roleApiFilters = roleApiFilters; unauthorizedHttpMessage = DIMENSION_MISSING_MANDATORY_ROLE.format(dimension.getApiName()); }
@Override public void serialize(Dimension value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeString( SerializerUtil.findPhysicalName(value, gen).orElseThrow(() -> { LOG.error(ErrorMessageFormat.PHYSICAL_NAME_NOT_FOUND.logFormat(value.getApiName())); return new IllegalStateException(ErrorMessageFormat.PHYSICAL_NAME_NOT_FOUND.format()); } ) ); } }
/** * Verify that, given this user, that at least some of the whitelisted filters have been collected. * Failure to have any whitelisted filters indicate a user has not been authorized for values with this dimension. * * @param userPrincipal The userPrincipal being validated * @param mergedSecurityFilters The combined security filters for this request * * @throws RequestValidationException An http request exception documenting the lack of privileges */ protected void validateSecurityFilters(Principal userPrincipal, Set<ApiFilter> mergedSecurityFilters) throws RequestValidationException { if (mergedSecurityFilters.isEmpty()) { LOG.warn(DIMENSION_MISSING_MANDATORY_ROLE.logFormat(userPrincipal.getName(), dimension.getApiName())); throw new RequestValidationException( Response.Status.FORBIDDEN, unauthorizedHttpMessage, UNAUTHORIZED_USER_MESSAGE ); } }
@Override public void serialize(Dimension value, JsonGenerator gen, SerializerProvider provider) throws IOException { String apiName = value.getApiName(); String physicalName = SerializerUtil.findPhysicalName(value, gen).orElseThrow(() -> { LOG.error(ErrorMessageFormat.PHYSICAL_NAME_NOT_FOUND.logFormat(value.getApiName())); return new IllegalStateException(ErrorMessageFormat.PHYSICAL_NAME_NOT_FOUND.format()); } ); // serialize to only apiName if api and physical name is same or there are nested queries if (physicalName.equals(apiName) || SerializerUtil.hasInnerQuery(gen)) { gen.writeString(apiName); } else { gen.writeObject(new DefaultDimensionSpec(physicalName, apiName, value)); } } }
/** * Extracts dimension rows for the given dimension columns. * * @param dimensionRowsNode JsonNode which contains all the dimension rows which contains dimension names * and its unique id as its value * @param dimensionColumns DimensionColumns which needs to have dimension rows * * @return Map of all the dimensionRows associated with dimensionColumns */ private Map<DimensionColumn, DimensionRow> extractDimensionValues( JsonNode dimensionRowsNode, Set<DimensionColumn> dimensionColumns ) { return dimensionColumns.stream().collect(Collectors.toMap( Function.identity(), dimensionColumn -> dimensionColumn.getDimension().findDimensionRowByKeyValue( dimensionRowsNode.get(dimensionColumn.getDimension().getApiName()).asText() ) )); }
/** * Get the summary view of the dimension. * * @param dimension Dimension to get the view of * @param uriInfo UriInfo of the request * * @return Summary view of the dimension */ public static Map<String, Object> getDimensionSummaryView(Dimension dimension, final UriInfo uriInfo) { Map<String, Object> resultRow = new LinkedHashMap<>(); resultRow.put("category", dimension.getCategory()); resultRow.put("name", dimension.getApiName()); resultRow.put("longName", dimension.getLongName()); resultRow.put("uri", getDimensionUrl(dimension, uriInfo)); resultRow.put("cardinality", dimension.getCardinality()); resultRow.put("storageStrategy", dimension.getStorageStrategy()); return resultRow; }
/** * Get the summary view of the dimension. * * @param dimension Dimension to get the view of * @param uriInfo UriInfo of the request * * @return Summary view of the dimension */ private static Map<String, Object> getDimensionSummaryViewWithFields(Dimension dimension, UriInfo uriInfo) { Map<String, Object> resultRow = new LinkedHashMap<>(); resultRow.put("category", dimension.getCategory()); resultRow.put("name", dimension.getApiName()); resultRow.put("longName", dimension.getLongName()); resultRow.put("uri", DimensionsServlet.getDimensionUrl(dimension, uriInfo)); resultRow.put("cardinality", dimension.getCardinality()); resultRow.put("fields", dimension.getDimensionFields()); resultRow.put("storageStrategy", dimension.getStorageStrategy()); return resultRow; } }
/** * Resolves a set of ApiFilters into a list of dimension rows that need to be filtered in Druid. * * @param dimension The dimension being filtered * @param filters The filters being applied to the {@code dimension} * * @return A list of dimension rows that Druid needs to filter on * * @throws DimensionRowNotFoundException if the filters filter out all dimension rows */ protected Set<DimensionRow> getFilteredDimensionRows(Dimension dimension, Set<ApiFilter> filters) throws DimensionRowNotFoundException { Set<DimensionRow> rows = dimension.getSearchProvider().findFilteredDimensionRows(filters); if (rows.isEmpty()) { String msg = ErrorMessageFormat.DIMENSION_ROWS_NOT_FOUND.format(dimension.getApiName(), filters); LOG.debug(msg); throw new DimensionRowNotFoundException(msg); } return rows; }
/** * Reads the results of a Druid GroupBy Query to find dimension values. * * @param dimension The dimension to load values for. * * @return the callback to load dimension values. */ private SuccessCallback buildSuccessCallback(Dimension dimension) { return rootNode -> { rootNode.forEach(row -> { JsonNode eventRow = row.get("event"); String dimensionValue = eventRow.get(dimension.getApiName()).asText(); if (dimension.findDimensionRowByKeyValue(dimensionValue) == null) { DimensionRow dimensionRow = dimension.createEmptyDimensionRow(dimensionValue); updateDimensionWithValue(dimension, dimensionRow); } }); updateDimension(dimension); }; }
/** * Constructor. * * @param filter The filter being recorded */ public Filter(ApiFilter filter) { this.dimension = filter.getDimension().getApiName(); this.field = filter.getDimensionField().getName(); this.operator = filter.getOperation().getName(); this.numberOfValues = filter.getValues().size(); }
/** * JSON tree walk up to physical table to retrieve physical name for a dimension. * * @param value the dimension to retrieve api name. * @param gen the Json Generator to retrieve the tree to walk on. * * @return an Optional String of physical name */ public static Optional<String> findPhysicalName(Dimension value, JsonGenerator gen) { String apiName = value.getApiName(); // Search for physical name return mapNearestDruidQuery( gen, druidQuery -> druidQuery.getDataSource().getPhysicalTable().getPhysicalColumnName(apiName) ); }
/** * Evaluates a regular expression filter. * * @param regexFilter A regexFilter to be evaluated. * @param builder The RelBuilder used to build queries with Calcite. * @param apiToFieldMapper A function to get the aliased aggregation's name from the metric name. * * @return a RexNode containing an equivalent filter to the one given. */ public RexNode evaluate( RegularExpressionFilter regexFilter, RelBuilder builder, ApiToFieldMapper apiToFieldMapper ) { // todo test this String apiName = regexFilter.getDimension().getApiName(); return builder.call( SqlStdOperatorTable.LIKE, builder.field(apiToFieldMapper.apply(apiName)), builder.literal(regexFilter.getPattern().toString()) ); }
/** * Evaluates a Selector filter. * * @param selectorFilter A selectorFilter to be evaluated. * @param builder The RelBuilder used to build queries with Calcite. * @param apiToFieldMapper A function to get the aliased aggregation's name from the metric name. * * @return a RexNode containing an equivalent filter to the one given. */ public RexNode evaluate(SelectorFilter selectorFilter, RelBuilder builder, ApiToFieldMapper apiToFieldMapper) { String apiName = selectorFilter.getDimension().getApiName(); return builder.call( SqlStdOperatorTable.EQUALS, builder.field(apiToFieldMapper.apply(apiName)), builder.literal(selectorFilter.getValue()) ); }