diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismNamespaceContext.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismNamespaceContext.java index f67ca65d8e4..093df2469da 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismNamespaceContext.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/PrismNamespaceContext.java @@ -17,10 +17,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.Random; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; @@ -337,6 +340,11 @@ private Multimap nsToPrefix() { return nsToPrefix; } + @Override + public Builder childBuilder() { + return new Builder(this); + } + } private static class Inherited extends PrismNamespaceContext { @@ -415,6 +423,11 @@ public Map allPrefixes() { public PrismNamespaceContext rebasedOn(PrismNamespaceContext current) { return parent.rebasedOn(current); } + + @Override + public Builder childBuilder() { + return parent.childBuilder(); + } } private static class Empty extends PrismNamespaceContext { @@ -494,6 +507,11 @@ public PrismNamespaceContext rebasedOn(PrismNamespaceContext current) { return super.withoutDefault(); } + @Override + public Builder childBuilder() { + return builder(); + } + } public enum PrefixPreference { @@ -568,24 +586,99 @@ public String toString() { public static class Builder { + private PrismNamespaceContext parent; + protected Builder() { + parent = EMPTY; + } + public Builder(Impl impl) { + parent = impl; } private final Map prefixToNamespace = new HashMap<>(); + private final Map namespaceToPrefix = new HashMap<>(); public Builder addPrefix(String prefix, String namespace) { + var existing = parent.namespaceFor(prefix); + if (existing.isPresent() && existing.get().equals(namespace)) { + // No need to redefine existing prefix. + return this; + } + + String previous = prefixToNamespace.putIfAbsent(prefix, namespace); Preconditions.checkArgument(previous == null || namespace.equals(previous), "Prefix %s is already declared as '%s', trying to redeclare it as '%s'", prefix, previous, namespace); + namespaceToPrefix.put(namespace, prefix); return this; } public PrismNamespaceContext build() { - return from(prefixToNamespace); + return parent.childContext(prefixToNamespace); } public void defaultNamespace(String defaultNamespace) { addPrefix(DEFAULT_PREFIX, defaultNamespace); } + + public Optional namespaceFor(String prefix) { + if(prefix == null) { + prefix = DEFAULT_PREFIX; + } + String local = prefixToNamespace.get(prefix); + if (local != null) { + return Optional.of(local); + } + return parent.namespaceFor(prefix); + } + + public Optional prefixFor(String namespace) { + String local = namespaceToPrefix.get(namespace); + if (local != null) { + return Optional.of(local); + } + return parent.prefixFor(namespace); + } + + public void addPrefixes(Map undeclaredPrefixes) { + for (Map.Entry mapping : undeclaredPrefixes.entrySet()) { + addPrefix(mapping.getKey(), mapping.getValue()); + } + } + + public @NotNull String assignPrefixFor(@NotNull String namespaceURI, @Nullable String suggestedPrefix) { + if (Strings.isNullOrEmpty(namespaceURI)) { + // NO namespace, do not assign any prefix + return DEFAULT_PREFIX; + } + suggestedPrefix = suggestedPrefix != null ? suggestedPrefix : "gen"; + Optional directHit = namespaceFor(suggestedPrefix); + if (directHit.isPresent() && directHit.get().equals(namespaceURI)) { + // suggestedPrefix is already pointing to namespace, use it + return suggestedPrefix; + } + + Optional other = prefixFor(namespaceURI); + if (other.isPresent()) { + // Namespace has other prefix assigned, ignore suggested reuse parent + return other.get(); + } + + // We assign new prefix - suggestedPrefix or randomPrefix, if suggestedPrefix is unavailable. + suggestedPrefix = suggestedPrefix.isEmpty() ? "gen" : suggestedPrefix; + String assigned = suggestedPrefix; + while (namespaceFor(assigned).isPresent()) { + assigned = randomPrefix(suggestedPrefix); + } + addPrefix(assigned, namespaceURI); + return assigned; + } + + + private @NotNull String randomPrefix(@Nullable String base) { + return base + new Random().nextInt(999); + } } + + public abstract Builder childBuilder(); } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java index ed20a9a67ec..06aa245212e 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/PrismQuerySerializerImpl.java @@ -92,8 +92,8 @@ public void emit(String prefix) { spaceRequired = true; } - public String prefixFor(String namespaceURI) { - return prefixes.assignPrefixFor(namespaceURI); + public String prefixFor(String namespaceURI, String prefix) { + return prefixes.assignPrefixFor(namespaceURI, prefix); } @Override diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java index 5e3eaa57f82..7ae1fcdb113 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/lang/QueryWriter.java @@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable; import com.evolveum.axiom.concepts.Builder; +import com.evolveum.midpoint.prism.PrismNamespaceContext; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.impl.marshaller.ItemPathSerialization; import com.evolveum.midpoint.prism.path.ItemPath; @@ -142,7 +143,7 @@ private Optional lookupAlias(QName filter) { private void emitQName(QName filter, String additionalDefaultNs) { String prefix = resolvePrefix(filter, additionalDefaultNs); - if(prefix != null) { + if(!Strings.isNullOrEmpty(prefix)) { target.emit(prefix); target.emit(":"); } @@ -151,9 +152,9 @@ private void emitQName(QName filter, String additionalDefaultNs) { private String resolvePrefix(QName name, String additionalDefaultNs) { if (Strings.isNullOrEmpty(name.getNamespaceURI()) || Objects.equals(additionalDefaultNs, name.getNamespaceURI())) { - return null; + return PrismNamespaceContext.DEFAULT_PREFIX; } - return target.prefixFor(name.getNamespaceURI()); + return target.prefixFor(name.getNamespaceURI(), name.getPrefix()); }