Skip to content

Commit

Permalink
Fix "unparseable PrimitiveXNode" hack (MID-3616)
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Mar 11, 2020
1 parent 9ff6a2b commit b39e36a
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 38 deletions.
Expand Up @@ -29,7 +29,7 @@ public interface PrimitiveXNode<T> extends XNode {
*/
String getStringValue();

T getParsedValue(@NotNull QName typeName, @Nullable Class<T> expectedClass) throws SchemaException;
<X> X getParsedValue(@NotNull QName typeName, @Nullable Class<X> expectedClass) throws SchemaException;

T getValue();

Expand Down
Expand Up @@ -23,6 +23,15 @@ public interface ValueParser<T> {

T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaException;

/**
* Checks if this parser can (by itself) parse the value as given type.
* (If it cannot, the parsing must be done by higher layers.)
*
* This method should be efficient. It may bring some performance penalty (when coupled with following
* parse() call) but we assume this situation is quite rare and performance effect is negligible.
*/
boolean canParseAs(QName typeName);

// This has to work even without the type
boolean isEmpty();

Expand Down
Expand Up @@ -55,6 +55,11 @@ public T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaE
}
}

@Override
public boolean canParseAs(QName typeName) {
return DOMUtil.XSD_QNAME.equals(typeName) || XsdTypeMapper.getXsdToJavaMapping(typeName) != null;
}

@Override
public boolean isEmpty() {
return DOMUtil.isEmpty(attr);
Expand Down
Expand Up @@ -10,6 +10,7 @@
import com.evolveum.midpoint.prism.impl.marshaller.ItemPathHolder;
import com.evolveum.midpoint.prism.marshaller.XNodeProcessorEvaluationMode;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.prism.xnode.ValueParser;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
Expand Down Expand Up @@ -51,20 +52,30 @@ public T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaE
if (ItemPathType.COMPLEX_TYPE.equals(typeName)) {
//noinspection unchecked
return (T) new ItemPathType(ItemPathHolder.parseFromString(textContent, visibleNamespaceDeclarations));
} else if (XmlTypeConverter.canConvert(typeName)) { // todo optimize redundant calls to canConvert/toJavaValue
//noinspection unchecked
return (T) XmlTypeConverter.toJavaValue(textContent, visibleNamespaceDeclarations, typeName);
} else if (DOMUtil.XSD_ANYTYPE.equals(typeName)) {
//noinspection unchecked
return (T) textContent; // if parsing primitive as xsd:anyType, we can safely parse it as string
} else {
throw new SchemaException("Cannot convert element/attribute '" + textContent + "' to " + typeName);
Class<?> javaType = XsdTypeMapper.getXsdToJavaMapping(typeName);
if (javaType != null) {
//noinspection unchecked
return (T) XmlTypeConverter.toJavaValue(textContent, visibleNamespaceDeclarations, javaType);
} else if (DOMUtil.XSD_ANYTYPE.equals(typeName)) {
//noinspection unchecked
return (T) textContent; // if parsing primitive as xsd:anyType, we can safely parse it as string
} else {
throw new SchemaException("Cannot convert element/attribute '" + textContent + "' to " + typeName);
}
}
} catch (IllegalArgumentException e) {
return DomLexicalProcessor.processIllegalArgumentException(textContent, typeName, e, mode); // primitive way of ensuring compatibility mode
}
}

@Override
public boolean canParseAs(QName typeName) {
return ItemPathType.COMPLEX_TYPE.equals(typeName) ||
XmlTypeConverter.canConvert(typeName) ||
DOMUtil.XSD_ANYTYPE.equals(typeName);
}

@Override
public boolean isEmpty() {
return StringUtils.isBlank(textContent);
Expand Down
Expand Up @@ -61,6 +61,13 @@ public T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaE
}
}

@Override
public boolean canParseAs(QName typeName) {
return ItemPathType.COMPLEX_TYPE.equals(typeName) ||
XsdTypeMapper.getXsdToJavaMapping(typeName) != null ||
DOMUtil.XSD_QNAME.equals(typeName);
}

@Override
public boolean isEmpty() {
return DOMUtil.isEmpty(element);
Expand Down
Expand Up @@ -7,15 +7,15 @@

package com.evolveum.midpoint.prism.impl.lex.json;

import java.util.Map;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.marshaller.XNodeProcessorEvaluationMode;
import com.evolveum.midpoint.prism.util.JavaTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.prism.xnode.ValueParser;
import com.evolveum.midpoint.util.exception.SchemaException;

import javax.xml.namespace.QName;
import java.util.Map;

public class JsonNullValueParser<T> implements ValueParser<T> {

public JsonNullValueParser() {
Expand All @@ -30,6 +30,11 @@ public T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaE
return (T) JavaTypeConverter.convert(clazz, "");
}

@Override
public boolean canParseAs(QName typeName) {
return XsdTypeMapper.toJavaTypeIfKnown(typeName) != null;
}

@Override
public boolean isEmpty() {
return true;
Expand Down
Expand Up @@ -56,6 +56,11 @@ public T parse(QName typeName, XNodeProcessorEvaluationMode mode) throws SchemaE
}
}

@Override
public boolean canParseAs(QName typeName) {
return XsdTypeMapper.toJavaTypeIfKnown(typeName) != null; // TODO do we have a reader for any relevant class?
}

@Override
public boolean isEmpty() {
return node == null || StringUtils.isBlank(node.asText()); // to be consistent with PrimitiveXNode.isEmpty for parsed values
Expand Down
Expand Up @@ -14,7 +14,6 @@
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.QNameUtil;
Expand All @@ -34,7 +33,6 @@

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
Expand Down Expand Up @@ -147,24 +145,26 @@ private <T> T unmarshalInternal(@NotNull XNodeImpl xnode, @NotNull Class<T> bean
// only maps and primitives and heterogeneous lists after this point

if (xnode instanceof PrimitiveXNodeImpl) {
//noinspection unchecked
PrimitiveXNodeImpl<T> prim = (PrimitiveXNodeImpl) xnode;
QName xsdType = XsdTypeMapper.getJavaToXsdMapping(beanClass);
if (xsdType != null) {
return prim.getParsedValue(xsdType, beanClass);
} else if (beanClass.isEnum()) {
return unmarshalEnumFromPrimitive(prim, beanClass, pc);
}
@SuppressWarnings("unchecked")
PrimitiveUnmarshaller<T> unmarshaller = specialPrimitiveUnmarshallers.get(beanClass);
if (unmarshaller != null) {
return unmarshaller.unmarshal(prim, beanClass, pc);
} else {
return unmarshallPrimitive(prim, beanClass, pc);
@SuppressWarnings("unchecked")
PrimitiveUnmarshaller<T> unmarshaller = specialPrimitiveUnmarshallers.get(beanClass);
if (unmarshaller != null) {
return unmarshaller.unmarshal(prim, beanClass, pc);
} else {
return unmarshalPrimitiveOther(prim, beanClass, pc);
}
}
} else {

if (beanClass.getPackage() == null || beanClass.getPackage().getName().equals("java.lang")) {
// We obviously have primitive data type, but we are asked to unmarshall from map xnode
// We obviously have primitive data type, but we are asked to unmarshal from map xnode
// NOTE: this may happen in XML when we have "empty" element, but it has some whitespace in it
// such as those troublesome newlines. This also happens if there is "empty" element
// but it contains an expression (so it is not PrimitiveXNode but MapXNode).
Expand Down Expand Up @@ -194,7 +194,7 @@ private <T> T unmarshalInternal(@NotNull XNodeImpl xnode, @NotNull Class<T> bean
/**
* For cases when XSD complex type has a simple content. In that case the resulting class has @XmlValue annotation.
*/
private <T> T unmarshallPrimitive(PrimitiveXNodeImpl<T> prim, Class<T> beanClass, ParsingContext pc) throws SchemaException {
private <T> T unmarshalPrimitiveOther(PrimitiveXNodeImpl<T> prim, Class<T> beanClass, ParsingContext pc) throws SchemaException {
if (prim.isEmpty()) {
return instantiateWithSubtypeGuess(beanClass, emptySet()); // Special case. Just return empty object
}
Expand Down Expand Up @@ -1117,9 +1117,10 @@ private Object unmarshalPolyStringFromPrimitive(PrimitiveXNodeImpl<?> node, Clas
throws SchemaException {
Object value;
if (node.isParsed()) {
value = node.getValue(); // there can be e.g. PolyString there
value = node.getValue(); // there can be e.g. PolyString there
} else {
value = ((PrimitiveXNodeImpl<String>) node).getParsedValue(DOMUtil.XSD_STRING, String.class);
//noinspection unchecked
value = node.getParsedValue(DOMUtil.XSD_STRING, String.class);
}
return toCorrectPolyStringClass(value, beanClass, node);
}
Expand Down Expand Up @@ -1167,6 +1168,7 @@ private Map<String, String> unmarshalLang(XNodeImpl xLang, ParsingContext pc) th
if (!(xLangEntryVal instanceof PrimitiveXNodeImpl)) {
throw new SchemaException("Polystring lang for key '"+key.getLocalPart()+"' is not primitive, it is "+xLangEntryVal);
}
//noinspection unchecked
String value = ((PrimitiveXNodeImpl<String>)xLangEntryVal).getParsedValue(DOMUtil.XSD_STRING, String.class);
lang.put(key.getLocalPart(), value);
}
Expand Down Expand Up @@ -1205,6 +1207,7 @@ private Object notSupported(XNodeImpl node, Class<?> beanClass, ParsingContext p
}

private XmlAsStringType unmarshalXmlAsStringFromPrimitive(PrimitiveXNodeImpl node, Class<XmlAsStringType> beanClass, ParsingContext parsingContext) throws SchemaException {
//noinspection unchecked
return new XmlAsStringType(((PrimitiveXNodeImpl<String>) node).getParsedValue(DOMUtil.XSD_STRING, String.class));
}

Expand All @@ -1229,11 +1232,10 @@ private RawType unmarshalRawType(XNodeImpl node, Class<RawType> beanClass, Parsi
return new RawType(node, prismContext);
}

private <T> T unmarshalEnumFromPrimitive(PrimitiveXNodeImpl prim, Class<T> beanClass, ParsingContext pc)
private <T> T unmarshalEnumFromPrimitive(PrimitiveXNodeImpl<?> prim, Class<T> beanClass, ParsingContext pc)
throws SchemaException {

String primValue = (String) prim.getParsedValue(DOMUtil.XSD_STRING, String.class);
primValue = StringUtils.trim(primValue);
String primValue = StringUtils.trim(prim.getParsedValue(DOMUtil.XSD_STRING, String.class));
if (StringUtils.isEmpty(primValue)) {
return null;
}
Expand Down
Expand Up @@ -73,19 +73,22 @@ public T getParsedValue(@NotNull QName typeName) throws SchemaException {
}

// @post: return value is type-compliant with expectedClass (if both are non-null)
public T getParsedValue(@NotNull QName typeName, @Nullable Class<T> expectedClass) throws SchemaException {
public <X> X getParsedValue(@NotNull QName typeName, @Nullable Class<X> expectedClass) throws SchemaException {
return getParsedValue(typeName, expectedClass, XNodeProcessorEvaluationMode.STRICT);
}

// @post: return value is type-compliant with expectedClass (if both are non-null)
public T getParsedValue(@NotNull QName typeName, @Nullable Class<T> expectedClass, XNodeProcessorEvaluationMode mode) throws SchemaException {
T parsedValue;
public <X> X getParsedValue(@NotNull QName typeName, @Nullable Class<X> expectedClass, XNodeProcessorEvaluationMode mode) throws SchemaException {
X parsedValue;
if (isParsed()) {
parsedValue = value;
//noinspection unchecked
parsedValue = (X) value;
} else {
parsedValue = valueParser.parse(typeName, mode);
//noinspection unchecked
parsedValue = (X) valueParser.parse(typeName, mode);
if (!immutable) {
value = parsedValue;
//noinspection unchecked
value = (T) parsedValue;
valueParser = null; // Necessary. It marks that the value is parsed. It also frees some memory.
}
}
Expand Down Expand Up @@ -192,15 +195,15 @@ public String getGuessedFormattedValue() throws SchemaException {
if (isParsed()) {
return getFormattedValue();
}
if (getTypeQName() == null) {
QName typeName = getTypeQName();
if (typeName == null) {
throw new IllegalStateException("Cannot fetch formatted value if type definition is not set");
}
// brutal hack - fix this! MID-3616
try {
T value = valueParser.parse(getTypeQName(), XNodeProcessorEvaluationMode.STRICT);
if (valueParser.canParseAs(typeName)) {
T value = valueParser.parse(typeName, XNodeProcessorEvaluationMode.STRICT);
return formatValue(value);
} catch (SchemaException e) {
return (String) valueParser.parse(DOMUtil.XSD_STRING, XNodeProcessorEvaluationMode.COMPAT);
} else {
return valueParser.getStringValue();
}
}

Expand Down
Expand Up @@ -147,6 +147,11 @@ public String parse(QName typeName, XNodeProcessorEvaluationMode mode) {
return value;
}

@Override
public boolean canParseAs(QName typeName) {
return true;
}

@Override
public boolean isEmpty() {
return false;
Expand Down

0 comments on commit b39e36a

Please sign in to comment.