All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.immutables.common.repository.internal.BsonEncoding Maven / Gradle / Ivy

/*
    Copyright 2013-2014 Immutables Authors and Contributors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package org.immutables.common.repository.internal;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import com.google.common.primitives.UnsignedBytes;
import com.mongodb.DBCallback;
import com.mongodb.DBCollection;
import com.mongodb.DBDecoder;
import com.mongodb.DBDecoderFactory;
import com.mongodb.DBEncoder;
import com.mongodb.DBObject;
import com.mongodb.DefaultDBEncoder;
import com.mongodb.LazyDBCallback;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.BsonGenerator;
import de.undercouch.bson4jackson.BsonParser;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.bson.BSONCallback;
import org.bson.BSONObject;
import org.bson.BasicBSONDecoder;
import org.bson.io.BasicOutputBuffer;
import org.bson.io.OutputBuffer;
import org.immutables.common.marshal.Marshaler;
import org.immutables.common.repository.internal.RepositorySupport.UnmarshalableWrapper;

/**
 * MongoDB driver specific encoding and jumping hoops.
 */
@SuppressWarnings("resource")
public final class BsonEncoding {
  private static final BsonFactory BSON_FACTORY = new BsonFactory()
      .enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH);

  private static final JsonFactory JSON_FACTORY = new JsonFactory()
      .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
      .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);

  /**
   * This field name will cause an MongoDB confuse if not unwrapped correctly so it's may be a good
   * choice.
   */
  private static final String PREENCODED_VALUE_WRAPPER_FIELD_NAME = "$";

  private BsonEncoding() {}

  /**
   * Although it may seem that re-parsing is bizarre, but it is one [of not so many] ways to do
   * proper marshaling. This kind of inefficiency will only hit query constraints that have many
   * object with custom marshaling, which considered to be a rare case.
   * @param marshalableValue the value in a marshalable wrapper
   * @return object converted to MongoDB driver's {@link BSONObject}.
   */
  public static Object unwrapBsonable(RepositorySupport.MarshalableWrapper marshalableValue) {
    try {
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      BsonGenerator generator = BSON_FACTORY.createGenerator(outputStream);
      generator.writeStartObject();
      generator.writeFieldName(PREENCODED_VALUE_WRAPPER_FIELD_NAME);
      marshalableValue.marshalWrapped(generator);
      generator.writeEndObject();
      generator.close();
      BSONObject object = new BasicBSONDecoder().readObject(outputStream.toByteArray());
      return object.get(PREENCODED_VALUE_WRAPPER_FIELD_NAME);
    } catch (IOException ex) {
      throw Throwables.propagate(ex);
    }
  }

  public static DBObject unwrapJsonable(UnmarshalableWrapper value) {
    try {
      JsonParser parser = JSON_FACTORY.createParser(value.toString());
      parser.nextToken();
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      BsonGenerator generator = BSON_FACTORY.createGenerator(outputStream);
      generator.copyCurrentStructure(parser);
      generator.close();
      parser.close();
      byte[] data = outputStream.toByteArray();
      return (DBObject) new LazyDBCallback(null).createObject(data, 0);
    } catch (IOException ex) {
      throw Throwables.propagate(ex);
    }
  }

  public static  T unmarshalDbObject(DBObject dbObject, Marshaler marshaler) throws IOException {
    BasicOutputBuffer buffer = new BasicOutputBuffer();
    encoder().writeObject(buffer, dbObject);
    JsonParser parser = BSON_FACTORY.createParser(buffer.toByteArray());
    parser.nextToken();
    T instance = marshaler.unmarshalInstance(parser);
    parser.close();
    return instance;
  }

  private static class CountingOutputBufferStream extends OutputStream {
    final OutputBuffer buffer;
    int count;

    public CountingOutputBufferStream(OutputBuffer buffer) {
      this.buffer = buffer;
    }

    @Override
    public void write(byte[] bytes, int offset, int length) throws IOException {
      buffer.write(bytes, offset, length);
      count += length;
    }

    @Override
    public void write(int byteValue) throws IOException {
      buffer.write(byteValue);
      count++;
    }
  }

  public static DBEncoder encoder() {
    return Encoder.ENCODER;
  }

  enum Encoder implements DBEncoder {
    ENCODER;

    @Override
    public int writeObject(OutputBuffer buffer, BSONObject object) {
      try {
        if (object instanceof WritableObjectPosition) {
          return ((WritableObjectPosition) object).writePlainCurrent(buffer);
        }
        return DefaultDBEncoder.FACTORY.create().writeObject(buffer, object);
      } catch (IOException ex) {
        throw Throwables.propagate(ex);
      }
    }
  }

  public static  DBObject wrapUpdateObject(T instance, Marshaler marshaler) {
    return new UpdateObject(instance, marshaler);
  }

  public static  List wrapInsertObjectList(ImmutableList list, Marshaler marshaler) {
    return new InsertObjectList(list, marshaler);
  }

  interface WritableObjectPosition {
    int writeCurrent(OutputBuffer buffer) throws IOException;

    int writePlainCurrent(OutputBuffer buffer) throws IOException;
  }

  private static class UpdateObject implements DBObject, WritableObjectPosition {

    private final T instance;
    private final Marshaler marshaler;

    UpdateObject(T instance, Marshaler marshaler) {
      this.instance = instance;
      this.marshaler = marshaler;
    }

    @Override
    public int writeCurrent(OutputBuffer buffer) throws IOException {
      CountingOutputBufferStream outputStream = new CountingOutputBufferStream(buffer);
      JsonGenerator generator = BSON_FACTORY.createGenerator(outputStream);
      marshaler.marshalInstance(generator, instance);
      generator.close();
      return outputStream.count;
    }

    @Override
    public int writePlainCurrent(OutputBuffer buffer) throws IOException {
      return writeCurrent(buffer);
    }

    @Override
    public Object put(String key, Object v) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(BSONObject o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(Map m) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object get(String key) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Map toMap() {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object removeField(String key) {
      throw new UnsupportedOperationException();
    }

    @Deprecated
    @Override
    public boolean containsKey(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean containsField(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Set keySet() {
      return ImmutableSet.of();
    }

    @Override
    public void markAsPartialObject() {}

    @Override
    public boolean isPartialObject() {
      return false;
    }
  }

  private static class InsertObjectList implements DBObject, List, WritableObjectPosition {
    private final ImmutableList list;
    private int position;
    @Nullable
    private JsonGenerator generator;

    private CountingOutputBufferStream outputStream;
    private final Marshaler marshaler;

    InsertObjectList(ImmutableList list, Marshaler marshaler) {
      this.list = list;
      this.marshaler = marshaler;
    }

    @Override
    public int writeCurrent(OutputBuffer buffer) throws IOException {
      createGeneratorIfNecessary(buffer);
      int previousByteCount = outputStream.count;
      marshaler.marshalInstance(generator, list.get(position));
      if (isLastPosition()) {
        closeGenerator();
      }
      return outputStream.count - previousByteCount;
    }

    @Override
    public int writePlainCurrent(OutputBuffer buffer) throws IOException {
      CountingOutputBufferStream outputStream = new CountingOutputBufferStream(buffer);
      JsonGenerator generator = BSON_FACTORY.createGenerator(outputStream);
      marshaler.marshalInstance(generator, list.get(position));
      generator.close();
      return outputStream.count;
    }

    private void closeGenerator() throws IOException {
      if (generator != null) {
        generator.close();
        generator = null;
      }
    }

    private void createGeneratorIfNecessary(OutputBuffer buffer) throws IOException {
      if (generator == null) {
        outputStream = new CountingOutputBufferStream(buffer);
        generator = BSON_FACTORY.createGenerator(outputStream);
      }
    }

    private boolean isLastPosition() {
      return position == list.size() - 1;
    }

    @Override
    public DBObject get(int index) {
      position = index;
      return this;
    }

    @Override
    public int size() {
      return list.size();
    }

    @Override
    public Object put(String key, Object v) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(BSONObject o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(Map m) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object get(String key) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Map toMap() {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object removeField(String key) {
      throw new UnsupportedOperationException();
    }

    @Deprecated
    @Override
    public boolean containsKey(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean containsField(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Set keySet() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void markAsPartialObject() {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean isPartialObject() {
      return false;
    }

    @Override
    public boolean add(DBObject e) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void add(int index, DBObject element) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(Collection c) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(int index, Collection c) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean contains(Object o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean containsAll(Collection c) {
      throw new UnsupportedOperationException();
    }

    @Override
    public int indexOf(Object o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean isEmpty() {
      return false;
    }

    @Override
    public Iterator iterator() {
      return ImmutableSet.of().iterator();
    }

    @Override
    public int lastIndexOf(Object o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public ListIterator listIterator() {
      return ImmutableList.of().listIterator();
    }

    @Override
    public ListIterator listIterator(int index) {
      return ImmutableList.of().listIterator();
    }

    @Override
    public DBObject set(int index, DBObject element) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public DBObject remove(int index) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean removeAll(Collection c) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean retainAll(Collection c) {
      throw new UnsupportedOperationException();
    }

    @Override
    public List subList(int fromIndex, int toIndex) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object[] toArray() {
      throw new UnsupportedOperationException();
    }

    @Override
    public  V[] toArray(V[] a) {
      throw new UnsupportedOperationException();
    }
  }

  public static  ImmutableList unwrapResultObjectList(List result) {
    if (result.isEmpty()) {
      return ImmutableList.of();
    }
    // Safe as long as caller will use same T for decoder and unwrap
    @SuppressWarnings("unchecked") List results = ((ResultDecoder) result.get(0)).results;

    return ImmutableList.copyOf(results);
  }

  public static  DBDecoderFactory newResultDecoderFor(Marshaler marshaler, int expectedSize) {
    return new ResultDecoder(marshaler, expectedSize);
  }

  /**
   * Special input stream that operates from as writable byte buffer that is filled with BSON object
   * from other input stream ({@link #resetObjectFrom(InputStream)}).
   * Extending buffered input stream
   * to prevent excessive wraping in another buffered stream by {@link BsonParser}
   */
  static final class ObjectBufferInputStream extends BufferedInputStream {
    private byte[] buffer;
    private int position;
    private int limit;

    ObjectBufferInputStream(int capacity) {
      super(null, 1);
      ensureBufferWithCapacity(capacity);
    }

    private void ensureBufferWithCapacity(int capacity) {
      if (buffer == null || buffer.length < capacity) {
        Preconditions.checkArgument(capacity >= 4);
        byte[] temp = buffer;
        this.buffer = new byte[capacity];
        if (temp != null) {
          System.arraycopy(temp, 0, buffer, 0, temp.length);
        }
      }
    }

    void resetObjectFrom(InputStream inputStream) throws IOException {
      ByteStreams.readFully(inputStream, buffer, 0, Ints.BYTES);

      int objectSize = Ints.fromBytes(
          buffer[3],
          buffer[2],
          buffer[1],
          buffer[0]);

      ensureBufferWithCapacity(objectSize);

      ByteStreams.readFully(inputStream, buffer, Ints.BYTES, objectSize - Ints.BYTES);

      position = 0;
      limit = objectSize;
    }

    @Override
    public int available() throws IOException {
      return limit - position;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      len = Math.min(len, available());
      System.arraycopy(buffer, position, b, off, len);
      position += len;
      return len;
    }

    @Override
    public int read() throws IOException {
      if (available() > 0) {
        return UnsignedBytes.toInt(buffer[position++]);
      }
      return -1;
    }
  }

  private static class ResultDecoder implements DBDecoderFactory, DBDecoder, DBObject {
    final List results;
    private final Marshaler marshaler;
    @Nullable
    private BsonParser parser;

    private final ObjectBufferInputStream bufferStream = new ObjectBufferInputStream(2012);

    public ResultDecoder(Marshaler marshaler, int expectedSize) {
      this.marshaler = marshaler;
      this.results = Lists.newArrayListWithExpectedSize(expectedSize);
    }

    public BsonParser createParserIfNecessary() throws IOException {
      if (parser != null) {
        parser.close();
      }
      parser = BSON_FACTORY.createParser(bufferStream);
      return parser;
    }

    @Override
    public DBObject decode(InputStream inputStream, DBCollection collection) throws IOException {
      bufferStream.resetObjectFrom(inputStream);
      createParserIfNecessary();
      parser.nextToken();
      T unmarshaledObject = marshaler.unmarshalInstance(parser);
      results.add(unmarshaledObject);
      return this;
    }

    @Override
    public DBDecoder create() {
      return this;
    }

    @Override
    public BSONObject readObject(byte[] b) {
      throw new UnsupportedOperationException();
    }

    @Override
    public BSONObject readObject(InputStream in) throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public int decode(byte[] b, BSONCallback callback) {
      throw new UnsupportedOperationException();
    }

    @Override
    public int decode(InputStream in, BSONCallback callback) throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public DBCallback getDBCallback(DBCollection collection) {
      throw new UnsupportedOperationException();
    }

    @Override
    public DBObject decode(byte[] b, DBCollection collection) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object put(String key, Object v) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(BSONObject o) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(Map m) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object get(String key) {
      return null;
    }

    @Override
    public Map toMap() {
      throw new UnsupportedOperationException();
    }

    @Override
    public Object removeField(String key) {
      throw new UnsupportedOperationException();
    }

    @Deprecated
    @Override
    public boolean containsKey(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean containsField(String s) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Set keySet() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void markAsPartialObject() {}

    @Override
    public boolean isPartialObject() {
      return false;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy