001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io;
007
008import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
009import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
010import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
011import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
012import gov.nist.secauto.metaschema.core.model.AbstractResourceResolver;
013import gov.nist.secauto.metaschema.core.model.IBoundObject;
014import gov.nist.secauto.metaschema.core.util.ObjectUtils;
015import gov.nist.secauto.metaschema.databind.IBindingContext;
016import gov.nist.secauto.metaschema.databind.io.ModelDetector.Result;
017
018import org.eclipse.jdt.annotation.NotOwning;
019import org.eclipse.jdt.annotation.Owning;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.URI;
024import java.net.URL;
025import java.util.Map;
026
027import edu.umd.cs.findbugs.annotations.NonNull;
028
029/**
030 * A default implementation of an {@link IBoundLoader}.
031 */
032@SuppressWarnings("PMD.CouplingBetweenObjects")
033public class DefaultBoundLoader
034    extends AbstractResourceResolver
035    implements IBoundLoader {
036  /**
037   * The number of bytes to read ahead when determining the source format.
038   */
039  public static final int LOOK_AHEAD_BYTES = 32_768;
040  // @NonNull
041  // private static final JsonFactory JSON_FACTORY = new JsonFactory();
042  // @NonNull
043  // private static final XmlFactory XML_FACTORY = new XmlFactory();
044  // @NonNull
045  // private static final YAMLFactory YAML_FACTORY = new YAMLFactory();
046
047  private FormatDetector formatDetector;
048
049  private ModelDetector modelDetector;
050
051  @NonNull
052  private final IBindingContext bindingContext;
053  @NonNull
054  private final IMutableConfiguration<DeserializationFeature<?>> configuration;
055
056  /**
057   * Construct a new loader instance, using the provided {@link IBindingContext}.
058   *
059   * @param bindingContext
060   *          the Module binding context to use to load Java types
061   */
062  public DefaultBoundLoader(@NonNull IBindingContext bindingContext) {
063    this.bindingContext = bindingContext;
064    this.configuration = new DefaultConfiguration<>();
065  }
066
067  @NonNull
068  private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() {
069    return configuration;
070  }
071
072  @Override
073  public boolean isFeatureEnabled(DeserializationFeature<?> feature) {
074    return getConfiguration().isFeatureEnabled(feature);
075  }
076
077  @Override
078  public Map<DeserializationFeature<?>, Object> getFeatureValues() {
079    return getConfiguration().getFeatureValues();
080  }
081
082  @Override
083  public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) {
084    getConfiguration().applyConfiguration(other);
085    resetDetector();
086    return this;
087  }
088
089  @SuppressWarnings("PMD.NullAssignment")
090  private void resetDetector() {
091    // reset the detector
092    formatDetector = null;
093  }
094
095  @Override
096  public IBoundLoader set(DeserializationFeature<?> feature, Object value) {
097    getConfiguration().set(feature, value);
098    resetDetector();
099    return this;
100  }
101
102  @Override
103  public IBindingContext getBindingContext() {
104    return bindingContext;
105  }
106
107  @Override
108  public Format detectFormat(@NonNull URI uri) throws IOException {
109    URI resourceUri = resolve(uri);
110    URL resource = resourceUri.toURL();
111
112    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
113      return detectFormat(is, uri).getFormat();
114    }
115  }
116
117  @Override
118  public FormatDetector.Result detectFormat(InputStream is, URI resource) throws IOException {
119    return getFormatDetector().detect(is);
120  }
121
122  @NonNull
123  private FormatDetector getFormatDetector() {
124    if (formatDetector == null) {
125      formatDetector = new FormatDetector(getConfiguration());
126    }
127    assert formatDetector != null;
128    return formatDetector;
129  }
130
131  @NonNull
132  private ModelDetector getModelDetector() {
133    if (modelDetector == null) {
134      modelDetector = new ModelDetector(
135          getBindingContext(),
136          getConfiguration());
137    }
138    assert modelDetector != null;
139    return modelDetector;
140  }
141
142  @Override
143  @Owning
144  public Result detectModel(@NotOwning InputStream is, URI resource, Format format) throws IOException {
145    return getModelDetector().detect(is, resource, format);
146  }
147
148  @Override
149  public <CLASS extends IBoundObject> CLASS load(@NonNull URI uri) throws IOException {
150    URI resourceUri = resolve(uri);
151    URL resource = resourceUri.toURL();
152
153    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
154      return load(is, uri);
155    }
156  }
157
158  @SuppressWarnings("unchecked")
159  @Override
160  @NonNull
161  public <CLASS extends IBoundObject> CLASS load(
162      @NotOwning @NonNull InputStream is,
163      @NonNull URI resource)
164      throws IOException {
165    FormatDetector.Result formatMatch = getFormatDetector().detect(is);
166    Format format = formatMatch.getFormat();
167
168    try (InputStream formatStream = formatMatch.getDataStream()) {
169      try (ModelDetector.Result modelMatch = detectModel(formatStream, resource, format)) {
170
171        IDeserializer<?> deserializer = getDeserializer(
172            modelMatch.getBoundClass(),
173            format,
174            getConfiguration());
175        try (InputStream modelStream = modelMatch.getDataStream()) {
176          return (CLASS) deserializer.deserialize(modelStream, resource);
177        }
178      }
179    }
180  }
181
182  @Override
183  public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, URI uri) throws IOException {
184    URI resourceUri = resolve(uri);
185    URL resource = resourceUri.toURL();
186
187    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
188      return load(clazz, is, resourceUri);
189    }
190  }
191
192  @Override
193  public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, InputStream is, URI documentUri)
194      throws IOException {
195    // we cannot close this stream, since it will cause the underlying stream to be
196    // closed
197    FormatDetector.Result match = getFormatDetector().detect(is);
198    Format format = match.getFormat();
199
200    try (InputStream remainingStream = match.getDataStream()) {
201      // is autoclosing ok?
202      return load(clazz, format, remainingStream, documentUri);
203    }
204  }
205
206  @Override
207  @NonNull
208  public <CLASS extends IBoundObject> CLASS load(
209      @NonNull Class<CLASS> clazz,
210      @NonNull Format format,
211      @NonNull InputStream is,
212      @NonNull URI documentUri) throws IOException {
213
214    IDeserializer<CLASS> deserializer = getDeserializer(clazz, format, getConfiguration());
215    return deserializer.deserialize(is, documentUri);
216  }
217
218  @Override
219  public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
220    URI resourceUri = resolve(uri);
221    URL resource = resourceUri.toURL();
222
223    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
224      return loadAsNodeItem(is, resourceUri);
225    }
226  }
227
228  @NonNull
229  private IDocumentNodeItem loadAsNodeItem(@NonNull InputStream is, @NonNull URI documentUri) throws IOException {
230    FormatDetector.Result formatMatch = getFormatDetector().detect(is);
231    Format format = formatMatch.getFormat();
232
233    try (InputStream formatStream = formatMatch.getDataStream()) {
234      return loadAsNodeItem(format, formatStream, documentUri);
235    }
236  }
237
238  @Override
239  public IDocumentNodeItem loadAsNodeItem(Format format, URI uri) throws IOException {
240    URI resourceUri = resolve(uri);
241    URL resource = resourceUri.toURL();
242
243    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
244      return loadAsNodeItem(format, is, resourceUri);
245    }
246  }
247
248  @Override
249  public IDocumentNodeItem loadAsNodeItem(Format format, InputStream is, URI resource)
250      throws IOException {
251    try (ModelDetector.Result modelMatch = detectModel(is, resource, format)) {
252
253      IDeserializer<?> deserializer = getDeserializer(
254          modelMatch.getBoundClass(),
255          format,
256          getConfiguration());
257      try (InputStream modelStream = modelMatch.getDataStream()) {
258        return (IDocumentNodeItem) deserializer.deserializeToNodeItem(modelStream, resource);
259      }
260    }
261  }
262
263  @NonNull
264  private <CLASS extends IBoundObject> IDeserializer<CLASS> getDeserializer(
265      @NonNull Class<CLASS> clazz,
266      @NonNull Format format,
267      @NonNull IConfiguration<DeserializationFeature<?>> config) {
268    IDeserializer<CLASS> retval = getBindingContext().newDeserializer(format, clazz);
269    retval.applyConfiguration(config);
270    return retval;
271  }
272}