Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feature/secrets-pro…
Browse files Browse the repository at this point in the history
…vider
  • Loading branch information
1azyman committed Jan 24, 2024
2 parents b0709bc + 9160dee commit 9b318f8
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2010-2024 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.prism;

import java.io.Serializable;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.util.annotation.Experimental;

/**
* Resolution of a path in a prism container definition, as far as possible.
* See {@link #resolvedPath}, {@link #remainderPath} and {@link #lastDefinition} for details.
*/
@Experimental
public class ResolvedItemPath<ID extends ItemDefinition<?>> implements Serializable {

/** The path as in the definitions, i.e. usually with qualified names. */
@NotNull private final ItemPath resolvedPath;

/** Path that cannot be resolved. Hopefully empty. */
@NotNull private final ItemPath remainderPath;

/** Definition of the last item in {@link #resolvedPath}. */
@NotNull private final ID lastDefinition;

private ResolvedItemPath(@NotNull ItemPath resolvedPath, @NotNull ItemPath remainderPath, @NotNull ID lastDefinition) {
this.lastDefinition = lastDefinition;
this.resolvedPath = resolvedPath;
this.remainderPath = remainderPath;
}

/** Resolves the path against a container definition. The path should be "names-only". */
public static <ID extends ItemDefinition<?>> ResolvedItemPath<ID> create(
@NotNull PrismContainerDefinition<?> rootDefinition, @NotNull ItemPath rawItemPath) {
ItemPath resolvedPath = ItemPath.EMPTY_PATH;
ItemPath remainderPath = rawItemPath.namedSegmentsOnly();
PrismContainerDefinition<?> currentDefinition = rootDefinition;
for (;;) {
if (remainderPath.isEmpty()) {
//noinspection unchecked
return new ResolvedItemPath<>(resolvedPath, remainderPath, (ID) currentDefinition);
}
ItemDefinition<?> childDefinition = currentDefinition.findItemDefinition(remainderPath.firstToName());
if (childDefinition == null) {
//noinspection unchecked
return new ResolvedItemPath<>(resolvedPath, remainderPath, (ID) currentDefinition);
}
resolvedPath = resolvedPath.append(childDefinition.getItemName());
remainderPath = remainderPath.rest();
if (!(childDefinition instanceof PrismContainerDefinition<?> childPcd)) {
//noinspection unchecked
return new ResolvedItemPath<>(resolvedPath, remainderPath, (ID) childDefinition);
}
currentDefinition = childPcd;
}
}

public @NotNull ItemPath getResolvedPath() {
return resolvedPath;
}

public @NotNull ID getLastDefinition() {
return lastDefinition;
}

public boolean isComplete() {
return remainderPath.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,9 @@ public void setMatchingRule(@Nullable QName matchingRule) {

@NotNull
MatchingRule<Object> getMatchingRuleFromRegistry(MatchingRuleRegistry matchingRuleRegistry) {
if (definition == null) {
throw new IllegalArgumentException("No definition in item " + fullPath);
}
try {
return matchingRuleRegistry.getMatchingRule(matchingRule, definition.getTypeName());
QName typeName = definition != null ? definition.getTypeName() : null;
return matchingRuleRegistry.getMatchingRule(matchingRule, typeName);
} catch (SchemaException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.evolveum.midpoint.prism.impl.query.lang;

import com.evolveum.axiom.lang.antlr.query.AxiomQueryParser;
import com.evolveum.axiom.lang.antlr.query.AxiomQueryParser.*;
import com.evolveum.axiom.lang.antlr.query.AxiomQueryParserBaseVisitor;

import com.evolveum.midpoint.prism.ItemDefinition;
Expand All @@ -22,6 +21,8 @@
import java.util.*;
import java.util.stream.Collectors;

import static com.evolveum.midpoint.prism.impl.query.lang.PrismQueryLanguageParserImpl.*;

/**
* Created by Dominik.
*/
Expand Down Expand Up @@ -49,7 +50,7 @@ public Object visitErrorNode(ErrorNode node) {
}

@Override
public Object visitItemComponent(ItemComponentContext ctx) {
public Object visitItemComponent(AxiomQueryParser.ItemComponentContext ctx) {

if (schemaRegistry.findTypeDefinitionByType(new QName(ctx.getText())) != null) {
lastType = ctx;
Expand All @@ -59,45 +60,59 @@ public Object visitItemComponent(ItemComponentContext ctx) {
}

public Map<String, String> generateSuggestion() {
// structure of suggestion: filterName -> alias
Map<String, String> suggestions = new HashMap<>();
final ParseTree lastNode = getLastNode();

// generate suggestion
if (lastNode instanceof AxiomQueryParser.ItemPathComponentContext ctx) {
suggestions = getFilters(lastNode.getText());
suggestions.put(FilterNames.NOT.getLocalPart(), null);
} else if (lastNode instanceof AxiomQueryParser.SelfPathContext ctx) {
// TODO solve SelfPathContext
} else if (lastNode instanceof AxiomQueryParser.FilterNameContext ctx) {
// TODO maybe to add suggestion for value
// value for @type || . type
if (findNode(ctx).getChild(0).getText().equals(FilterNames.META_TYPE) || ctx.getText().equals(FilterNames.TYPE.getLocalPart())) {
TypeDefinition typeDefinition = schemaRegistry.findTypeDefinitionByType(defineObjectType());
suggestions = schemaRegistry.getAllSubTypesByTypeDefinition(List.of(typeDefinition)).stream()
.collect(Collectors.toMap(item -> item.getTypeName().getLocalPart(), item -> item.getTypeName().getLocalPart(), (existing, replacement) -> existing))
.values().stream().collect(Collectors.toMap(filterName -> filterName, alias -> alias));
.collect(Collectors.toMap(item -> item.getTypeName().getLocalPart(), item -> item.getTypeName().getLocalPart(), (existing, replacement) -> existing));
}

// value for @path
if (findNode(ctx).getChild(0).getText().equals(FilterNames.META_PATH)) {
suggestions = getAllPath().stream().collect(Collectors.toMap(x -> x, x -> x));
if (findNode(ctx).getChild(0).getText().equals(FilterNames.META_PATH) || ctx.getText().equals(LPAR)) {
suggestions = getAllPath();
}
// value for @relation
if (ctx.getText().equals(FilterNames.META_RELATION)) {
// TODO add value for @relation to suggestions list
}

if (ctx.getText().equals(FilterNames.MATCHES.getLocalPart()) || ctx.getText().equals(FilterNames.REFERENCED_BY.getLocalPart())) {
suggestions.put("(", null);
if (ctx.getText().equals(FilterNames.MATCHES.getLocalPart()) || ctx.getText().equals(FilterNames.REFERENCED_BY.getLocalPart()) || ctx.getText().equals(FilterNames.OWNED_BY.getLocalPart())) {
suggestions.put(LPAR, null);
}
} else if (lastNode instanceof AxiomQueryParser.GenFilterContext || lastNode instanceof AxiomQueryParser.DescendantPathContext) {
suggestions = getFilters(lastNode.getText());
suggestions.put(FilterNames.NOT.getLocalPart(), null);
} else if (lastNode instanceof AxiomQueryParser.GenFilterContext ctx) {
if (ctx.getText().equals(DOT)) {
suggestions = getFilters(lastNode.getText());
} else {
suggestions = getFilters(lastNode.getText());
suggestions.put(FilterNames.NOT.getLocalPart(), null);
}
} else if (lastNode instanceof AxiomQueryParser.DescendantPathContext ctx) {
// TODO solve DescendantPathContext
} else if (lastNode instanceof AxiomQueryParser.SubfilterOrValueContext ctx) {
if (ctx.getText().equals(LPAR)) {
if (ctx.getText().equals(REF_TARGET_ALIAS)) {
// TODO ( -> @type, @path, @relation
} else {
suggestions = getAllPath();
}
}

suggestions.put(FilterNames.AND.getLocalPart(), null);
suggestions.put(FilterNames.OR.getLocalPart(), null);
} else if (lastNode instanceof TerminalNode ctx) {
if (ctx.getSymbol().getType() == AxiomQueryParser.SEP || ctx.getSymbol().getType() == AxiomQueryParser.AND_KEYWORD || ctx.getSymbol().getType() == AxiomQueryParser.OR_KEYWORD) {
suggestions = getAllPath().stream().collect(Collectors.toMap(x -> x, x -> x));
suggestions.put(".", null);
suggestions = getAllPath();
suggestions.put(DOT, null);
}
} else if (lastNode instanceof ErrorNode ctx) {
// TODO solve Error token
Expand Down Expand Up @@ -154,18 +169,18 @@ private ParseTree getPreviousToken(@NotNull ParserRuleContext ctx) {
return count >= 1 ? ctx.getChild(count - 1) : null;
}

private List<String> getAllPath() {
private Map<String, String> getAllPath() {
TypeDefinition typeDefinition = schemaRegistry.findTypeDefinitionByType(defineObjectType());
PrismObjectDefinition<?> objectDefinition = schemaRegistry.findObjectDefinitionByCompileTimeClass((Class) typeDefinition.getCompileTimeClass());
return objectDefinition.getItemNames().stream().map(QName::getLocalPart).collect(Collectors.toList());
return objectDefinition.getItemNames().stream().map(QName::getLocalPart).collect(Collectors.toMap(filterName -> filterName, alias -> alias));
}

private Map<String, String> getFilters(@NotNull String stringItemPath) {
ItemPath itemPath = ItemPathHolder.parseFromString(stringItemPath);
TypeDefinition typeDefinition = schemaRegistry.findTypeDefinitionByType(defineObjectType());
PrismObjectDefinition<?> objectDefinition = schemaRegistry.findObjectDefinitionByCompileTimeClass((Class) typeDefinition.getCompileTimeClass());
ItemDefinition<?> itemDefinition = objectDefinition.findItemDefinition(itemPath, ItemDefinition.class);
return FilterNamesProvider.findFilterNamesByItemDefinition(itemDefinition, new FilterContext());
return FilterNamesProvider.findFilterNamesByItemDefinition(itemDefinition, new AxiomQueryParser.FilterContext());
}

// remove after implementing schemaContext annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public List<AxiomQueryError> validate(String query) {
}

public Map<String, String> queryCompletion(String query) {

if (query.isEmpty()) query = " ";

AxiomQuerySource axiomQuerySource = AxiomQuerySource.from(query);
AxiomQueryCompletionVisitor axiomQueryCompletionVisitor = new AxiomQueryCompletionVisitor(this.prismContext);
axiomQueryCompletionVisitor.visit(axiomQuerySource.root());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ public static Map<String, String> findFilterNamesByItemDefinition(ItemDefinition
suggestions.put(EXISTS.getLocalPart(), NAME_TO_ALIAS.get(EXISTS));
suggestions.put(LEVENSHTEIN.getLocalPart(), NAME_TO_ALIAS.get(LEVENSHTEIN));
suggestions.put(SIMILARITY.getLocalPart(), NAME_TO_ALIAS.get(SIMILARITY));
suggestions.put(IN_OID.getLocalPart(), NAME_TO_ALIAS.get(IN_OID));
suggestions.put(OWNED_BY_OID.getLocalPart(), NAME_TO_ALIAS.get(OWNED_BY_OID));
suggestions.put(IN_ORG.getLocalPart(), NAME_TO_ALIAS.get(IN_ORG));
suggestions.put(IS_ROOT.getLocalPart(), NAME_TO_ALIAS.get(IS_ROOT));
suggestions.put(OWNED_BY.getLocalPart(), NAME_TO_ALIAS.get(OWNED_BY));
suggestions.put(ANY_IN.getLocalPart(), NAME_TO_ALIAS.get(ANY_IN));
suggestions.put(TYPE.getLocalPart(), NAME_TO_ALIAS.get(TYPE));

Expand All @@ -49,11 +45,14 @@ public static Map<String, String> findFilterNamesByItemDefinition(ItemDefinition
suggestions.put(MATCHES.getLocalPart(), NAME_TO_ALIAS.get(MATCHES));
}
}
if (itemDefinition instanceof PrismReferenceDefinition) {
suggestions.put(REFERENCED_BY.getLocalPart(), NAME_TO_ALIAS.get(REFERENCED_BY));
}
if (itemDefinition instanceof PrismContainerDefinition<?>) {

if (itemDefinition instanceof PrismContainerDefinition || itemDefinition instanceof PrismReferenceDefinition) {
suggestions.put(MATCHES.getLocalPart(), NAME_TO_ALIAS.get(MATCHES));
suggestions.put(REFERENCED_BY.getLocalPart(), NAME_TO_ALIAS.get(REFERENCED_BY));
suggestions.put(OWNED_BY.getLocalPart(), NAME_TO_ALIAS.get(OWNED_BY));
suggestions.put(IN_ORG.getLocalPart(), NAME_TO_ALIAS.get(IN_ORG));
suggestions.put(IN_OID.getLocalPart(), NAME_TO_ALIAS.get(IN_OID));
suggestions.put(IS_ROOT.getLocalPart(), NAME_TO_ALIAS.get(IS_ROOT));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class PrismQueryLanguageParserImpl implements PrismQueryLanguageParser {
protected static final String REF_REL = "relation";
protected static final String REF_TARGET_ALIAS = "@";
protected static final String REF_TARGET = "target";
protected static final String DOT = ".";
protected static final String LPAR = "(";

private static final QName VALUES = new QName(PrismConstants.NS_QUERY, "values");

private static final Map<String, Class<?>> POLYSTRING_PROPS = ImmutableMap.<String, Class<?>>builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.evolveum.midpoint.prism.query.lang;

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.impl.query.lang.AxiomQueryLangServiceImpl;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.util.PrismTestUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.exception.SchemaException;

import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
import org.xml.sax.SAXException;
import org.testng.Assert;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.util.*;

import static com.evolveum.midpoint.prism.PrismInternalTestUtil.DEFAULT_NAMESPACE_PREFIX;

/**
* Created by Dominik.
*/
public class TestQueryCompletion extends AbstractPrismTest {

private AxiomQueryLangServiceImpl axiomQueryLangServiceImpl;

private SchemaRegistry schemaRegistry;

@BeforeSuite
public void setupDebug() throws SchemaException, SAXException, IOException {
PrettyPrinter.setDefaultNamespacePrefix(DEFAULT_NAMESPACE_PREFIX);
PrismTestUtil.resetPrismContext(new PrismInternalTestUtil());
axiomQueryLangServiceImpl = new AxiomQueryLangServiceImpl(PrismContext.get());
schemaRegistry = PrismContext.get().getSchemaRegistry();
}


// Basic filters
@Test
public void testQueryCompletionDot() {
String query = ". ";
Map<String, String> suggestions = axiomQueryLangServiceImpl.queryCompletion(query);
Map<String, String> expected = new HashMap<>();
expected.put("isRoot", null);
expected.put("inOrg", null);
expected.put("referencedBy", null);
expected.put("matches", null);
expected.put("ownedBy", null);
expected.put("inOid", null);

Assert.assertEquals(suggestions.keySet().stream().sorted().toList(), expected.keySet().stream().sorted().toList());
}

@Test
public void testQueryCompletionTypesOfUserType() {
String query = ". type ";
TypeDefinition typeDefinition = schemaRegistry.findTypeDefinitionByType(new QName("UserType"));
Map<String, String> suggestions = axiomQueryLangServiceImpl.queryCompletion(query);
List<String> objectTypes = schemaRegistry.getAllSubTypesByTypeDefinition(Collections.singletonList(typeDefinition)).stream().map(item -> item.getTypeName().getLocalPart()).sorted().toList();
Assert.assertEquals(suggestions.keySet().stream().sorted().toList(), objectTypes);
}

@Test
public void testQueryCompletionBasePathsOfUserType() {
String query = ". type UserType and ";
TypeDefinition typeDefinition = schemaRegistry.findTypeDefinitionByType(new QName("UserType"));
PrismObjectDefinition<?> objectDefinition = schemaRegistry.findObjectDefinitionByCompileTimeClass((Class) typeDefinition.getCompileTimeClass());
List<String> itemPaths = new ArrayList<>(objectDefinition.getItemNames().stream().map(QName::getLocalPart).toList());
itemPaths.add(".");
Map<String, String> suggestions = axiomQueryLangServiceImpl.queryCompletion(query);
Assert.assertEquals(suggestions.keySet().stream().sorted().toList(), itemPaths.stream().sorted().toList());
}

@Test
public void testQueryCompletionBaseFilterName() {
String query = ". type UserType and givenName ";

List<String> expected = new ArrayList<String>();
expected.add("levenshtein");
expected.add("greaterOrEqual");
expected.add("isRoot");
expected.add("inOrg");
expected.add("lessOrEqual");
expected.add("notEqual");
expected.add("fullText");
expected.add("less");
expected.add("type");
expected.add("equal");
expected.add("contains");
expected.add("ownedByOid");
expected.add("similarity");
expected.add("endsWith");
expected.add("exists");
expected.add("anyIn");
expected.add("greater");
expected.add("ownedBy");
expected.add("inOid");
expected.add("startsWith");
expected.add("not");

Map<String, String> suggestions = axiomQueryLangServiceImpl.queryCompletion(query);
Assert.assertEquals(suggestions.keySet().stream().sorted().toList(), expected.stream().sorted().toList());
}

@Test
public void testQueryCompletionBaseSubFilter() {
String query = ". type UserType and givenName equal \"Jack\" ";
Map<String, String> suggestions = axiomQueryLangServiceImpl.queryCompletion(query);
Assert.assertEquals(suggestions.keySet().stream().sorted().toList(), List.of("and", "or"));
}

// Advanced filters
}

0 comments on commit 9b318f8

Please sign in to comment.