Skip to content

Commit

Permalink
repo-sqlbase: added support for embedded/nested mapping (not tested yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
virgo47 committed Feb 15, 2021
1 parent 05abe3f commit d1bfc04
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 140 deletions.
Expand Up @@ -57,8 +57,8 @@ public Predicate process(PropertyValueFilter<AuditEventRecordCustomColumnPropert
throws QueryException {
// This is a tricky situation, if multi-value, each value can have different path (derived
// from AuditEventRecordCustomColumnPropertyType.getName()), so we can't use this directly.
ValueFilterValues<AuditEventRecordCustomColumnPropertyType> values =
new ValueFilterValues<>(filter);
ValueFilterValues<AuditEventRecordCustomColumnPropertyType, ?> values =
ValueFilterValues.from(filter);
if (values.isEmpty()) {
throw new QueryException("Custom column null value is not supported,"
+ " column can't be determined from filter: " + filter);
Expand Down
Expand Up @@ -65,8 +65,8 @@ private QName resolvePath(ItemPath path) throws QueryException {
ItemName firstName = path.firstName();
path = path.rest();

ItemRelationResolver<?> resolver = mapping.relationResolver(firstName);
ItemRelationResolver.ResolutionResult resolution = resolver.resolve(context, mapping);
ItemRelationResolver resolver = mapping.relationResolver(firstName);
ItemRelationResolver.ResolutionResult resolution = resolver.resolve(context);
context = resolution.context;
mapping = resolution.mapping;
}
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010-2020 Evolveum and contributors
* Copyright (C) 2010-2021 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
Expand All @@ -25,27 +25,36 @@
* Object wraps zero, one or multiple values and makes their processing easier.
* Instead of just wrapping the values it uses the whole filter object
* to utilize its convenience methods.
* <p>
*
* Returned values are typed to Object, because they can be converted from original type.
* Conversion is moved into this class, so the client code doesn't have to handle translation
* from {@link PrismPropertyValue} to "real value" and then to convert it.
* Both {@link #singleValue()} and {@link #allValues()} are handled the same way.
* <p>
*
* If {@link #conversionFunction} is used any {@link IllegalArgumentException} will be rewrapped
* as {@link QueryException}, other runtime exceptions are not intercepted.
*
* @param <T> type of filter value
* @param <S> type of value after conversion (can by the same like T)
*/
public class ValueFilterValues<T> {
public class ValueFilterValues<T, S> {

@NotNull private final PropertyValueFilter<T> filter;
@Nullable private final Function<T, ?> conversionFunction;
@Nullable private final Function<T, S> conversionFunction;

public ValueFilterValues(PropertyValueFilter<T> filter) {
this(filter, null);
public static <T> ValueFilterValues<T, T> from(@NotNull PropertyValueFilter<T> filter) {
return new ValueFilterValues<>(filter, null);
}

public static <T, S> ValueFilterValues<T, S> from(
@NotNull PropertyValueFilter<T> filter,
@Nullable Function<T, S> conversionFunction) {
return new ValueFilterValues<>(filter, conversionFunction);
}

public ValueFilterValues(
private ValueFilterValues(
@NotNull PropertyValueFilter<T> filter,
@Nullable Function<T, ?> conversionFunction) {
@Nullable Function<T, S> conversionFunction) {
this.filter = Objects.requireNonNull(filter);
this.conversionFunction = conversionFunction;
}
Expand Down
Expand Up @@ -6,28 +6,131 @@
*/
package com.evolveum.midpoint.repo.sqlbase.mapping;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.namespace.QName;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.repo.sqlbase.QueryException;
import com.evolveum.midpoint.repo.sqlbase.filtering.FilterProcessor;
import com.evolveum.midpoint.repo.sqlbase.mapping.item.ItemFilterProcessor;
import com.evolveum.midpoint.repo.sqlbase.mapping.item.ItemRelationResolver;
import com.evolveum.midpoint.repo.sqlbase.mapping.item.ItemSqlMapper;
import com.evolveum.midpoint.repo.sqlbase.querydsl.FlexibleRelationalPathBase;
import com.evolveum.midpoint.util.QNameUtil;

/**
* Common mapping functionality that covers the need for mapping from item paths
* to table columns, but also to nested embedded mappings (e.g. metadata).
*
* This also works as implementation for nested mappings like `metadata` that contain attributes
* resolved to the same query type - e.g. `metadata/createTimestamp`.
* While `metadata` is resolved on the master mapping (for the query type representing table)
* the nested `createTimestamp` is resolved by nested mapper implemented by this type.
* Nested mapping can still contain relations, so {@link #addRelationResolver} is available.
*
* @param <S> schema type
* @param <Q> type of entity path
* @param <R> row type related to the {@link Q}
*/
public interface QueryModelMapping {
public class QueryModelMapping<S, Q extends FlexibleRelationalPathBase<R>, R> {

private final Class<S> schemaType;
private final Class<Q> queryType;

private final Map<QName, ItemSqlMapper> itemMapping = new LinkedHashMap<>();
private final Map<QName, ItemRelationResolver> itemRelationResolvers = new HashMap<>();

public QueryModelMapping(
@NotNull Class<S> schemaType,
@NotNull Class<Q> queryType) {
this.schemaType = schemaType;
this.queryType = queryType;
}

/** Returns schema type as class - refers to midPoint schema, not DB schema. */
public Class<S> schemaType() {
return schemaType;
}

public Class<Q> queryType() {
return queryType;
}

/**
* Adds information how item (attribute) from schema type is mapped to query,
* especially for condition creating purposes.
* This is not usable for complex item path resolution,
* see {@link #addRelationResolver(ItemName, ItemRelationResolver)} for that purpose.
*
* The {@link ItemSqlMapper} works as a factory for {@link FilterProcessor} that can process
* {@link ObjectFilter} related to the {@link ItemName} specified as the first parameter.
* It is not possible to use filter processor directly because at the time of mapping
* specification we don't have the actual query path representing the entity or the column.
* These paths are non-static properties of query class instances.
*
* The {@link ItemSqlMapper} also provides so called "primary mapping" to a column for ORDER BY
* part of the filter.
* But there can be additional column mappings specified as for some types (e.g. poly-strings)
* there may be other than 1-to-1 mapping.
*
* Construction of the {@link ItemSqlMapper} is typically simplified by static methods
* {@code #mapper()} provided on various {@code *ItemFilterProcessor} classes.
* This works as a "processor factory factory" and makes table mapping specification simpler.
*
* @param itemName item name from schema type (see {@code F_*} constants on schema types)
* @param itemMapper mapper wrapping the information about column mappings working also
* as a factory for {@link FilterProcessor}
*/
public final QueryModelMapping<S, Q, R> addItemMapping(
@NotNull QName itemName,
@NotNull ItemSqlMapper itemMapper) {
itemMapping.put(itemName, itemMapper);
return this;
}

/**
* Adds information how {@link ItemName} (attribute) from schema type is to be resolved
* when it appears as a component of a complex (non-single) {@link ItemPath}.
* This is in contrast with "item mapping" that is used for single (or last) component
* of the item path and helps with query interpretation.
*/
public final QueryModelMapping<S, Q, R> addRelationResolver(
@NotNull ItemName itemName,
@NotNull ItemRelationResolver itemRelationResolver) {
itemRelationResolvers.put(itemName, itemRelationResolver);
return this;
}

/**
* Returns {@link ItemSqlMapper} for provided {@link ItemName}.
* This is later used to create {@link ItemFilterProcessor}
*/
@NotNull ItemSqlMapper itemMapper(QName itemName) throws QueryException;
public final @NotNull ItemSqlMapper itemMapper(QName itemName) throws QueryException {
ItemSqlMapper itemMapping = QNameUtil.getByQName(this.itemMapping, itemName);
if (itemMapping == null) {
throw new QueryException("Missing item mapping for " + itemName
+ " in mapping " + getClass().getSimpleName());
}
return itemMapping;
}

@NotNull ItemRelationResolver<?> relationResolver(ItemName itemName)
throws QueryException;
/**
* Returns {@link ItemRelationResolver} for provided {@link ItemName}.
* Relation resolver helps with traversal over all-but-last components of item paths.
*/
public final @NotNull ItemRelationResolver relationResolver(ItemName itemName)
throws QueryException {
ItemRelationResolver resolver = QNameUtil.getByQName(itemRelationResolvers, itemName);
if (resolver == null) {
throw new QueryException("Missing relation resolver for " + itemName
+ " in mapping " + getClass().getSimpleName());
}
return resolver;
}
}

0 comments on commit d1bfc04

Please sign in to comment.