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}