001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.databind.io.json; 007 008import com.fasterxml.jackson.core.JsonFactory; 009import com.fasterxml.jackson.core.JsonParser; 010 011import java.io.IOException; 012import java.io.Reader; 013import java.net.URI; 014 015import dev.metaschema.core.configuration.IConfiguration; 016import dev.metaschema.core.configuration.IMutableConfiguration; 017import dev.metaschema.core.metapath.item.node.INodeItem; 018import dev.metaschema.core.metapath.item.node.INodeItemFactory; 019import dev.metaschema.core.model.IBoundObject; 020import dev.metaschema.core.util.ObjectUtils; 021import dev.metaschema.databind.io.AbstractDeserializer; 022import dev.metaschema.databind.io.DeserializationFeature; 023import dev.metaschema.databind.model.IBoundDefinitionModelAssembly; 024import edu.umd.cs.findbugs.annotations.NonNull; 025import nl.talsmasoftware.lazy4j.Lazy; 026 027/** 028 * Provides support for reading JSON-based data based on a bound Metaschema 029 * module. 030 * 031 * @param <CLASS> 032 * the Java type of the bound object representing the root node to read 033 */ 034public class DefaultJsonDeserializer<CLASS extends IBoundObject> 035 extends AbstractDeserializer<CLASS> { 036 private Lazy<JsonFactory> factory; 037 038 /** 039 * Construct a new JSON deserializer that will parse the bound class identified 040 * by the {@code classBinding}. 041 * 042 * @param definition 043 * the bound class information for the Java type this deserializer is 044 * operating on 045 */ 046 public DefaultJsonDeserializer(@NonNull IBoundDefinitionModelAssembly definition) { 047 super(definition); 048 resetFactory(); 049 } 050 051 /** 052 * For use by subclasses to reset the underlying JSON factory when an important 053 * change has occurred that will change how the factory produces a 054 * {@link JsonParser}. 055 */ 056 protected final void resetFactory() { 057 this.factory = Lazy.of(this::newFactoryInstance); 058 } 059 060 @Override 061 protected void configurationChanged(IMutableConfiguration<DeserializationFeature<?>> config) { 062 super.configurationChanged(config); 063 resetFactory(); 064 } 065 066 /** 067 * Get a JSON factory instance. 068 * <p> 069 * This method can be used by sub-classes to create a customized factory 070 * instance. 071 * 072 * @return the factory 073 */ 074 @NonNull 075 protected JsonFactory newFactoryInstance() { 076 return JsonFactoryFactory.instance(); 077 } 078 079 /** 080 * Get the parser factory associated with this deserializer. 081 * 082 * @return the factory instance 083 */ 084 @NonNull 085 protected JsonFactory getJsonFactory() { 086 return ObjectUtils.notNull(factory.get()); 087 } 088 089 /** 090 * Using the managed JSON factory, create a new JSON parser instance using the 091 * provided reader. 092 * 093 * @param reader 094 * the reader for the parser to read data from 095 * @return the new parser 096 * @throws IOException 097 * if an error occurred while creating the parser 098 */ 099 @SuppressWarnings("resource") // reader resource not owned 100 @NonNull 101 protected final JsonParser newJsonParser(@NonNull Reader reader) throws IOException { 102 return ObjectUtils.notNull(getJsonFactory().createParser(reader)); 103 } 104 105 /** 106 * Create a new JSON reader with the appropriate problem handler based on the 107 * current configuration. 108 * 109 * @param jsonParser 110 * the JSON parser to use 111 * @param documentUri 112 * the URI of the document being parsed 113 * @return the new reader 114 * @throws IOException 115 * if an error occurred creating the reader 116 */ 117 @NonNull 118 private MetaschemaJsonReader newMetaschemaJsonReader( 119 @NonNull JsonParser jsonParser, 120 @NonNull URI documentUri) throws IOException { 121 boolean validateRequired = isFeatureEnabled(DeserializationFeature.DESERIALIZE_VALIDATE_REQUIRED_FIELDS); 122 return new MetaschemaJsonReader( 123 jsonParser, 124 documentUri, 125 new DefaultJsonProblemHandler(validateRequired)); 126 } 127 128 @Override 129 protected INodeItem deserializeToNodeItemInternal(@NonNull Reader reader, @NonNull URI documentUri) 130 throws IOException { 131 INodeItem retval; 132 try (JsonParser jsonParser = newJsonParser(reader)) { 133 MetaschemaJsonReader parser = newMetaschemaJsonReader(jsonParser, documentUri); 134 IBoundDefinitionModelAssembly definition = getDefinition(); 135 IConfiguration<DeserializationFeature<?>> configuration = getConfiguration(); 136 137 if (definition.isRoot() 138 && configuration.isFeatureEnabled(DeserializationFeature.DESERIALIZE_JSON_ROOT_PROPERTY)) { 139 // now parse the root property 140 CLASS value = ObjectUtils.requireNonNull(parser.readObjectRoot( 141 definition, 142 ObjectUtils.notNull(definition.getRootJsonName()))); 143 144 retval = INodeItemFactory.instance().newDocumentNodeItem(definition, documentUri, value); 145 } else { 146 // read the top-level definition 147 CLASS value = ObjectUtils.asType(parser.readObject(definition)); 148 149 retval = INodeItemFactory.instance().newAssemblyNodeItem(definition, documentUri, value); 150 } 151 return retval; 152 } 153 } 154 155 @Override 156 public CLASS deserializeToValueInternal(@NonNull Reader reader, @NonNull URI documentUri) throws IOException { 157 try (JsonParser jsonParser = newJsonParser(reader)) { 158 MetaschemaJsonReader parser = newMetaschemaJsonReader(jsonParser, documentUri); 159 IBoundDefinitionModelAssembly definition = getDefinition(); 160 IConfiguration<DeserializationFeature<?>> configuration = getConfiguration(); 161 162 CLASS retval; 163 if (definition.isRoot() 164 && configuration.isFeatureEnabled(DeserializationFeature.DESERIALIZE_JSON_ROOT_PROPERTY)) { 165 166 // now parse the root property 167 retval = ObjectUtils.requireNonNull(parser.readObjectRoot( 168 definition, 169 ObjectUtils.notNull(definition.getRootJsonName()))); 170 } else { 171 // read the top-level definition 172 retval = ObjectUtils.asType(ObjectUtils.requireNonNull( 173 parser.readObject(definition))); 174 } 175 return retval; 176 } 177 } 178}