1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.io.json;
7   
8   import com.fasterxml.jackson.core.JsonFactory;
9   import com.fasterxml.jackson.core.JsonParser;
10  
11  import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
12  import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
14  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
15  import gov.nist.secauto.metaschema.core.model.IBoundObject;
16  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
17  import gov.nist.secauto.metaschema.databind.io.AbstractDeserializer;
18  import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
19  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
20  
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.net.URI;
24  
25  import edu.umd.cs.findbugs.annotations.NonNull;
26  import nl.talsmasoftware.lazy4j.Lazy;
27  
28  /**
29   * Provides support for reading JSON-based data based on a bound Metaschema
30   * module.
31   *
32   * @param <CLASS>
33   *          the Java type of the bound object representing the root node to read
34   */
35  public class DefaultJsonDeserializer<CLASS extends IBoundObject>
36      extends AbstractDeserializer<CLASS> {
37    private Lazy<JsonFactory> factory;
38  
39    /**
40     * Construct a new JSON deserializer that will parse the bound class identified
41     * by the {@code classBinding}.
42     *
43     * @param definition
44     *          the bound class information for the Java type this deserializer is
45     *          operating on
46     */
47    public DefaultJsonDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
48      super(definition);
49      resetFactory();
50    }
51  
52    /**
53     * For use by subclasses to reset the underlying JSON factory when an important
54     * change has occurred that will change how the factory produces a
55     * {@link JsonParser}.
56     */
57    protected final void resetFactory() {
58      this.factory = Lazy.lazy(this::newFactoryInstance);
59    }
60  
61    @Override
62    protected void configurationChanged(IMutableConfiguration<DeserializationFeature<?>> config) {
63      super.configurationChanged(config);
64      resetFactory();
65    }
66  
67    /**
68     * Get a JSON factory instance.
69     * <p>
70     * This method can be used by sub-classes to create a customized factory
71     * instance.
72     *
73     * @return the factory
74     */
75    @NonNull
76    protected JsonFactory newFactoryInstance() {
77      return JsonFactoryFactory.instance();
78    }
79  
80    /**
81     * Get the parser factory associated with this deserializer.
82     *
83     * @return the factory instance
84     */
85    @NonNull
86    protected JsonFactory getJsonFactory() {
87      return ObjectUtils.notNull(factory.get());
88    }
89  
90    /**
91     * Using the managed JSON factory, create a new JSON parser instance using the
92     * provided reader.
93     *
94     * @param reader
95     *          the reader for the parser to read data from
96     * @return the new parser
97     * @throws IOException
98     *           if an error occurred while creating the parser
99     */
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 }