001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.databind.io;
028
029import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
030import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
031import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
032import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
033import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
034import gov.nist.secauto.metaschema.core.resource.AbstractResourceLoader;
035import gov.nist.secauto.metaschema.core.util.ObjectUtils;
036import gov.nist.secauto.metaschema.databind.IBindingContext;
037
038import java.io.IOException;
039import java.io.InputStream;
040import java.net.URI;
041import java.net.URL;
042import java.util.Map;
043
044import edu.umd.cs.findbugs.annotations.NonNull;
045
046/**
047 * A default implementation of an {@link IBoundLoader}.
048 */
049public class DefaultBoundLoader
050    extends AbstractResourceLoader
051    implements IBoundLoader {
052  public static final int LOOK_AHEAD_BYTES = 32_768;
053  // @NonNull
054  // private static final JsonFactory JSON_FACTORY = new JsonFactory();
055  // @NonNull
056  // private static final XmlFactory XML_FACTORY = new XmlFactory();
057  // @NonNull
058  // private static final YAMLFactory YAML_FACTORY = new YAMLFactory();
059
060  private FormatDetector formatDetector;
061
062  private ModelDetector modelDetector;
063
064  @NonNull
065  private final IBindingContext bindingContext;
066  @NonNull
067  private final IMutableConfiguration<DeserializationFeature<?>> configuration;
068
069  /**
070   * Construct a new OSCAL loader instance, using the provided
071   * {@link IBindingContext}.
072   *
073   * @param bindingContext
074   *          the Module binding context to use to load Java types
075   */
076  public DefaultBoundLoader(@NonNull IBindingContext bindingContext) {
077    this.bindingContext = bindingContext;
078    this.configuration = new DefaultConfiguration<>();
079  }
080
081  @NonNull
082  private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() {
083    return configuration;
084  }
085
086  @Override
087  public boolean isFeatureEnabled(DeserializationFeature<?> feature) {
088    return getConfiguration().isFeatureEnabled(feature);
089  }
090
091  @Override
092  public Map<DeserializationFeature<?>, Object> getFeatureValues() {
093    return getConfiguration().getFeatureValues();
094  }
095
096  @Override
097  public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) {
098    getConfiguration().applyConfiguration(other);
099    resetDetector();
100    return this;
101  }
102
103  @SuppressWarnings("PMD.NullAssignment")
104  private void resetDetector() {
105    // reset the detector
106    formatDetector = null;
107  }
108
109  @Override
110  public IBoundLoader set(DeserializationFeature<?> feature, Object value) {
111    getConfiguration().set(feature, value);
112    resetDetector();
113    return this;
114  }
115
116  @Override
117  public IBindingContext getBindingContext() {
118    return bindingContext;
119  }
120
121  @Override
122  public FormatDetector.Result detectFormat(@NonNull URI uri) throws IOException {
123    URI resourceUri = resolve(uri);
124    URL resource = resourceUri.toURL();
125
126    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
127      return detectFormat(is);
128    }
129  }
130
131  @Override
132  public FormatDetector.Result detectFormat(@NonNull InputStream is) throws IOException {
133    return getFormatDetector().detect(is);
134  }
135
136  @NonNull
137  private FormatDetector getFormatDetector() {
138    if (formatDetector == null) {
139      formatDetector = new FormatDetector(getConfiguration());
140    }
141    assert formatDetector != null;
142    return formatDetector;
143  }
144
145  @NonNull
146  private ModelDetector getModelDetector() {
147    if (modelDetector == null) {
148      modelDetector = new ModelDetector(
149          getBindingContext(),
150          getConfiguration());
151    }
152    assert modelDetector != null;
153    return modelDetector;
154  }
155
156  //
157  // @NonNull
158  // private static BufferedInputStream toBufferedInputStream(@NonNull InputStream
159  // is) {
160  // return toBufferedInputStream(is, LOOK_AHEAD_BYTES);
161  // }
162  //
163  // @NonNull
164  // private static BufferedInputStream toBufferedInputStream(@NonNull InputStream
165  // is, int lookaheadSize) {
166  // BufferedInputStream bis = new BufferedInputStream(is, lookaheadSize); //
167  // NOPMD - stream not owned
168  // bis.mark(lookaheadSize);
169  // return bis;
170  // }
171
172  @Override
173  public <CLASS> CLASS load(@NonNull URI uri) throws IOException {
174    URI resourceUri = resolve(uri);
175    URL resource = resourceUri.toURL();
176
177    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
178      return load(is, uri);
179    }
180  }
181
182  @Override
183  @NonNull
184  public <CLASS> CLASS load(@NonNull InputStream is, @NonNull URI documentUri) throws IOException {
185    // TODO: avoid node item
186    return INodeItem.toValue(loadAsNodeItem(is, documentUri));
187  }
188
189  @Override
190  public <CLASS> CLASS load(Class<CLASS> clazz, URI uri) throws IOException {
191    URI resourceUri = resolve(uri);
192    URL resource = resourceUri.toURL();
193
194    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
195      return load(clazz, is, resourceUri);
196    }
197  }
198
199  @Override
200  public <CLASS> CLASS load(Class<CLASS> clazz, InputStream is, URI documentUri) throws IOException {
201    // we cannot close this stream, since it will cause the underlying stream to be
202    // closed
203    FormatDetector.Result match = getFormatDetector().detect(is);
204    Format format = match.getFormat();
205
206    try (InputStream remainingStream = match.getDataStream()) {
207      // is autoclosing ok?
208      return load(clazz, format, remainingStream, documentUri);
209    }
210  }
211
212  @NonNull
213  private <CLASS> CLASS load(
214      @NonNull Class<CLASS> clazz,
215      @NonNull Format format,
216      @NonNull InputStream is,
217      @NonNull URI documentUri) throws IOException {
218
219    IDeserializer<CLASS> deserializer = getDeserializer(clazz, format, getConfiguration());
220    return deserializer.deserialize(is, documentUri);
221  }
222
223  @Override
224  public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
225    URI resourceUri = resolve(uri);
226    URL resource = resourceUri.toURL();
227
228    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
229      return loadAsNodeItem(is, resourceUri);
230    }
231  }
232
233  @Override
234  public IDocumentNodeItem loadAsNodeItem(InputStream is, URI documentUri) throws IOException {
235    FormatDetector.Result formatMatch = getFormatDetector().detect(is);
236    Format format = formatMatch.getFormat();
237
238    try (InputStream formatStream = formatMatch.getDataStream()) {
239      return loadAsNodeItem(format, formatStream, documentUri);
240    }
241  }
242
243  @Override
244  public IDocumentNodeItem loadAsNodeItem(Format format, URI uri) throws IOException {
245    URI resourceUri = resolve(uri);
246    URL resource = resourceUri.toURL();
247
248    try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
249      return loadAsNodeItem(format, is, resourceUri);
250    }
251  }
252
253  @Override
254  public IDocumentNodeItem loadAsNodeItem(Format format, InputStream is, URI documentUri) throws IOException {
255    ModelDetector.Result modelMatch = getModelDetector().detect(is, format);
256
257    IDeserializer<?> deserializer = getDeserializer(
258        modelMatch.getBoundClass(),
259        format,
260        getConfiguration());
261    try (InputStream modelStream = modelMatch.getDataStream()) {
262      return (IDocumentNodeItem) deserializer.deserializeToNodeItem(modelStream, documentUri);
263    }
264  }
265
266  @NonNull
267  private <CLASS> IDeserializer<CLASS> getDeserializer(
268      @NonNull Class<CLASS> clazz,
269      @NonNull Format format,
270      @NonNull IConfiguration<DeserializationFeature<?>> config) {
271    IDeserializer<CLASS> retval = getBindingContext().newDeserializer(format, clazz);
272    retval.applyConfiguration(config);
273    return retval;
274  }
275}