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