@Nullable @SuppressWarnings("unchecked") @Override public Object convertToMongoType(@Nullable Object obj, TypeInformation<?> typeInformation) { Optional<Class<?>> target = conversions.getCustomWriteTarget(obj.getClass()); if (target.isPresent()) { return conversionService.convert(obj, target.get()); if (conversions.isSimpleType(obj.getClass())) { return getPotentiallyConvertedSimpleWrite(obj); return maybeConvertList((List<Object>) obj, typeInformation); for (String vk : ((Document) obj).keySet()) { Object o = ((Document) obj).get(vk); newValueDocument.put(vk, convertToMongoType(o, typeInformation));
/** * Registers additional converters that will be available when using the {@link ConversionService} directly (e.g. for * id conversion). These converters are not custom conversions as they'd introduce unwanted conversions (e.g. * ObjectId-to-String). */ private void initializeConverters() { conversionService.addConverter(ObjectIdToStringConverter.INSTANCE); conversionService.addConverter(StringToObjectIdConverter.INSTANCE); if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) { conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE); } if (!conversionService.canConvert(BigInteger.class, ObjectId.class)) { conversionService.addConverter(BigIntegerToObjectIdConverter.INSTANCE); } conversions.registerConvertersIn(conversionService); }
/** * Checks whether we have a custom conversion for the given simple object. Converts the given value if so, applies * {@link Enum} handling or returns the value as is. * * @param value * @param target must not be {@literal null}. * @return */ @Nullable @SuppressWarnings({ "rawtypes", "unchecked" }) private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) { if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) { return value; } if (conversions.hasCustomReadTarget(value.getClass(), target)) { return conversionService.convert(value, target); } if (Enum.class.isAssignableFrom(target)) { return Enum.valueOf((Class<Enum>) target, value.toString()); } return conversionService.convert(value, target); }
/** * Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document. * * @param type must not be {@literal null}. * @return {@literal true} if not a simple type, {@link Collection} or type with custom write target. */ private boolean requiresTypeHint(Class<?> type) { return !conversions.isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type) && !conversions.hasCustomWriteTarget(type, Document.class); }
private void writeToBucket(String path, @Nullable Object value, RedisData sink, Class<?> propertyType) { if (value == null || (value instanceof Optional && !((Optional<?>) value).isPresent())) { return; } if (customConversions.hasCustomWriteTarget(value.getClass())) { Optional<Class<?>> targetType = customConversions.getCustomWriteTarget(value.getClass()); if (!propertyType.isPrimitive() && !targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent() && customConversions.isSimpleType(value.getClass()) && value.getClass() != propertyType) { typeMapper.writeType(value.getClass(), sink.getBucket().getPropertyPath(path)); } if (targetType.filter(it -> ClassUtils.isAssignable(Map.class, it)).isPresent()) { Map<?, ?> map = (Map<?, ?>) conversionService.convert(value, targetType.get()); for (Map.Entry<?, ?> entry : map.entrySet()) { sink.getBucket().put(path + (StringUtils.hasText(path) ? "." : "") + entry.getKey(), toBytes(entry.getValue())); } } else if (targetType.filter(it -> ClassUtils.isAssignable(byte[].class, it)).isPresent()) { sink.getBucket().put(path, toBytes(value)); } else { throw new IllegalArgumentException( String.format("Cannot convert value '%s' of type %s to bytes.", value, value.getClass())); } } }
/** * Checks whether we have a custom conversion for the given simple object. Converts the given value if so, applies * {@link Enum} handling or returns the value as is. * * @param value simple value to convert into a value of type {@code target}. * @param target must not be {@literal null}. * @return the converted value. */ @Nullable @SuppressWarnings({ "rawtypes", "unchecked" }) private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) { if (value == null || target == null || target.isAssignableFrom(value.getClass())) { return value; } if (getCustomConversions().hasCustomReadTarget(value.getClass(), target)) { return getConversionService().convert(value, target); } if (Enum.class.isAssignableFrom(target)) { return Enum.valueOf((Class<Enum>) target, value.toString()); } return getConversionService().convert(value, target); }
@SuppressWarnings({ "unchecked" }) protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor accessor, MongoPersistentProperty prop) { List<Object> collectionInternal = createCollection(asCollection(obj), prop); accessor.put(prop, collectionInternal); return; Bson mapDbObj = createMap((Map<Object, Object>) obj, prop); accessor.put(prop, mapDbObj); return; Optional<Class<?>> basicTargetType = conversions.getCustomWriteTarget(obj.getClass()); accessor.put(prop, conversionService.convert(obj, basicTargetType.get())); return;
@Nullable @SuppressWarnings("unchecked") <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path) { Class<?> rawType = type.getType(); if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { return (T) conversionService.convert(value, rawType); } else if (value instanceof DBRef) { return potentiallyReadOrResolveDbRef((DBRef) value, type, path, rawType); } else if (value instanceof List) { return (T) readCollectionOrArray(type, (List<Object>) value, path); } else if (value instanceof Document) { return (T) read(type, (Document) value, path); } else if (value instanceof DBObject) { return (T) read(type, (BasicDBObject) value, path); } else { return (T) getPotentiallyConvertedSimpleRead(value, rawType); } }
protected void writeInternal(@Nullable Object obj, Bson bson, @Nullable TypeInformation<?> typeHint) { Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Document.class); Document result = conversionService.convert(obj, Document.class); addAllToMap(bson, result); return; writeMapInternal((Map<Object, Object>) obj, bson, ClassTypeInformation.MAP); return; writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection<?>) bson); return;
@Nullable @SuppressWarnings("unchecked") private <S extends Object> S read(TypeInformation<S> type, @Nullable Bson bson, ObjectPath path) { Class<? extends S> rawType = typeToUse.getType(); if (conversions.hasCustomReadTarget(bson.getClass(), rawType)) { return conversionService.convert(bson, rawType); return (S) readCollectionOrArray(typeToUse, (List<?>) bson, path); return (S) readMap(typeToUse, bson, path); return read((MongoPersistentEntity<S>) entity, target, path);
/** * Checks whether we have a custom conversion registered for the given value into an arbitrary simple Mongo type. * Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. * * @param value * @return */ @Nullable private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) { if (value == null) { return null; } Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass()); if (customTarget.isPresent()) { return conversionService.convert(value, customTarget.get()); } if (ObjectUtils.isArray(value)) { if (value instanceof byte[]) { return value; } return asCollection(value); } return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value; }
@SuppressWarnings("unchecked") @Nullable private <R> R readInternal(String path, Class<R> type, RedisData source) { if (customConversions.hasCustomReadTarget(Map.class, readType.getType())) { partial.putAll(source.getBucket().asMap()); R instance = (R) conversionService.convert(partial, readType.getType()); if (conversionService.canConvert(byte[].class, readType.getType())) { return (R) conversionService.convert(source.getBucket().get(StringUtils.hasText(path) ? path : "_raw"), readType.getType());
/** * Populates the given {@link Collection sink} with converted values from the given {@link Collection source}. * * @param source the collection to create a {@link Collection} for, must not be {@literal null}. * @param type the {@link TypeInformation} to consider or {@literal null} if unknown. * @param sink the {@link Collection} to write to. * @return */ @SuppressWarnings("unchecked") private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) { TypeInformation<?> componentType = null; List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink); if (type != null) { componentType = type.getComponentType(); } for (Object element : source) { Class<?> elementType = element == null ? null : element.getClass(); if (elementType == null || conversions.isSimpleType(elementType)) { collection.add(getPotentiallyConvertedSimpleWrite(element)); } else if (element instanceof Collection || elementType.isArray()) { collection.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList())); } else { Document document = new Document(); writeInternal(element, document, componentType); collection.add(document); } } return collection; }
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor, DocumentAccessor dbObjectAccessor, @Nullable MongoPersistentProperty idProperty) { // Write the properties for (MongoPersistentProperty prop : entity) { if (prop.equals(idProperty) || !prop.isWritable()) { continue; } if (prop.isAssociation()) { writeAssociation(prop.getRequiredAssociation(), accessor, dbObjectAccessor); continue; } Object value = accessor.getProperty(prop); if (value == null) { continue; } if (!conversions.isSimpleType(value.getClass())) { writePropertyInternal(value, dbObjectAccessor, prop); } else { writeSimpleInternal(value, bson, prop); } } }
/** * @param keyspace * @param path * @param values * @param typeHint * @param sink */ private void writeCollection(String keyspace, String path, @Nullable Iterable<?> values, TypeInformation<?> typeHint, RedisData sink) { if (values == null) { return; } int i = 0; for (Object value : values) { if (value == null) { break; } String currentPath = path + ".[" + i + "]"; if (!ClassUtils.isAssignable(typeHint.getType(), value.getClass())) { throw new MappingException( String.format(INVALID_TYPE_ASSIGNMENT, value.getClass(), currentPath, typeHint.getType())); } if (customConversions.hasCustomWriteTarget(value.getClass())) { writeToBucket(currentPath, value, sink, typeHint.getType()); } else { writeInternal(keyspace, currentPath, value, typeHint, sink); } i++; } }
private void writeProperties(VaultPersistentEntity<?> entity, PersistentPropertyAccessor accessor, SecretDocumentAccessor sink, @Nullable VaultPersistentProperty idProperty) { // Write the properties for (VaultPersistentProperty prop : entity) { if (prop.equals(idProperty) || !prop.isWritable()) { continue; } Object value = accessor.getProperty(prop); if (value == null) { continue; } if (!conversions.isSimpleType(value.getClass())) { writePropertyInternal(value, sink, prop); } else { sink.put(prop, getPotentiallyConvertedSimpleWrite(value)); } } }
public TwoStepsConversions(CustomConversions customConversions, ObjectToKeyFactory objectToKeyFactory) { this.objectToKeyFactory = objectToKeyFactory; this.conversionService = new DefaultConversionService(); this.internalConversionService = new DefaultConversionService(); this.customConversions = customConversions; this.customConversions.registerConvertersIn(this.conversionService); this.internalConversionService.addConverter(BYTE_ARRAY_TO_BLOB_CONVERTER); this.internalConversionService.addConverter(BLOB_TO_BYTE_ARRAY_CONVERTER); }
@Override public Object convertForWriteIfNeeded(Object value) { if (value == null) { return null; } return this.conversions.getCustomWriteTarget(value.getClass()) // .map(it -> (Object) this.conversionService.convert(value, it)) // .orElse(value); }
/** * Helper method to read the value based on the value type. * * @param value the value to convert. * @param type the type information. * @param parent the optional parent. * @param <R> the target type. * @return the converted object. */ @SuppressWarnings("unchecked") private <R> R readValue(Object value, TypeInformation<?> type, Object parent) { Class<?> rawType = type.getType(); if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { return (R) conversionService.convert(value, rawType); } else if (value instanceof CouchbaseDocument) { return (R) read(type, (CouchbaseDocument) value, parent); } else if (value instanceof CouchbaseList) { return (R) readCollection(type, (CouchbaseList) value, parent); } else { return (R) getPotentiallyConvertedSimpleRead(value, rawType); } }
if (!customConversions.hasCustomWriteTarget(source.getClass())) { typeMapper.writeType(ClassUtils.getUserClass(source), sink.getBucket().getPath()); sink.getBucket().put("_raw", conversionService.convert(source, byte[].class)); return;