diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/PrimitiveXNode.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/PrimitiveXNode.java index 3090f4839d8..b9656cf81cf 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/PrimitiveXNode.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/PrimitiveXNode.java @@ -29,7 +29,7 @@ public interface PrimitiveXNode extends XNode { */ String getStringValue(); - T getParsedValue(@NotNull QName typeName, @Nullable Class expectedClass) throws SchemaException; + X getParsedValue(@NotNull QName typeName, @Nullable Class expectedClass) throws SchemaException; T getValue(); diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/ValueParser.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/ValueParser.java index 85cfbab71b8..bd3f0c36d69 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/ValueParser.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xnode/ValueParser.java @@ -23,6 +23,15 @@ public interface ValueParser { 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(); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/AttributeValueParser.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/AttributeValueParser.java index 43f6aa069f3..3fc53b1a79b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/AttributeValueParser.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/AttributeValueParser.java @@ -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); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/DomLessValueParser.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/DomLessValueParser.java index 5154da12415..5d88471675c 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/DomLessValueParser.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/DomLessValueParser.java @@ -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; @@ -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); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/ElementValueParser.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/ElementValueParser.java index 0efa3bf376e..2113068b19a 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/ElementValueParser.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/dom/ElementValueParser.java @@ -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); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonNullValueParser.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonNullValueParser.java index 7c84f70a6b6..000df0d87a5 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonNullValueParser.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonNullValueParser.java @@ -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 implements ValueParser { public JsonNullValueParser() { @@ -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; diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonValueParser.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonValueParser.java index f63095c1a6f..4c6b1be9bd3 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonValueParser.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/lex/json/JsonValueParser.java @@ -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 diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java index 1024025b13a..c6d042f9597 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanUnmarshaller.java @@ -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; @@ -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.*; @@ -147,24 +145,26 @@ private T unmarshalInternal(@NotNull XNodeImpl xnode, @NotNull Class bean // only maps and primitives and heterogeneous lists after this point if (xnode instanceof PrimitiveXNodeImpl) { + //noinspection unchecked PrimitiveXNodeImpl 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 unmarshaller = specialPrimitiveUnmarshallers.get(beanClass); - if (unmarshaller != null) { - return unmarshaller.unmarshal(prim, beanClass, pc); } else { - return unmarshallPrimitive(prim, beanClass, pc); + @SuppressWarnings("unchecked") + PrimitiveUnmarshaller 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). @@ -194,7 +194,7 @@ private T unmarshalInternal(@NotNull XNodeImpl xnode, @NotNull Class bean /** * For cases when XSD complex type has a simple content. In that case the resulting class has @XmlValue annotation. */ - private T unmarshallPrimitive(PrimitiveXNodeImpl prim, Class beanClass, ParsingContext pc) throws SchemaException { + private T unmarshalPrimitiveOther(PrimitiveXNodeImpl prim, Class beanClass, ParsingContext pc) throws SchemaException { if (prim.isEmpty()) { return instantiateWithSubtypeGuess(beanClass, emptySet()); // Special case. Just return empty object } @@ -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) node).getParsedValue(DOMUtil.XSD_STRING, String.class); + //noinspection unchecked + value = node.getParsedValue(DOMUtil.XSD_STRING, String.class); } return toCorrectPolyStringClass(value, beanClass, node); } @@ -1167,6 +1168,7 @@ private Map 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)xLangEntryVal).getParsedValue(DOMUtil.XSD_STRING, String.class); lang.put(key.getLocalPart(), value); } @@ -1205,6 +1207,7 @@ private Object notSupported(XNodeImpl node, Class beanClass, ParsingContext p } private XmlAsStringType unmarshalXmlAsStringFromPrimitive(PrimitiveXNodeImpl node, Class beanClass, ParsingContext parsingContext) throws SchemaException { + //noinspection unchecked return new XmlAsStringType(((PrimitiveXNodeImpl) node).getParsedValue(DOMUtil.XSD_STRING, String.class)); } @@ -1229,11 +1232,10 @@ private RawType unmarshalRawType(XNodeImpl node, Class beanClass, Parsi return new RawType(node, prismContext); } - private T unmarshalEnumFromPrimitive(PrimitiveXNodeImpl prim, Class beanClass, ParsingContext pc) + private T unmarshalEnumFromPrimitive(PrimitiveXNodeImpl prim, Class 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; } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/PrimitiveXNodeImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/PrimitiveXNodeImpl.java index 82ae2c18ce3..7cb766183f7 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/PrimitiveXNodeImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/xnode/PrimitiveXNodeImpl.java @@ -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 expectedClass) throws SchemaException { + public X getParsedValue(@NotNull QName typeName, @Nullable Class 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 expectedClass, XNodeProcessorEvaluationMode mode) throws SchemaException { - T parsedValue; + public X getParsedValue(@NotNull QName typeName, @Nullable Class 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. } } @@ -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(); } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestModelWebServiceNegative.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestModelWebServiceNegative.java index a02d4fa8db0..80d89536b68 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestModelWebServiceNegative.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestModelWebServiceNegative.java @@ -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;