1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.io;
7   
8   import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
9   import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
10  import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
12  import gov.nist.secauto.metaschema.core.model.AbstractResourceResolver;
13  import gov.nist.secauto.metaschema.core.model.IBoundObject;
14  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
15  import gov.nist.secauto.metaschema.databind.IBindingContext;
16  import gov.nist.secauto.metaschema.databind.io.ModelDetector.Result;
17  
18  import org.eclipse.jdt.annotation.NotOwning;
19  import org.eclipse.jdt.annotation.Owning;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.URI;
24  import java.net.URL;
25  import java.util.Map;
26  
27  import edu.umd.cs.findbugs.annotations.NonNull;
28  
29  /**
30   * A default implementation of an {@link IBoundLoader}.
31   */
32  public class DefaultBoundLoader
33      extends AbstractResourceResolver
34      implements IBoundLoader {
35    public static final int LOOK_AHEAD_BYTES = 32_768;
36    // @NonNull
37    // private static final JsonFactory JSON_FACTORY = new JsonFactory();
38    // @NonNull
39    // private static final XmlFactory XML_FACTORY = new XmlFactory();
40    // @NonNull
41    // private static final YAMLFactory YAML_FACTORY = new YAMLFactory();
42  
43    private FormatDetector formatDetector;
44  
45    private ModelDetector modelDetector;
46  
47    @NonNull
48    private final IBindingContext bindingContext;
49    @NonNull
50    private final IMutableConfiguration<DeserializationFeature<?>> configuration;
51  
52    /**
53     * Construct a new loader instance, using the provided {@link IBindingContext}.
54     *
55     * @param bindingContext
56     *          the Module binding context to use to load Java types
57     */
58    public DefaultBoundLoader(@NonNull IBindingContext bindingContext) {
59      this.bindingContext = bindingContext;
60      this.configuration = new DefaultConfiguration<>();
61    }
62  
63    @NonNull
64    private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() {
65      return configuration;
66    }
67  
68    @Override
69    public boolean isFeatureEnabled(DeserializationFeature<?> feature) {
70      return getConfiguration().isFeatureEnabled(feature);
71    }
72  
73    @Override
74    public Map<DeserializationFeature<?>, Object> getFeatureValues() {
75      return getConfiguration().getFeatureValues();
76    }
77  
78    @Override
79    public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) {
80      getConfiguration().applyConfiguration(other);
81      resetDetector();
82      return this;
83    }
84  
85    @SuppressWarnings("PMD.NullAssignment")
86    private void resetDetector() {
87      // reset the detector
88      formatDetector = null;
89    }
90  
91    @Override
92    public IBoundLoader set(DeserializationFeature<?> feature, Object value) {
93      getConfiguration().set(feature, value);
94      resetDetector();
95      return this;
96    }
97  
98    @Override
99    public IBindingContext getBindingContext() {
100     return bindingContext;
101   }
102 
103   @Override
104   public Format detectFormat(@NonNull URI uri) throws IOException {
105     URI resourceUri = resolve(uri);
106     URL resource = resourceUri.toURL();
107 
108     try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
109       return detectFormat(is).getFormat();
110     }
111   }
112 
113   @Override
114   public FormatDetector.Result detectFormat(@NonNull InputStream is) throws IOException {
115     return getFormatDetector().detect(is);
116   }
117 
118   @NonNull
119   private FormatDetector getFormatDetector() {
120     if (formatDetector == null) {
121       formatDetector = new FormatDetector(getConfiguration());
122     }
123     assert formatDetector != null;
124     return formatDetector;
125   }
126 
127   @NonNull
128   private ModelDetector getModelDetector() {
129     if (modelDetector == null) {
130       modelDetector = new ModelDetector(
131           getBindingContext(),
132           getConfiguration());
133     }
134     assert modelDetector != null;
135     return modelDetector;
136   }
137 
138   @Override
139   @Owning
140   public Result detectModel(@NotOwning InputStream is, Format format) throws IOException {
141     return getModelDetector().detect(is, format);
142   }
143 
144   @Override
145   public <CLASS extends IBoundObject> CLASS load(@NonNull URI uri) throws IOException {
146     URI resourceUri = resolve(uri);
147     URL resource = resourceUri.toURL();
148 
149     try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
150       return load(is, uri);
151     }
152   }
153 
154   @SuppressWarnings("unchecked")
155   @Override
156   @NonNull
157   public <CLASS extends IBoundObject> CLASS load(
158       @NotOwning @NonNull InputStream is,
159       @NonNull URI documentUri)
160       throws IOException {
161     FormatDetector.Result formatMatch = getFormatDetector().detect(is);
162     Format format = formatMatch.getFormat();
163 
164     try (InputStream formatStream = formatMatch.getDataStream()) {
165       try (ModelDetector.Result modelMatch = detectModel(formatStream, format)) {
166 
167         IDeserializer<?> deserializer = getDeserializer(
168             modelMatch.getBoundClass(),
169             format,
170             getConfiguration());
171         try (InputStream modelStream = modelMatch.getDataStream()) {
172           return (CLASS) deserializer.deserialize(modelStream, documentUri);
173         }
174       }
175     }
176   }
177 
178   @Override
179   public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, URI uri) throws IOException {
180     URI resourceUri = resolve(uri);
181     URL resource = resourceUri.toURL();
182 
183     try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
184       return load(clazz, is, resourceUri);
185     }
186   }
187 
188   @Override
189   public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, InputStream is, URI documentUri)
190       throws IOException {
191     // we cannot close this stream, since it will cause the underlying stream to be
192     // closed
193     FormatDetector.Result match = getFormatDetector().detect(is);
194     Format format = match.getFormat();
195 
196     try (InputStream remainingStream = match.getDataStream()) {
197       // is autoclosing ok?
198       return load(clazz, format, remainingStream, documentUri);
199     }
200   }
201 
202   @Override
203   @NonNull
204   public <CLASS extends IBoundObject> CLASS load(
205       @NonNull Class<CLASS> clazz,
206       @NonNull Format format,
207       @NonNull InputStream is,
208       @NonNull URI documentUri) throws IOException {
209 
210     IDeserializer<CLASS> deserializer = getDeserializer(clazz, format, getConfiguration());
211     return deserializer.deserialize(is, documentUri);
212   }
213 
214   @Override
215   public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
216     URI resourceUri = resolve(uri);
217     URL resource = resourceUri.toURL();
218 
219     try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
220       return loadAsNodeItem(is, resourceUri);
221     }
222   }
223 
224   @NonNull
225   private IDocumentNodeItem loadAsNodeItem(@NonNull InputStream is, @NonNull URI documentUri) throws IOException {
226     FormatDetector.Result formatMatch = getFormatDetector().detect(is);
227     Format format = formatMatch.getFormat();
228 
229     try (InputStream formatStream = formatMatch.getDataStream()) {
230       return loadAsNodeItem(format, formatStream, documentUri);
231     }
232   }
233 
234   @Override
235   public IDocumentNodeItem loadAsNodeItem(Format format, URI uri) throws IOException {
236     URI resourceUri = resolve(uri);
237     URL resource = resourceUri.toURL();
238 
239     try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
240       return loadAsNodeItem(format, is, resourceUri);
241     }
242   }
243 
244   @Override
245   public IDocumentNodeItem loadAsNodeItem(Format format, InputStream is, URI documentUri)
246       throws IOException {
247     try (ModelDetector.Result modelMatch = detectModel(is, format)) {
248 
249       IDeserializer<?> deserializer = getDeserializer(
250           modelMatch.getBoundClass(),
251           format,
252           getConfiguration());
253       try (InputStream modelStream = modelMatch.getDataStream()) {
254         return (IDocumentNodeItem) deserializer.deserializeToNodeItem(modelStream, documentUri);
255       }
256     }
257   }
258 
259   @NonNull
260   private <CLASS extends IBoundObject> IDeserializer<CLASS> getDeserializer(
261       @NonNull Class<CLASS> clazz,
262       @NonNull Format format,
263       @NonNull IConfiguration<DeserializationFeature<?>> config) {
264     IDeserializer<CLASS> retval = getBindingContext().newDeserializer(format, clazz);
265     retval.applyConfiguration(config);
266     return retval;
267   }
268 }