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

clients.mongodb.codec.CustomDocumentCodec Maven / Gradle / Ivy

The newest version!
package clients.mongodb.codec;

import com.github.simondan.definition.*;
import com.github.simondan.util.ObjectCreator;
import config.InternalConfig;
import org.bson.*;
import org.bson.assertions.Assertions;
import org.bson.codecs.*;
import org.bson.codecs.DocumentCodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.types.ObjectId;
import util.*;

import java.util.*;

import static java.util.Arrays.asList;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;

/**
 * @author Simon Danner, 19.12.2015
 */
public class CustomDocumentCodec implements CollectibleCodec
{
  private static final String OBJECT_ID = "_id";
  private static final String ID = "id";
  private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(asList(new ValueCodecProvider(),
                                                                             new BsonValueCodecProvider(),
                                                                             new DocumentCodecProvider()));
  private static final BsonTypeClassMap DEFAULT_BSON_TYPE_CLASS_MAP = new BsonTypeClassMap();

  private BsonTypeClassMap bsonTypeClassMap;
  private CodecRegistry registry;
  private IdGenerator idGenerator;
  private Transformer valueTransformer;
  private Class objectClass;

  public CustomDocumentCodec(Class pObjectClass)
  {
    registry = Assertions.notNull("registry", DEFAULT_REGISTRY);
    bsonTypeClassMap = Assertions.notNull("bsonTypeClassMap", DEFAULT_BSON_TYPE_CLASS_MAP);
    idGenerator = new ObjectIdGenerator();
    valueTransformer = value -> value;
    objectClass = pObjectClass;
  }

  @Override
  public boolean documentHasId(T document)
  {
    return document.asMap().containsKey(ID);
  }

  @Override
  public BsonValue getDocumentId(T document)
  {
    if (!documentHasId(document))
      throw new IllegalStateException("The document does not contain an _id");

    Object id = document.asMap().get(ID);
    if (id instanceof BsonValue)
      return (BsonValue) id;

    BsonDocument idHoldingDocument = new BsonDocument();
    BsonWriter writer = new BsonDocumentWriter(idHoldingDocument);
    writer.writeStartDocument();
    writer.writeName(ID);
    writeValue(writer, EncoderContext.builder().build(), id);
    writer.writeEndDocument();
    return idHoldingDocument.get(ID);
  }

  @Override
  public T generateIdIfAbsentFromDocument(final T document)
  {
    if (!documentHasId(document))
      document.asMap().put(ID, idGenerator.generate());

    return document;
  }

  @Override
  public void encode(final BsonWriter writer, final T document, final EncoderContext encoderContext)
  {
    Map map = document.asMap();
    //Bei Unique-Objekten muss die ID als Object-ID gesetzt werden
    if (document instanceof AbstractUniqueStorableObject)
    {
      String id = ((AbstractUniqueStorableObject) document).getID();
      ObjectId objectID = id == null ? new ObjectId() : new ObjectId(id);
      map.put(InternalConfig.OBJECT_ID, objectID);
      map.remove(document.getKey(AbstractUniqueStorableObject.id));
    }
    writeMap(writer, map, encoderContext);
  }

  @SuppressWarnings("unchecked")
  @Override
  public T decode(final BsonReader pReader, final DecoderContext pDecoderContext)
  {
    //Map ermitteln
    Map keyValueMap = new LinkedHashMap<>();
    pReader.readStartDocument();
    while (pReader.readBsonType() != BsonType.END_OF_DOCUMENT)
    {
      String key = pReader.readName();
      Object value = readValue(pReader, pDecoderContext);
      if (AbstractUniqueStorableObject.class.isAssignableFrom(objectClass) && key.equals(InternalConfig.OBJECT_ID))
      {
        Objects.requireNonNull(value);
        keyValueMap.put("id", value.toString());
      }
      else if (!key.equals(InternalConfig.OBJECT_ID))
        keyValueMap.put(key, value);
    }
    pReader.readEndDocument();

    return new ObjectCreator<>(objectClass, keyValueMap, DatabaseUtil.boxTypes).create();
  }

  @Override
  public Class getEncoderClass()
  {
    return objectClass;
  }

  private void beforeFields(final BsonWriter bsonWriter, final EncoderContext encoderContext, final Map document)
  {
    if (encoderContext.isEncodingCollectibleDocument() && document.containsKey(ID))
    {
      bsonWriter.writeName(OBJECT_ID);
      writeValue(bsonWriter, encoderContext, document.get(ID));
    }
  }

  private boolean skipField(final EncoderContext encoderContext, final String key)
  {
    return encoderContext.isEncodingCollectibleDocument() && key.equals(OBJECT_ID);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final Object value)
  {
    if (value == null)
      writer.writeNull();
    else if (value instanceof Iterable)
      writeIterable(writer, (Iterable) value, encoderContext.getChildContext());
    else if (value instanceof Map)
      writeMap(writer, (Map) value, encoderContext.getChildContext());
    else
    {
      Codec codec = registry.get(value.getClass());
      encoderContext.encodeWithChildContext(codec, writer, value);
    }
  }

  private void writeMap(final BsonWriter writer, final Map map, final EncoderContext encoderContext)
  {
    writer.writeStartDocument();

    beforeFields(writer, encoderContext, map);

    for (final Map.Entry entry : map.entrySet())
    {
      if (skipField(encoderContext, entry.getKey()))
        continue;

      writer.writeName(entry.getKey());
      writeValue(writer, encoderContext, entry.getValue());
    }
    writer.writeEndDocument();
  }

  private void writeIterable(final BsonWriter writer, final Iterable list, final EncoderContext encoderContext)
  {
    writer.writeStartArray();
    for (final Object value : list)
      writeValue(writer, encoderContext, value);

    writer.writeEndArray();
  }

  private Object readValue(final BsonReader reader, final DecoderContext decoderContext)
  {
    BsonType bsonType = reader.getCurrentBsonType();
    if (bsonType == BsonType.NULL)
    {
      reader.readNull();
      return null;
    }
    else if (bsonType == BsonType.ARRAY)
      return readList(reader, decoderContext);
    else if (bsonType == BsonType.BINARY)
    {
      byte bsonSubType = reader.peekBinarySubType();
      if (bsonSubType == BsonBinarySubType.UUID_STANDARD.getValue() || bsonSubType == BsonBinarySubType.UUID_LEGACY.getValue())
        return registry.get(UUID.class).decode(reader, decoderContext);
    }
    return valueTransformer.transform(registry.get(bsonTypeClassMap.get(bsonType)).decode(reader, decoderContext));
  }

  private List readList(final BsonReader reader, final DecoderContext decoderContext)
  {
    reader.readStartArray();
    List list = new ArrayList<>();
    while (reader.readBsonType() != BsonType.END_OF_DOCUMENT)
      list.add(readValue(reader, decoderContext));

    reader.readEndArray();
    return list;
  }
}