Skip to content

Commit

Permalink
JsonReader: Use PrismNamespaceContext during parsing
Browse files Browse the repository at this point in the history
Signed-off-by: Tony Tkacik <tonydamage@gmail.com>
  • Loading branch information
tonydamage committed Jan 29, 2021
1 parent 37d0553 commit 8c7e1c0
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 49 deletions.
Expand Up @@ -82,6 +82,10 @@ public abstract class PrismNamespaceContext {
*/
public abstract Optional<String> namespaceFor(String prefix);

public Optional<String> defaultNamespace() {
return namespaceFor(DEFAULT_PREFIX);
}

/**
* Look up suitable prefix for namespace using default prefix search preference-
*
Expand Down
Expand Up @@ -225,4 +225,8 @@ <ID extends ItemDefinition> ComparisonResult compareDefinitions(@NotNull ID def1
interface InvalidationListener {
void invalidate();
}

default PrismNamespaceContext globalNamespaceContext() {
return PrismNamespaceContext.EMPTY; // FIXME later
}
}
Expand Up @@ -9,6 +9,7 @@

import com.evolveum.midpoint.prism.ParserSource;
import com.evolveum.midpoint.prism.ParsingContext;
import com.evolveum.midpoint.prism.PrismNamespaceContext;
import com.evolveum.midpoint.prism.impl.ParsingContextImpl;
import com.evolveum.midpoint.prism.impl.lex.LexicalProcessor;
import com.evolveum.midpoint.prism.impl.xnode.*;
Expand Down Expand Up @@ -45,8 +46,11 @@ public abstract class AbstractReader {

@NotNull protected final SchemaRegistry schemaRegistry;

private final PrismNamespaceContext namespaceContext;

AbstractReader(@NotNull SchemaRegistry schemaRegistry) {
this.schemaRegistry = schemaRegistry;
this.namespaceContext = schemaRegistry.globalNamespaceContext();
}

@NotNull
Expand Down Expand Up @@ -128,9 +132,9 @@ private void readTreatingExceptions(boolean expectingMultipleObjects, JsonParser
try {
readFirstTokenAndCheckEmptyInput(configuredParser);
if (supportsMultipleDocuments()) {
new MultiDocumentReader(ctx).read(expectingMultipleObjects);
new MultiDocumentReader(ctx, globalNamespaceContext()).read(expectingMultipleObjects);
} else {
new DocumentReader(ctx).read(expectingMultipleObjects);
new DocumentReader(ctx, globalNamespaceContext()).read(expectingMultipleObjects);
}
} catch (SchemaException e) {
throw e;
Expand All @@ -143,6 +147,10 @@ private void readTreatingExceptions(boolean expectingMultipleObjects, JsonParser
}
}

private PrismNamespaceContext globalNamespaceContext() {
return namespaceContext;
}

abstract boolean supportsMultipleDocuments();

private void readFirstTokenAndCheckEmptyInput(JsonParser configuredParser) throws IOException, SchemaException {
Expand Down
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.core.JsonToken;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.PrismNamespaceContext;
import com.evolveum.midpoint.util.exception.SchemaException;

/**
Expand All @@ -22,9 +23,11 @@
class DocumentReader {

@NotNull private final JsonReadingContext ctx;
private final PrismNamespaceContext nsContext;

DocumentReader(@NotNull JsonReadingContext ctx) {
DocumentReader(@NotNull JsonReadingContext ctx, PrismNamespaceContext context) {
this.ctx = ctx;
this.nsContext = context;
}

void read(boolean expectingMultipleObjects) throws IOException, SchemaException {
Expand All @@ -40,6 +43,6 @@ void read(boolean expectingMultipleObjects) throws IOException, SchemaException
}

private void read() throws IOException, SchemaException {
new RootObjectReader(ctx).read();
new RootObjectReader(ctx, nsContext).read();
}
}
Expand Up @@ -14,6 +14,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.xml.namespace.QName;

Expand All @@ -26,6 +27,7 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;

import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -54,7 +56,9 @@ class JsonObjectTokenReader {
* Map corresponding to the current object.
* It might or might not be used as a return value - depending on circumstances.
*/
@NotNull private final MapXNodeImpl map = new MapXNodeImpl();
@NotNull private MapXNodeImpl map;

private final PrismNamespaceContext parentContext;

/**
* Name of the type of this XNode.
Expand Down Expand Up @@ -86,15 +90,6 @@ class JsonObjectTokenReader {
*/
private Boolean incomplete;

/**
* Namespace (@ns).
* Should be set only once.
*/
private String declaredNamespace;


private PrismNamespaceContext context;

private boolean namespaceSensitiveStarted = false;

private static final Map<QName, ItemProcessor> PROCESSORS = ImmutableMap.<QName, ItemProcessor>builder()
Expand All @@ -113,9 +108,10 @@ class JsonObjectTokenReader {

private static final ItemProcessor STANDARD_PROCESSOR = namespaceSensitive(JsonObjectTokenReader::processStandardFieldValue);

JsonObjectTokenReader(@NotNull JsonReadingContext ctx) {
JsonObjectTokenReader(@NotNull JsonReadingContext ctx, PrismNamespaceContext parentContext) {
this.ctx = ctx;
this.parser = ctx.parser;
this.parentContext = parentContext;
}

/**
Expand Down Expand Up @@ -160,7 +156,23 @@ private void processFields() throws IOException, SchemaException {
if (currentFieldName != null) {
warnOrThrow("Two field names in succession: " + currentFieldName + " and " + newFieldName);
}
return QNameUtil.uriToQNameInfo(newFieldName, true);
return resolveQNameInfo(newFieldName);
}

private @NotNull QNameInfo resolveQNameInfo(String name) {

QNameInfo result = QNameUtil.uriToQNameInfo(name, true);
if(name.startsWith("@")) {
// Infra properties are unqualified
return result;
}
if(Strings.isNullOrEmpty(result.name.getNamespaceURI())) {
Optional<String> defaultNs = namespaceContext().defaultNamespace();
if(defaultNs.isPresent()) {
result = QNameUtil.qnameToQnameInfo(new QName(defaultNs.get(), result.name.getLocalPart()));
}
}
return result;
}

private void processFieldValue(QNameInfo name) throws IOException, SchemaException {
Expand All @@ -171,17 +183,27 @@ private void processFieldValue(QNameInfo name) throws IOException, SchemaExcepti
}

private XNodeImpl readValue() throws IOException, SchemaException {
return new JsonOtherTokenReader(ctx).readValue();
return new JsonOtherTokenReader(ctx,namespaceContext().inherited()).readValue();
}

private PrismNamespaceContext namespaceContext() {
if(map != null) {
return map.namespaceContext();
}
return parentContext.inherited();
}

private void processContextDeclaration(QNameInfo name, XNodeImpl value) {
throw new UnsupportedOperationException("Not implemented");
}

private void processStandardFieldValue(QNameInfo currentFieldName, @NotNull XNodeImpl currentFieldValue) {
// Beware of potential unqualified value conflict (see MID-5326).
// MID-5326:
// If namespace is defined, fieldName is always qualified,
// If namespace is undefined, then we can not effectivelly distinguish between
// Therefore we use special "default-namespace" marker that is dealt with later.
QName key;

if (currentFieldName.explicitEmptyNamespace || QNameUtil.isQualified(currentFieldName.name)) {
key = currentFieldName.name;
} else {
Expand Down Expand Up @@ -231,7 +253,8 @@ private void processElementNameDeclaration(QNameInfo currentFieldName, XNodeImpl
if (elementName != null) {
warnOrThrow("Element name defined more than once");
}
elementName = QNameUtil.uriToQNameInfo(getCurrentFieldStringValue(currentFieldName, value), true);
String name = getCurrentFieldStringValue(currentFieldName, value);
elementName = resolveQNameInfo(name);
}

private void processTypeDeclaration(QNameInfo currentFieldName, XNodeImpl value) throws SchemaException {
Expand All @@ -245,24 +268,24 @@ private void processNamespaceDeclaration(QNameInfo currentFieldName, XNodeImpl v
if(namespaceSensitiveStarted) {
warnOrThrow("Namespace declared after other fields: " + ctx.getPositionSuffix());
}
if (declaredNamespace != null) {
warnOrThrow("Default namespace defined more than once");
if (map != null) {
warnOrThrow("Namespace defined more than once");
}
var ns = getCurrentFieldStringValue(currentFieldName, value);
context = PrismNamespaceContext.of(ns);
declaredNamespace = ns;
map = new MapXNodeImpl(parentContext.childContext(ImmutableMap.of("", ns)));
}

@NotNull
private XNodeImpl postProcess() throws SchemaException {
startNamespaceSensitive();
// Return either map or primitive value (in case of @type/@value) or incomplete xnode
int haveRegular = !map.isEmpty() ? 1 : 0;
int haveWrapped = wrappedValue != null ? 1 : 0;
int haveIncomplete = Boolean.TRUE.equals(incomplete) ? 1 : 0;
XNodeImpl rv;

if (haveRegular + haveWrapped + haveIncomplete > 1) {
warnOrThrow("More than one of '" + JsonInfraItems.PROP_VALUE + "', '" + JsonInfraItems.PROP_INCOMPLETE
warnOrThrow("More than one of '" + PROP_VALUE + "', '" + PROP_INCOMPLETE
+ "' and regular content present");
rv = map;
} else {
Expand Down Expand Up @@ -299,15 +322,6 @@ private XNodeImpl postProcess() throws SchemaException {
}
}

if (declaredNamespace != null) {
if (rv instanceof MapXNodeImpl) {
ctx.defaultNamespaces.put((MapXNodeImpl) rv, declaredNamespace);
}
for (MapXNode metadataNode : metadata) {
ctx.defaultNamespaces.put((MapXNodeImpl) metadataNode, declaredNamespace);
}
}

if (!metadata.isEmpty()) {
if (rv instanceof MetadataAware) {
((MetadataAware) rv).setMetadataNodes(metadata);
Expand All @@ -332,7 +346,8 @@ private XNodeProcessorEvaluationMode getEvaluationMode() {
return ctx.prismParsingContext.getEvaluationMode();
}

private void warnOrThrow(String message) throws SchemaException {
private void warnOrThrow(String format, Object... args) throws SchemaException {
String message = Strings.lenientFormat(format, args);
ctx.prismParsingContext.warnOrThrow(LOGGER, message + ". At " + ctx.getPositionSuffix());
}

Expand All @@ -346,6 +361,9 @@ private static ItemProcessor namespaceSensitive(ItemProcessor processor) {

private void startNamespaceSensitive() {
namespaceSensitiveStarted = true;
if(map == null) {
map = new MapXNodeImpl(parentContext);
}
}


Expand Down
Expand Up @@ -17,6 +17,7 @@
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.PrismNamespaceContext;
import com.evolveum.midpoint.prism.impl.lex.json.JsonNullValueParser;
import com.evolveum.midpoint.prism.impl.lex.json.JsonValueParser;
import com.evolveum.midpoint.prism.impl.xnode.ListXNodeImpl;
Expand All @@ -37,17 +38,20 @@ class JsonOtherTokenReader {
@NotNull private final JsonReadingContext ctx;
@NotNull private final JsonParser parser;

JsonOtherTokenReader(JsonReadingContext ctx) {
private final PrismNamespaceContext parentContext;

JsonOtherTokenReader(JsonReadingContext ctx, PrismNamespaceContext context) {
this.ctx = ctx;
this.parser = ctx.parser;
this.parentContext = context;
}

@NotNull XNodeImpl readValue() throws IOException, SchemaException {
JsonToken currentToken = Objects.requireNonNull(parser.currentToken(), "currentToken");

switch (currentToken) {
case START_OBJECT:
return new JsonObjectTokenReader(ctx).read();
return new JsonObjectTokenReader(ctx, parentContext).read();
case START_ARRAY:
return parseToList();
case VALUE_STRING:
Expand Down Expand Up @@ -86,7 +90,7 @@ private ListXNodeImpl parseToList() throws SchemaException, IOException {
}

private <T> PrimitiveXNodeImpl<T> parseToPrimitive() throws IOException, SchemaException {
PrimitiveXNodeImpl<T> primitive = new PrimitiveXNodeImpl<>();
PrimitiveXNodeImpl<T> primitive = new PrimitiveXNodeImpl<>(this.parentContext);

Object tid = parser.getTypeId();
if (tid != null) {
Expand Down
Expand Up @@ -10,7 +10,6 @@
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.impl.ParsingContextImpl;
import com.evolveum.midpoint.prism.impl.lex.LexicalProcessor;
import com.evolveum.midpoint.prism.impl.xnode.MapXNodeImpl;
import com.evolveum.midpoint.prism.impl.xnode.XNodeImpl;
import com.fasterxml.jackson.core.JsonParser;
import org.jetbrains.annotations.NotNull;
Expand All @@ -30,11 +29,6 @@ class JsonReadingContext {

private boolean aborted;

// TODO consider getting rid of these IdentityHashMaps by support default namespace marking and resolution
// directly in XNode structures (like it was done for Map XNode keys recently).

// Definitions of namespaces ('@ns') within maps; to be applied after parsing.
@NotNull final IdentityHashMap<MapXNodeImpl, String> defaultNamespaces = new IdentityHashMap<>();
// Elements that should be skipped when filling-in default namespaces - those that are explicitly set with no-NS ('#name').
// (Values for these entries are not important. Only key presence is relevant.)
@NotNull final IdentityHashMap<XNodeImpl, Object> noNamespaceElementNames = new IdentityHashMap<>();
Expand Down
Expand Up @@ -11,6 +11,7 @@

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.PrismNamespaceContext;
import com.evolveum.midpoint.util.exception.SchemaException;

/**
Expand All @@ -22,14 +23,16 @@ class MultiDocumentReader {
* TODO
*/
@NotNull private final JsonReadingContext ctx;
private final PrismNamespaceContext nsContext;

MultiDocumentReader(@NotNull JsonReadingContext ctx) {
MultiDocumentReader(@NotNull JsonReadingContext ctx, PrismNamespaceContext global) {
this.ctx = ctx;
this.nsContext = global;
}

public void read(boolean expectingMultipleObjects) throws IOException, SchemaException {
do {
new DocumentReader(ctx).read(expectingMultipleObjects);
new DocumentReader(ctx,nsContext).read(expectingMultipleObjects);
} while (!ctx.isAborted() && ctx.parser.nextToken() != null); // YAML multi-document files
}
}
Expand Up @@ -14,6 +14,7 @@
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.PrismNamespaceContext;
import com.evolveum.midpoint.prism.impl.lex.json.JsonValueParser;
import com.evolveum.midpoint.prism.impl.xnode.*;
import com.evolveum.midpoint.prism.xnode.MapXNode;
Expand All @@ -31,12 +32,15 @@ class RootObjectReader {

@NotNull private final JsonReadingContext ctx;

RootObjectReader(@NotNull JsonReadingContext ctx) {
private final PrismNamespaceContext nsContext;

RootObjectReader(@NotNull JsonReadingContext ctx, PrismNamespaceContext context) {
this.ctx = ctx;
this.nsContext = context;
}

void read() throws SchemaException, IOException {
XNodeImpl xnode = new JsonOtherTokenReader(ctx).readValue();
XNodeImpl xnode = new JsonOtherTokenReader(ctx, nsContext).readValue();
RootXNodeImpl root = postProcessValueToRoot(xnode, null);
if (!ctx.objectHandler.handleData(root)) {
ctx.setAborted();
Expand Down Expand Up @@ -64,7 +68,7 @@ private RootXNodeImpl postProcessValueToRoot(XNodeImpl xnode, String defaultName
private void processDefaultNamespaces(XNodeImpl xnode, String parentDefault, JsonReadingContext ctx) {
if (xnode instanceof MapXNodeImpl) {
MapXNodeImpl map = (MapXNodeImpl) xnode;
String currentDefault = ctx.defaultNamespaces.getOrDefault(map, parentDefault);
String currentDefault = xnode.namespaceContext().defaultNamespace().orElse("");
map.replaceDefaultNamespaceMarkers(DEFAULT_NAMESPACE_MARKER, currentDefault);
for (Map.Entry<QName, XNodeImpl> entry : map.entrySet()) {
processDefaultNamespaces(entry.getValue(), currentDefault, ctx);
Expand Down

0 comments on commit 8c7e1c0

Please sign in to comment.