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}