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>"&lt;NULL&gt;"</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 &le; 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}