Skip to content

Commit

Permalink
Remove two iterative lookups
Browse files Browse the repository at this point in the history
1. PrismSchemaImpl#findItemDefinitionsByCompileTimeClass iterated
through all definitions in given schema. Its results are now cached.

2. SchemaRegistryImpl#findSchemaByCompileTimeClass iterated through
all schemas. The schemas are now put into a map keyed by compile-time
class package.

(cherry picked from commit e84729b)
  • Loading branch information
mederly committed Nov 22, 2023
1 parent fdeec7e commit b8f1ea7
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static com.evolveum.midpoint.util.MiscUtil.stateCheck;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.namespace.QName;

import com.google.common.collect.HashMultimap;
Expand Down Expand Up @@ -94,12 +95,26 @@ public class PrismSchemaImpl extends AbstractFreezable implements MutablePrismSc
*/
private final Multimap<QName, ItemDefinition<?>> substitutions = HashMultimap.create();

/**
* Caches the result of {@link #findItemDefinitionsByCompileTimeClass(Class, Class)} queries.
*
* Just to be sure, this map is created as thread-safe. Normally, all additions to a prism schema occur before the
* regular operation is started. But we cannot assume this will be always the case.
*
* Temporary. The method is not quite sound anyway.
*/
private final Map<Class<?>, List<ItemDefinition<?>>> itemDefinitionsByCompileTimeClassMap = new ConcurrentHashMap<>();

public PrismSchemaImpl(@NotNull String namespace) {
argCheck(StringUtils.isNotEmpty(namespace), "Namespace can't be null or empty.");
this.namespace = namespace;
this.prismContext = PrismContext.get();
}

private void invalidateCaches() {
itemDefinitionsByCompileTimeClassMap.clear();
}

//region Trivia
@NotNull
@Override
Expand Down Expand Up @@ -130,6 +145,7 @@ public <T extends Definition> List<T> getDefinitions(@NotNull Class<T> type) {
public void addDelayedItemDefinition(DefinitionSupplier supplier) {
checkMutable();
delayedItemDefinitions.add(supplier);
invalidateCaches();
}

@NotNull
Expand Down Expand Up @@ -173,6 +189,8 @@ public void add(@NotNull Definition def) {
}
typeDefinitionMap.put(typeName, (TypeDefinition) def);
}

invalidateCaches();
}

