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}