diff --git a/core/pom.xml b/core/pom.xml index 41528b3aa..876449181 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,9 +22,9 @@ - ${scm.url}/tree/develop/core - HEAD - + ${scm.url}/tree/develop/core + HEAD + diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java index 5e3c64079..3f86824f6 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/AbstractDataTypeAdapter.java @@ -10,6 +10,7 @@ import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException; import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.model.util.JsonUtil; import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil; import org.codehaus.stax2.XMLEventReader2; @@ -93,24 +94,31 @@ public boolean isXmlMixed() { @Override public TYPE parse(XMLEventReader2 eventReader) throws IOException { StringBuilder builder = new StringBuilder(); + XMLEvent nextEvent; try { - XMLEvent nextEvent; while (!(nextEvent = eventReader.peek()).isEndElement()) { - if (nextEvent.isCharacters()) { - Characters characters = nextEvent.asCharacters(); - builder.append(characters.getData()); - // advance past current event - eventReader.nextEvent(); - } else { + if (!nextEvent.isCharacters()) { throw new IOException(String.format("Invalid content '%s' at %s", XmlEventUtil.toString(nextEvent), XmlEventUtil.toString(nextEvent.getLocation()))); } + Characters characters = nextEvent.asCharacters(); + builder.append(characters.getData()); + // advance past current event + eventReader.nextEvent(); } // trim leading and trailing whitespace - @SuppressWarnings("null") - @NonNull String value = builder.toString().trim(); - return parse(value); + String value = builder == null ? null : builder.toString().trim(); + try { + return parse(value); + } catch (IllegalArgumentException ex) { + throw new IOException( + String.format("Malformed data '%s'%s. %s", + value, + XmlEventUtil.generateLocationMessage(nextEvent), + ex.getLocalizedMessage()), + ex); + } } catch (XMLStreamException ex) { throw new IOException(ex); } @@ -124,11 +132,20 @@ public TYPE parse(XMLEventReader2 eventReader) throws IOException { public TYPE parse(JsonParser parser) throws IOException { String value = parser.getValueAsString(); if (value == null) { - throw new IOException("Unable to parse field value as text"); + throw new IOException("Unable to null value as text"); } // skip over value parser.nextToken(); - return parse(value); + try { + return parse(value); + } catch (IllegalArgumentException ex) { + throw new IOException( + String.format("Malformed data '%s'%s. %s", + value, + JsonUtil.generateLocationMessage(parser), + ex.getLocalizedMessage()), + ex); + } } @SuppressWarnings("null") @@ -175,7 +192,8 @@ public ITEM_TYPE cast(IAnyAtomicItem item) { ITEM_TYPE retval; if (item == null) { throw new InvalidValueForCastFunctionException("item is null"); - } else if (getItemClass().isAssignableFrom(item.getClass())) { + } + if (getItemClass().isAssignableFrom(item.getClass())) { @SuppressWarnings("unchecked") ITEM_TYPE typedItem = (ITEM_TYPE) item; retval = typedItem; } else { diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/IDataTypeAdapter.java b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/IDataTypeAdapter.java index 42a1ae21b..85e48aa23 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/IDataTypeAdapter.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/datatype/IDataTypeAdapter.java @@ -234,6 +234,8 @@ default boolean isAtomic() { * @return a supplier that will provide new instances of the parsed data * @throws IOException * if an error occurs while parsing + * @throws IllegalArgumentException + * if the provided value is invalid based on the data type * @see #parse(String) */ @NonNull diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java index 6018eb3b6..98c67c5d1 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/json/MetaschemaJsonReader.java @@ -316,7 +316,17 @@ public IBoundObject readItemField(IBoundObject parentItem, IBoundDefinitionModel @Override public Object readItemFieldValue(IBoundObject parentItem, IBoundFieldValue fieldValue) throws IOException { // read the field value's value - return readScalarItem(fieldValue); + return checkMissingFieldValue(readScalarItem(fieldValue)); + } + + @NonNull + private Object checkMissingFieldValue(Object value) throws IOException { + if (value == null && LOGGER.isWarnEnabled()) { + LOGGER.atWarn().log("Missing property value%s", + JsonUtil.generateLocationMessage(getReader())); + } + // TODO: change nullness annotations to be @Nullable + return ObjectUtils.notNull(value); } @Override @@ -491,8 +501,17 @@ public void accept( // the field will be the JSON key String key = ObjectUtils.notNull(parser.currentName()); - Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); - jsonKey.setValue(parent, ObjectUtils.notNull(value.toString())); + try { + Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); + jsonKey.setValue(parent, ObjectUtils.notNull(value.toString())); + } catch (IllegalArgumentException ex) { + throw new IOException( + String.format("Malformed data '%s'%s. %s", + key, + JsonUtil.generateLocationMessage(parser), + ex.getLocalizedMessage()), + ex); + } // skip to the next token parser.nextToken(); @@ -626,14 +645,14 @@ private final class JsomValueKeyProblemHandler implements IJsonProblemHandler { @NonNull private final IJsonProblemHandler delegate; @NonNull - private final IBoundInstanceFlag jsonValueKyeFlag; + private final IBoundInstanceFlag jsonValueKeyFlag; private boolean foundJsonValueKey; // false private JsomValueKeyProblemHandler( @NonNull IJsonProblemHandler delegate, - @NonNull IBoundInstanceFlag jsonValueKyeFlag) { + @NonNull IBoundInstanceFlag jsonValueKeyFlag) { this.delegate = delegate; - this.jsonValueKyeFlag = jsonValueKyeFlag; + this.jsonValueKeyFlag = jsonValueKeyFlag; } @Override @@ -656,9 +675,17 @@ public boolean handleUnknownProperty( } else { // handle JSON value key String key = ObjectUtils.notNull(parser.currentName()); - Object keyValue = jsonValueKyeFlag.getJavaTypeAdapter().parse(key); - jsonValueKyeFlag.setValue(ObjectUtils.notNull(parentItem), keyValue); - + try { + Object keyValue = jsonValueKeyFlag.getJavaTypeAdapter().parse(key); + jsonValueKeyFlag.setValue(ObjectUtils.notNull(parentItem), keyValue); + } catch (IllegalArgumentException ex) { + throw new IOException( + String.format("Malformed data '%s'%s. %s", + key, + JsonUtil.generateLocationMessage(parser), + ex.getLocalizedMessage()), + ex); + } // advance past the field name JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); @@ -672,7 +699,6 @@ public boolean handleUnknownProperty( } return retval; } - } private class ModelInstanceReadHandler @@ -719,7 +745,8 @@ public Map readMap() throws IOException { IBoundInstanceModel instance = getCollectionInfo().getInstance(); - @SuppressWarnings("PMD.UseConcurrentHashMap") Map items = new LinkedHashMap<>(); + @SuppressWarnings("PMD.UseConcurrentHashMap") + Map items = new LinkedHashMap<>(); // A map value is always wrapped in a START_OBJECT, since fields are used for // the keys diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlReader.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlReader.java index e9fd1d80f..31c61b65e 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlReader.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/io/xml/MetaschemaXmlReader.java @@ -184,11 +184,20 @@ protected void readFlagInstances( XmlEventUtil.generateLocationMessage(attribute))); } } else { - // get the attribute value - Object value = instance.getDefinition().getJavaTypeAdapter().parse(ObjectUtils.notNull(attribute.getValue())); - // apply the value to the parentObject - instance.setValue(targetObject, value); - flagInstanceMap.remove(qname); + try { + // get the attribute value + Object value = instance.getDefinition().getJavaTypeAdapter().parse(ObjectUtils.notNull(attribute.getValue())); + // apply the value to the parentObject + instance.setValue(targetObject, value); + flagInstanceMap.remove(qname); + } catch (IllegalArgumentException ex) { + throw new IOException( + String.format("Malformed data '%s'%s. %s", + attribute.getValue(), + XmlEventUtil.generateLocationMessage(start), + ex.getLocalizedMessage()), + ex); + } } } @@ -453,6 +462,7 @@ private IBoundObject readDefinitionEl public Object readItemFlag( IBoundObject parent, IBoundInstanceFlag flag) throws IOException { + // should never be called throw new UnsupportedOperationException("handled by readFlagInstances()"); } @@ -481,7 +491,7 @@ public Object readItemField( XmlEventUtil.requireStartElement(getReader(), wrapper); } - Object retval = readScalarItem(instance); + Object retval = checkMissingFieldValue(readScalarItem(instance)); if (wrapper != null) { XmlEventUtil.skipWhitespace(getReader()); @@ -534,7 +544,19 @@ public IBoundObject readItemField( public Object readItemFieldValue( IBoundObject parent, IBoundFieldValue fieldValue) throws IOException { - return readScalarItem(fieldValue); + return checkMissingFieldValue(readScalarItem(fieldValue)); + } + + @NonNull + private Object checkMissingFieldValue(Object value) throws IOException { + if (value == null) { + StartElement start = getStartElement(); + throw new IOException( + String.format("Missing value in element '%s'%s", + start.getName(), + XmlEventUtil.generateLocationMessage(start))); + } + return value; } private void handleAssemblyDefinitionBody( @@ -578,7 +600,7 @@ public IBoundObject readItemAssembly( this::handleAssemblyDefinitionBody); } - @NonNull + @Nullable private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler) throws IOException { return handler.getJavaTypeAdapter().parse(getReader()); diff --git a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/info/IFeatureScalarItemValueHandler.java b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/info/IFeatureScalarItemValueHandler.java index bc2cd0edc..df1b96790 100644 --- a/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/info/IFeatureScalarItemValueHandler.java +++ b/databind/src/main/java/gov/nist/secauto/metaschema/databind/model/info/IFeatureScalarItemValueHandler.java @@ -16,6 +16,20 @@ public interface IFeatureScalarItemValueHandler extends IItemValueHandler, IValuedMutable { + /** + * Apply the string value. + *

+ * This first parses the value using the underlying data type implementation and + * then applies the parsed value. + * + * @param parent + * the parent object to apply the value to + * @param text + * the value to parse + * @throws IllegalArgumentException + * if the text was malformed + * @see #getJavaTypeAdapter() + */ default void setValue(@NonNull Object parent, @NonNull String text) { Object item = getValueFromString(text); setValue(parent, item); @@ -27,6 +41,16 @@ default String toStringFromItem(@NonNull Object parent) { return item == null ? null : getJavaTypeAdapter().asString(item); } + /** + * Parse a string value using the underlying data type implementation. + * + * @param text + * the value to parse + * @return the parsed value + * @throws IllegalArgumentException + * if the text was malformed + * @see #getJavaTypeAdapter() + */ default Object getValueFromString(@NonNull String text) { return getJavaTypeAdapter().parse(text); }