@Override
Expand Down Expand Up @@ -387,28 +405,60 @@ public String toString() {
@NotNull
public <ID extends ItemDefinition> List<ID> findItemDefinitionsByCompileTimeClass(
@NotNull Class<?> compileTimeClass, @NotNull Class<ID> definitionClass) {
List<ID> found = new ArrayList<>();
var cached = itemDefinitionsByCompileTimeClassMap.get(compileTimeClass);
if (cached != null) {
return narrowIfNeeded(cached, definitionClass);
}
var fresh = findItemDefinitionsByCompileTimeClassInternal(compileTimeClass);
itemDefinitionsByCompileTimeClassMap.put(compileTimeClass, fresh);
return narrowIfNeeded(fresh, definitionClass);
}

/**
* Narrows the list of definitions (expected to be short) by given class. We expect that for the most of the time,
* all members would be matching. Hence, we first check, and only in the case of mismatch we create a narrowed list.
*
* TODO get rid of list management altogether
*/
private <ID extends ItemDefinition<?>> List<ID> narrowIfNeeded(
@NotNull List<ItemDefinition<?>> definitions, @NotNull Class<ID> definitionClass) {
for (ItemDefinition<?> definition : definitions) {
if (!definitionClass.isAssignableFrom(definition.getClass())) {
return narrow(definitions, definitionClass);
}
}
//noinspection unchecked
return (List<ID>) definitions;
}

private <ID extends ItemDefinition<?>> List<ID> narrow(List<ItemDefinition<?>> definitions, Class<ID> definitionClass) {
List<ID> narrowed = new ArrayList<>();
for (ItemDefinition<?> definition : definitions) {
if (definitionClass.isAssignableFrom(definition.getClass())) {
//noinspection unchecked
narrowed.add((ID) definition);
}
}
return narrowed;
}

private @NotNull List<ItemDefinition<?>> findItemDefinitionsByCompileTimeClassInternal(@NotNull Class<?> compileTimeClass) {
List<ItemDefinition<?>> found = new ArrayList<>();
for (Definition def : definitions) {
if (definitionClass.isAssignableFrom(def.getClass())) {
if (def instanceof PrismContainerDefinition) {
@SuppressWarnings("unchecked")
ID contDef = (ID) def;
if (compileTimeClass.equals(((PrismContainerDefinition<?>) contDef).getCompileTimeClass())) {
found.add(contDef);
}
} else if (def instanceof PrismPropertyDefinition) {
if (compileTimeClass.equals(prismContext.getSchemaRegistry().determineClassForType(def.getTypeName()))) {
@SuppressWarnings("unchecked")
ID itemDef = (ID) def;
found.add(itemDef);
}
} else {
// Skipping the definition, PRD is not supported here.
// Currently, this does not work sensibly for midPoint because it has multiple top-level
// elements for ObjectReferenceType (not to mention it's common-3, not Prism ORT).
// Instead, use findItemDefinitionsByElementName(...) to find the reference definition
// by well-known top-level element name .
if (def instanceof PrismContainerDefinition) {
if (compileTimeClass.equals(((PrismContainerDefinition<?>) def).getCompileTimeClass())) {
found.add((ItemDefinition<?>) def);
}
} else if (def instanceof PrismPropertyDefinition) {
if (compileTimeClass.equals(prismContext.getSchemaRegistry().determineClassForType(def.getTypeName()))) {
found.add((ItemDefinition<?>) def);
}
} else {
// Skipping the definition, PRD is not supported here.
// Currently, this does not work sensibly for midPoint because it has multiple top-level
// elements for ObjectReferenceType (not to mention it's common-3, not Prism ORT).
// Instead, use findItemDefinitionsByElementName(...) to find the reference definition
// by well-known top-level element name .
}
}
return found;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import javax.xml.transform.stream.StreamSource;
import java.io.InputStream;

import static com.evolveum.midpoint.util.MiscUtil.stateCheck;

/**
* Schema (prism or non-prism) with additional information.
*
Expand All @@ -37,6 +39,7 @@ public final class SchemaDescriptionImpl extends AbstractFreezable implements Sc
private boolean isDeclaredByDefault = false;
private PrismSchema schema;
private Package compileTimeClassesPackage;
private boolean registered;

private String defaultPrefix;

Expand Down Expand Up @@ -133,9 +136,15 @@ public Package getCompileTimeClassesPackage() {

void setCompileTimeClassesPackage(Package compileTimeClassesPackage) {
checkMutable();
stateCheck(!registered, "Not possible to set compile time classes package after registration: %s", this);
this.compileTimeClassesPackage = compileTimeClassesPackage;
}

synchronized void setRegistered() {
stateCheck(!registered, "Already registered: %s", this);
registered = true;
}

@Override
public boolean canInputStream() {
return streamable != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public class SchemaRegistryImpl implements DebugDumpable, SchemaRegistry {
*/
private final List<SchemaDescriptionImpl> schemaDescriptions = new ArrayList<>();

/** Map for fast lookup of schema descriptions by class or package. */
private final Map<Package, SchemaDescriptionImpl> schemaDescriptionMap = new HashMap<>();

/**
* Schema descriptions for a given namespace.
* In case of extension schemas there can be more schema descriptions with the same namespace!
Expand Down Expand Up @@ -421,6 +424,12 @@ private void registerSchemaDescription(SchemaDescriptionImpl desc) {
}
parsedSchemas.put(desc.getNamespace(), desc);
schemaDescriptions.add(desc);
Package pkg = desc.getCompileTimeClassesPackage();
if (pkg != null) {
schemaDescriptionMap.put(pkg, desc);
}
desc.setRegistered();

invalidateCaches();
}

Expand Down Expand Up @@ -1314,14 +1323,10 @@ public Collection<SchemaDescription> getSchemaDescriptions() {
public PrismSchema findSchemaByCompileTimeClass(@NotNull Class<?> compileTimeClass) {
Package compileTimePackage = compileTimeClass.getPackage();
if (compileTimePackage == null) {
return null; // e.g. for arrays
return null; // e.g. for arrays
}
for (SchemaDescription desc : schemaDescriptions) {
if (compileTimePackage.equals(desc.getCompileTimeClassesPackage())) {
return desc.getSchema();
}
}
return null;
var schemaDescription = schemaDescriptionMap.get(compileTimePackage);
return schemaDescription != null ? schemaDescription.getSchema() : null;
}

@Override
Expand Down

0 comments on commit b8f1ea7

Please sign in to comment.