001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.bson; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.SystemUtils.*; 021 022import java.io.*; 023import java.math.*; 024import java.time.*; 025import java.util.*; 026import java.util.concurrent.*; 027 028import org.apache.juneau.commons.bean.*; 029import org.apache.juneau.commons.collections.*; 030import org.apache.juneau.marshall.*; 031import org.apache.juneau.marshall.parser.*; 032import org.apache.juneau.marshall.stream.*; 033 034/** 035 * Parses BSON (Binary JSON) into POJO models. 036 * 037 * <h5 class='section'>Media types:</h5> 038 * 039 * Handles <c>Content-Type</c> types: <bc>application/bson</bc> 040 * 041 * <h5 class='topic'>Description</h5> 042 * <p> 043 * BSON-to-Java type mapping: 044 * <ul class='spaced-list'> 045 * <li>BSON document (0x03) → bean or {@link Map} 046 * <li>BSON array (0x04) → {@link Collection} or Java array (numeric string keys ignored) 047 * <li>BSON string (0x02) → {@link String}; coerced to types parseable from string 048 * <li>BSON boolean (0x08) → {@link Boolean} 049 * <li>BSON int32 (0x10) → {@code int}; coerced to {@code long}, {@code short}, {@code byte} as needed 050 * <li>BSON int64 (0x12) → {@code long}; coerced to {@code int} if target is {@code int} 051 * <li>BSON double (0x01) → {@code double}; coerced to {@code float} 052 * <li>BSON datetime (0x09) → {@link Date}, {@link Calendar}, {@link Instant}, or 053 * {@code long} (millis) 054 * <li>BSON binary (0x05) → {@code byte[]} 055 * <li>BSON decimal128 (0x13) → {@link BigDecimal} 056 * <li>BSON null (0x0A) → {@code null} 057 * <li>BSON ObjectId (0x07) → 24-character hex {@link String} 058 * <li>Deprecated/unsupported types → silently skipped 059 * </ul> 060 * 061 * <h5 class='section'>Notes:</h5><ul> 062 * <li class='note'>This class is thread safe and reusable. 063 * </ul> 064 * 065 * <h5 class='section'>Limitations:</h5><ul> 066 * <li>BSON requires a top-level document. Standalone scalars are wrapped as <c>{value: x}</c>. 067 * <li>Map keys must be strings. Non-string keys use <c>toString()</c>. Null keys use a configurable placeholder. 068 * <li>ObjectId (0x07) is read-only; parsed as 24-character hex strings. 069 * <li>Deprecated types (Undefined, DBPointer, Symbol, JS with Scope) are silently skipped. 070 * <li>MongoDB-internal types (JavaScript, Timestamp, MinKey, MaxKey) are not supported. 071 * <li>Decimal128 (0x13) NaN and ±Infinity have no {@link BigDecimal} form; they are normalized to <jk>null</jk>. 072 * <li>Wire-declared lengths (strings, binary, document/array size prefixes) are bounds-checked against a 073 * configurable maximum (default 16 MiB, see {@link Builder#maxLength(int)}); malformed/adversarial lengths 074 * fail with a clean {@link ParseException} instead of an unchecked error. 075 * <li>BSON is binary; <c>SpacedHex</c> and <c>Base64</c> subclasses accept text-encoded input. 076 * </ul> 077 * 078 * <h5 class='section'>See Also:</h5><ul> 079 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Bson">BSON</a> 080 * </ul> 081 */ 082@SuppressWarnings({ 083 "java:S110", // Inheritance depth acceptable for this class hierarchy 084 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 085 "java:S9149", // Per-format static factories intentionally shadow the parent's. 086 "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design. 087}) 088public class BsonParser extends InputStreamParser implements BsonMetaProvider, RecordReadable, ArrayRecordReadable { 089 090 private static final String DEFAULT_NULL_KEY = "<NULL>"; 091 private static final int DEFAULT_MAX_LENGTH = BsonInputStream.DEFAULT_MAX_LENGTH; 092 093 /** Default parser, string input encoded as BASE64. */ 094 public static class Base64 extends BsonParser { 095 096 /** 097 * Constructor. 098 * 099 * @param builder The builder for this object. 100 */ 101 public Base64(Builder builder) { 102 super(builder.binaryFormat(BinaryFormat.BASE64)); 103 } 104 } 105 106 /** 107 * Builder class. 108 */ 109 public static class Builder extends InputStreamParser.Builder<Builder> { 110 111 private static final Cache<HashKey,BsonParser> CACHE = Cache.of(HashKey.class, BsonParser.class).build(); 112 113 private String nullKeyString = DEFAULT_NULL_KEY; 114 private int maxLength = DEFAULT_MAX_LENGTH; 115 116 /** 117 * Constructor, default settings. 118 */ 119 protected Builder() { 120 consumes("application/bson"); 121 nullKeyString = env("BsonParser.nullKeyString", DEFAULT_NULL_KEY); 122 maxLength = env("BsonParser.maxLength", DEFAULT_MAX_LENGTH); 123 } 124 125 /** 126 * Copy constructor. 127 * 128 * @param copyFrom The builder to copy from. 129 * <br>Cannot be <jk>null</jk>. 130 */ 131 protected Builder(Builder copyFrom) { 132 super(reqnn("copyFrom", copyFrom)); 133 nullKeyString = copyFrom.nullKeyString; 134 maxLength = copyFrom.maxLength; 135 } 136 137 /** 138 * Copy constructor. 139 * 140 * @param copyFrom The bean to copy from. 141 * <br>Cannot be <jk>null</jk>. 142 */ 143 protected Builder(BsonParser copyFrom) { 144 super(reqnn("copyFrom", copyFrom)); 145 nullKeyString = copyFrom.nullKeyString; 146 maxLength = copyFrom.maxLength; 147 } 148 149 @Override /* InputStreamParser.Builder<?> */ 150 public Builder binaryFormat(BinaryFormat value) { 151 super.binaryFormat(value); 152 return this; 153 } 154 155 /** 156 * The string used to represent <jk>null</jk> map keys in BSON documents. 157 * 158 * <p> 159 * BSON document field names cannot be <jk>null</jk>. When parsing a field with this name, 160 * it is stored with <jk>null</jk> as the key in the target map. 161 * 162 * @param value The placeholder string for null keys. Default is <js>"<NULL>"</js>. 163 * @return This object. 164 */ 165 public Builder nullKeyString(String value) { 166 nullKeyString = value == null ? DEFAULT_NULL_KEY : value; 167 return this; 168 } 169 170 /** 171 * The maximum allowed wire-declared length (in bytes) for BSON strings, binary payloads, and document/array 172 * size prefixes. 173 * 174 * <p> 175 * Guards against malformed or adversarial input where a small payload declares a huge or negative length, 176 * which would otherwise trigger {@link OutOfMemoryError} or {@link NegativeArraySizeException}. Malformed 177 * lengths are reported as a clean parse error instead. 178 * 179 * @param value The maximum length in bytes. Default is 16 MiB (the conventional BSON document-size limit). 180 * Values ≤ 0 disable the cap (only the negative-length check remains). 181 * @return This object. 182 */ 183 public Builder maxLength(int value) { 184 maxLength = value; 185 return this; 186 } 187 188 @Override /* Parser.Builder<?> */ 189 public Builder trimStrings() { 190 super.trimStrings(); 191 return this; 192 } 193 194 @Override /* Parser.Builder<?> */ 195 public Builder trimStrings(boolean value) { 196 super.trimStrings(value); 197 return this; 198 } 199 200 @Override /* Context.Builder<?> */ 201 public BsonParser build() { 202 return cache(CACHE).build(BsonParser.class); 203 } 204 205 @Override /* Context.Builder<?> */ 206 public HashKey hashKey() { 207 return HashKey.of(super.hashKey(), nullKeyString, maxLength); 208 } 209 210 @Override /* Context.Builder<?> */ 211 public Builder copy() { 212 return new Builder(this); 213 } 214 } 215 216 /** Default parser, string input encoded as spaced-hex. */ 217 public static class SpacedHex extends BsonParser { 218 219 /** 220 * Constructor. 221 * 222 * @param builder The builder for this object. 223 */ 224 public SpacedHex(Builder builder) { 225 super(builder.binaryFormat(BinaryFormat.SPACED_HEX)); 226 } 227 } 228 229 /** Default parser, all default settings. */ 230 public static final BsonParser DEFAULT = new BsonParser(create()); 231 /** Default parser, spaced-hex string input. */ 232 public static final BsonParser DEFAULT_SPACED_HEX = new SpacedHex(create()); 233 /** Default parser, BASE64 string input. */ 234 public static final BsonParser DEFAULT_BASE64 = new Base64(create()); 235 236 /** 237 * Creates a new builder for this object. 238 * 239 * @return A new builder. 240 */ 241 public static Builder create() { 242 return new Builder(); 243 } 244 245 private final Map<ClassMeta<?>,BsonClassMeta> bsonClassMetas = new ConcurrentHashMap<>(); 246 private final Map<BeanPropertyMeta,BsonBeanPropertyMeta> bsonBeanPropertyMetas = new ConcurrentHashMap<>(); 247 private final String nullKeyString; 248 private final int maxLength; 249 250 /** 251 * Constructor. 252 * 253 * @param builder The builder for this object. 254 */ 255 public BsonParser(Builder builder) { 256 super(builder); 257 nullKeyString = builder.nullKeyString; 258 maxLength = builder.maxLength; 259 } 260 261 @Override /* Context */ 262 public Builder copy() { 263 return new Builder(this); 264 } 265 266 @Override /* Context */ 267 public BsonParserSession.Builder createSession() { 268 return BsonParserSession.create(this); 269 } 270 271 @Override /* BsonMetaProvider */ 272 public BsonBeanPropertyMeta getBsonBeanPropertyMeta(BeanPropertyMeta bpm) { 273 if (bpm == null) 274 return BsonBeanPropertyMeta.DEFAULT; 275 return bsonBeanPropertyMetas.computeIfAbsent(bpm, k -> new BsonBeanPropertyMeta(k.getDelegateFor(), this)); 276 } 277 278 @Override /* BsonMetaProvider */ 279 public BsonClassMeta getBsonClassMeta(ClassMeta<?> cm) { 280 return bsonClassMetas.computeIfAbsent(cm, k -> new BsonClassMeta(k, this)); 281 } 282 283 @Override /* Context */ 284 public BsonParserSession getSession() { 285 return createSession().build(); 286 } 287 288 /** 289 * Returns the string used to represent <jk>null</jk> map keys in BSON documents. 290 * 291 * @return The null key placeholder string. 292 */ 293 public String getNullKeyString() { 294 return nullKeyString; 295 } 296 297 /** 298 * Returns the maximum allowed wire-declared length (in bytes) for strings, binary payloads, and document/array 299 * size prefixes. 300 * 301 * @return The maximum length in bytes. 302 */ 303 public int getMaxLength() { 304 return maxLength; 305 } 306 307 /** 308 * Convenience delegator for the whole-value {@link RecordReader} using <b>default session 309 * arguments</b>. The real implementation lives on {@link BsonParserSession#readRecords(Object)}. 310 * 311 * @param input The input. 312 * @return A new {@link RecordReader} cursor. 313 * @throws IOException If a problem occurred opening the underlying input. 314 */ 315 @Override /* RecordReadable */ 316 public RecordReader readRecords(Object input) throws IOException { 317 return getSession().readRecords(input); 318 } 319 320 /** 321 * Convenience delegator for the buffered array-element {@link RecordReader} using <b>default 322 * session arguments</b>. The real implementation lives on 323 * {@link BsonParserSession#readArrayRecords(Object)}. 324 * 325 * @param input The input. 326 * @return A buffered {@link RecordReader}. 327 * @throws IOException If a problem occurred reading the input. 328 */ 329 @Override /* ArrayRecordReadable */ 330 public RecordReader readArrayRecords(Object input) throws IOException { 331 return getSession().readArrayRecords(input); 332 } 333 334 /** 335 * The BSON record cursor is buffered/{@link RecordAdapter}-backed, not O(1) streaming. 336 * 337 * @return Always <jk>false</jk>. 338 */ 339 @Override /* RecordReadable */ 340 public boolean isRecordStreaming() { return false; } 341 342 /** 343 * The BSON array-record cursor is buffered/{@link RecordAdapter}-backed, not O(1) streaming. 344 * 345 * @return Always <jk>false</jk>. 346 */ 347 @Override /* ArrayRecordReadable */ 348 public boolean isArrayRecordStreaming() { return false; } 349}