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