Skip to content

Commit

Permalink
Documented Midpoint PreparedQuery and TypedQuery, made APIs more user…
Browse files Browse the repository at this point in the history
… friendly
  • Loading branch information
tonydamage committed Aug 8, 2023
1 parent 6f4e3f0 commit d54cad4
Show file tree
Hide file tree
Showing 3 changed files with 336 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.schema.query;


import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.ObjectOrdering;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.prism.query.OrderDirection;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.GetOperationOptionsBuilder;
import com.evolveum.midpoint.schema.SchemaService;
import com.evolveum.midpoint.schema.SelectorOptions;

import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public abstract class AbstractTypedQuery<T, F extends AbstractTypedQuery<T,F>> {

protected Class<T> type;
private final GetOperationOptionsBuilder optionsBuilder;

private List<ObjectOrdering> orderBy = new ArrayList<>();

private Integer offset;
private Integer maxSize;

private Collection<SelectorOptions<GetOperationOptions>> options;

public AbstractTypedQuery(Class<T> type) {
this.type = type;
this.optionsBuilder = SchemaService.get().getOperationOptionsBuilder();
}


abstract protected F self();


public GetOperationOptionsBuilder operationOptionsBuilder() {
return optionsBuilder;
}

public Integer getOffset() {
return offset;
}

public F offset(Integer offset) {
this.offset = offset;
return self();
}

public Integer getMaxSize() {
return maxSize;
}

public F maxSize(Integer maxSize) {
this.maxSize = maxSize;
return self();
}

public F orderBy(ItemPath path, OrderDirection direction) {
orderBy.add(PrismContext.get().queryFactory().createOrdering(path, direction));
return self();
}

protected boolean pagingRequired() {
return maxSize != null || offset != null || !orderBy.isEmpty();
}

@Nullable
protected ObjectPaging buildObjectPaging() {
if (pagingRequired()) {
var paging = PrismContext.get().queryFactory().createPaging(offset, maxSize);
paging.setOrdering(new ArrayList<>(orderBy));
return paging;
}
return null;
}

protected void fillFrom(AbstractTypedQuery<?,?> other) {
offset = other.offset;
maxSize = other.maxSize;
orderBy = new ArrayList<>(other.orderBy);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.schema.query;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.PreparedPrismQuery;
import com.evolveum.midpoint.schema.GetOperationOptionsBuilder;
import com.evolveum.midpoint.util.exception.SchemaException;

/**
* Prepared Query represents query with filter with placeholders, which can be used for search.
*
* Prepared Query differs from {@link TypedQuery} that original query contained placeholders, which needs to be bound to actual values
* before query is used.
*
* Values could be bound using {@link #bindValue(Object)} method for binding first unbound values or by using {@link #bind(Object...)}
* method which binds multiple unbound values in order of their appeareance.
*
* Prepared Query can be converted to {@link TypedQuery} by binding values using {@link #bind(Object...)} method. Once prepared query
* is successfully bound, filter is cached and new typed queries could be generated using {@link #toTypedQuery()}.
*
* @param <T> <T> Resulting item type
*/
public class PreparedQuery<T> extends AbstractTypedQuery<T, PreparedQuery<T>> {


private PreparedPrismQuery delegate;

private GetOperationOptionsBuilder options;
/**
* Final filter, available once query is successfully bound using {@link #bind(Object...)}.
*/
private ObjectFilter finalFilter;

PreparedQuery(Class<T> type, PreparedPrismQuery delegate) {
super(type);
this.delegate = delegate;
}

/**
* Parses supplied query string for type with placeholder support.
*
* @param type Type of the items which query should return
* @param query Midpoint Query string without placeholders
* @return new Prepared Query which contains partially parsed filter, type information and allows for values to be bound to placeholders.
* @param <T> Type of the items which query should return
* @throws SchemaException when supplied query string was syntactically or logically incorrect
*/
public static <T> PreparedQuery<T> parse(Class<T> type, String query) throws SchemaException {
var prismQuery = PrismContext.get().createQueryParser().parse(type, query);
return new PreparedQuery<>(type, prismQuery);
}

/**
* Binds next unbound value in filter to provided value.
*
* IMPORTANT: Current behavior is strict in checking value types for bound values, user must supply
* correct class for realValue (eg. {@link com.evolveum.midpoint.prism.polystring.PolyString},
* {@link javax.xml.datatype.XMLGregorianCalendar}
*
* @param realValue Real Value to be bound
* @throws SchemaException If provided value is invalid according to schema definition (type of value)
* @throws IllegalStateException If there is no positional value to be bound
*/
public PreparedQuery<T> bindValue(Object realValue) throws SchemaException {
delegate.bindValue(realValue);
return self();
}

/**
* Binds multiple values and returns final Typed Query which will contain filter.
*
* @param args Real values to be bound to unbound placeholders in order of appearance. See {@link #bindValue(Object)} for more details.
* @return Typed Query which contains final filter and configuration of paging and ordering.
* @throws IllegalStateException If not all placeholders were bound to values
* @throws SchemaException If provided values are invalid according to schema definition (type of value)
* resulting filter with bound values is invalid.
*/
public TypedQuery<T> bind(Object... args) throws SchemaException {
if (finalFilter == null) {
finalFilter = delegate.bind(args);
}
return toTypedQuery();
}

/**
* Creates new {@link TypedQuery} based on type, filter, bound values and configuration of paging and ordering.
*
* @return New Typed Query based on bound values and configuration of paging and ordering.
* @throws IllegalStateException If query was not successfully bound before using {@link #bind(Object...)} or {@link #build()}.
*/
public TypedQuery<T> toTypedQuery() {
if (finalFilter == null) {
throw new IllegalStateException("Filter was not bound");
}
var typed = TypedQuery.from(type, finalFilter);
typed.fillFrom(this);
typed.setOptions(operationOptionsBuilder().build());
return typed;
}

public ObjectQuery toObjectQuery() {
return toTypedQuery().toObjectQuery();
}

/**
*
* @return
* @throws SchemaException
*/
public TypedQuery<T> build() throws SchemaException {
return bind();
}

/**
* @return True if all placeholders were bound to value.
*/
public boolean allPlaceholdersBound() {
return delegate.allPlaceholdersBound();
}

@Override
protected PreparedQuery<T> self() {
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.schema.query;

import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.util.exception.SchemaException;

import java.util.Collection;

/**
* Typed Query represents query with filter, which can be used for searching references, containers and objects (depending on item type).
*
* Typed Query can be converted to existing {@link ObjectQuery} interface using {@link #toObjectQuery()} method. This allows
* to use TypedQuery as a builder for {@link ObjectQuery} where resulting object queries will only differ in paging information,
* but use same filter.
*
* @param <T> Resulting item type
*/
public class TypedQuery<T> extends AbstractTypedQuery<T,TypedQuery<T>> {

private ObjectFilter filter;

private Collection<SelectorOptions<GetOperationOptions>> options;


TypedQuery(Class<T> type, ObjectFilter filter) {
super(type);
this.filter = filter;
}

public Class<T> getType() {
return type;
}

/**
* Creates TypedFilter for supplied type and query string.
*
* Query string must not contain placeholders. If you need placeholder support see {@link PreparedQuery#parse(Class, String)}.
* Resulting query doe
*
* @param type Type of the items which query should return
* @param query Midpoint Query string without placeholders
* @return new Typed Query which contains parsed filter and type information, no ordering and no paging information.
* @param <T>
* @throws SchemaException when supplied query string was syntactically or logically incorrect
*/
public static <T> TypedQuery<T> parse(Class<T> type, String query) throws SchemaException {
var filter = PrismContext.get().createQueryParser().parseFilter(type, query);
return from(type, filter);
}

/**
* Creates TypedFilter for supplied type and filter.
*
* @param type Type of the items which query should return
* @param filter Existing filter to be used for typed query
* @return Typed Query which contains filter and type information, no ordering and no paging information.
* @param <T> Type of the items which query should return
*/
public static <T> TypedQuery<T> from(Class<T> type, ObjectFilter filter) {
return new TypedQuery<>(type, filter);
}

/**
* Creates new {@link ObjectQuery} representing this {@link TypedQuery}, created Object query uses same filter and
* copy of current paging information.
*
* This allows to use one TypedQuery to be used to generate multiple page specific queries.
*
* @return new Object Query representing this Type Query
*/
public ObjectQuery toObjectQuery() {
var query = PrismContext.get().queryFactory().createQuery(filter);
query.setPaging(buildObjectPaging());
return query;

}

/**
* Returns explicitly configured set of {@link GetOperationOptions}
*
* If set was not explicitly set using {@link #setOptions(Collection)}, it is constructed using {@link #operationOptionsBuilder()}.
*
* @return Set of {@link GetOperationOptions}
*/
public Collection<SelectorOptions<GetOperationOptions>> getOptions() {
if (options == null) {
options = operationOptionsBuilder().build();
}
return options;
}

public void setOptions(Collection<SelectorOptions<GetOperationOptions>> options) {
this.options = options;
}

@Override
protected TypedQuery<T> self() {
return this;
}
}

0 comments on commit d54cad4

Please sign in to comment.