DefaultBoundLoader.java
/*
* Portions of this software was developed by employees of the National Institute
* of Standards and Technology (NIST), an agency of the Federal Government and is
* being made available as a public service. Pursuant to title 17 United States
* Code Section 105, works of NIST employees are not subject to copyright
* protection in the United States. This software may be subject to foreign
* copyright. Permission in the United States and in foreign countries, to the
* extent that NIST may hold copyright, to use, copy, modify, create derivative
* works, and distribute this software and its documentation without fee is hereby
* granted on a non-exclusive basis, provided that this notice and disclaimer
* of warranty appears in all copies.
*
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
*/
package gov.nist.secauto.metaschema.databind.io;
import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
import gov.nist.secauto.metaschema.core.resource.AbstractResourceLoader;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.IBindingContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import edu.umd.cs.findbugs.annotations.NonNull;
/**
* A default implementation of an {@link IBoundLoader}.
*/
public class DefaultBoundLoader
extends AbstractResourceLoader
implements IBoundLoader {
public static final int LOOK_AHEAD_BYTES = 32_768;
// @NonNull
// private static final JsonFactory JSON_FACTORY = new JsonFactory();
// @NonNull
// private static final XmlFactory XML_FACTORY = new XmlFactory();
// @NonNull
// private static final YAMLFactory YAML_FACTORY = new YAMLFactory();
private FormatDetector formatDetector;
private ModelDetector modelDetector;
@NonNull
private final IBindingContext bindingContext;
@NonNull
private final IMutableConfiguration<DeserializationFeature<?>> configuration;
/**
* Construct a new OSCAL loader instance, using the provided
* {@link IBindingContext}.
*
* @param bindingContext
* the Module binding context to use to load Java types
*/
public DefaultBoundLoader(@NonNull IBindingContext bindingContext) {
this.bindingContext = bindingContext;
this.configuration = new DefaultConfiguration<>();
}
@NonNull
private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() {
return configuration;
}
@Override
public boolean isFeatureEnabled(DeserializationFeature<?> feature) {
return getConfiguration().isFeatureEnabled(feature);
}
@Override
public Map<DeserializationFeature<?>, Object> getFeatureValues() {
return getConfiguration().getFeatureValues();
}
@Override
public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) {
getConfiguration().applyConfiguration(other);
resetDetector();
return this;
}
@SuppressWarnings("PMD.NullAssignment")
private void resetDetector() {
// reset the detector
formatDetector = null;
}
@Override
public IBoundLoader set(DeserializationFeature<?> feature, Object value) {
getConfiguration().set(feature, value);
resetDetector();
return this;
}
@Override
public IBindingContext getBindingContext() {
return bindingContext;
}
@Override
public FormatDetector.Result detectFormat(@NonNull URI uri) throws IOException {
URI resourceUri = resolve(uri);
URL resource = resourceUri.toURL();
try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
return detectFormat(is);
}
}
@Override
public FormatDetector.Result detectFormat(@NonNull InputStream is) throws IOException {
return getFormatDetector().detect(is);
}
@NonNull
private FormatDetector getFormatDetector() {
if (formatDetector == null) {
formatDetector = new FormatDetector(getConfiguration());
}
assert formatDetector != null;
return formatDetector;
}
@NonNull
private ModelDetector getModelDetector() {
if (modelDetector == null) {
modelDetector = new ModelDetector(
getBindingContext(),
getConfiguration());
}
assert modelDetector != null;
return modelDetector;
}
//
// @NonNull
// private static BufferedInputStream toBufferedInputStream(@NonNull InputStream
// is) {
// return toBufferedInputStream(is, LOOK_AHEAD_BYTES);
// }
//
// @NonNull
// private static BufferedInputStream toBufferedInputStream(@NonNull InputStream
// is, int lookaheadSize) {
// BufferedInputStream bis = new BufferedInputStream(is, lookaheadSize); //
// NOPMD - stream not owned
// bis.mark(lookaheadSize);
// return bis;
// }
@Override
public <CLASS> CLASS load(@NonNull URI uri) throws IOException {
URI resourceUri = resolve(uri);
URL resource = resourceUri.toURL();
try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
return load(is, uri);
}
}
@Override
@NonNull
public <CLASS> CLASS load(@NonNull InputStream is, @NonNull URI documentUri) throws IOException {
// TODO: avoid node item
return INodeItem.toValue(loadAsNodeItem(is, documentUri));
}
@Override
public <CLASS> CLASS load(Class<CLASS> clazz, URI uri) throws IOException {
URI resourceUri = resolve(uri);
URL resource = resourceUri.toURL();
try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
return load(clazz, is, resourceUri);
}
}
@Override
public <CLASS> CLASS load(Class<CLASS> clazz, InputStream is, URI documentUri) throws IOException {
// we cannot close this stream, since it will cause the underlying stream to be
// closed
FormatDetector.Result match = getFormatDetector().detect(is);
Format format = match.getFormat();
try (InputStream remainingStream = match.getDataStream()) {
// is autoclosing ok?
return load(clazz, format, remainingStream, documentUri);
}
}
@NonNull
private <CLASS> CLASS load(
@NonNull Class<CLASS> clazz,
@NonNull Format format,
@NonNull InputStream is,
@NonNull URI documentUri) throws IOException {
IDeserializer<CLASS> deserializer = getDeserializer(clazz, format, getConfiguration());
return deserializer.deserialize(is, documentUri);
}
@Override
public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
URI resourceUri = resolve(uri);
URL resource = resourceUri.toURL();
try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
return loadAsNodeItem(is, resourceUri);
}
}
@Override
public IDocumentNodeItem loadAsNodeItem(InputStream is, URI documentUri) throws IOException {
FormatDetector.Result formatMatch = getFormatDetector().detect(is);
Format format = formatMatch.getFormat();
try (InputStream formatStream = formatMatch.getDataStream()) {
return loadAsNodeItem(format, formatStream, documentUri);
}
}
@Override
public IDocumentNodeItem loadAsNodeItem(Format format, URI uri) throws IOException {
URI resourceUri = resolve(uri);
URL resource = resourceUri.toURL();
try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
return loadAsNodeItem(format, is, resourceUri);
}
}
@Override
public IDocumentNodeItem loadAsNodeItem(Format format, InputStream is, URI documentUri) throws IOException {
ModelDetector.Result modelMatch = getModelDetector().detect(is, format);
IDeserializer<?> deserializer = getDeserializer(
modelMatch.getBoundClass(),
format,
getConfiguration());
try (InputStream modelStream = modelMatch.getDataStream()) {
return (IDocumentNodeItem) deserializer.deserializeToNodeItem(modelStream, documentUri);
}
}
@NonNull
private <CLASS> IDeserializer<CLASS> getDeserializer(
@NonNull Class<CLASS> clazz,
@NonNull Format format,
@NonNull IConfiguration<DeserializationFeature<?>> config) {
IDeserializer<CLASS> retval = getBindingContext().newDeserializer(format, clazz);
retval.applyConfiguration(config);
return retval;
}
}