/*
 * Copyright 2011-2020 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.data.convert;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Default implementation of TypeMapper.
Author:Oliver Gierke, Thomas Darimont, Christoph Strobl, Mark Paluch
/** * Default implementation of {@link TypeMapper}. * * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch */
public class DefaultTypeMapper<S> implements TypeMapper<S> { private final TypeAliasAccessor<S> accessor; private final List<? extends TypeInformationMapper> mappers; private final Map<Alias, Optional<TypeInformation<?>>> typeCache; private final Function<Alias, Optional<TypeInformation<?>>> getAlias;
Creates a new DefaultTypeMapper using the given TypeAliasAccessor. It will use a SimpleTypeInformationMapper to calculate type aliases.
Params:
  • accessor – must not be null.
/** * Creates a new {@link DefaultTypeMapper} using the given {@link TypeAliasAccessor}. It will use a * {@link SimpleTypeInformationMapper} to calculate type aliases. * * @param accessor must not be {@literal null}. */
public DefaultTypeMapper(TypeAliasAccessor<S> accessor) { this(accessor, Collections.singletonList(new SimpleTypeInformationMapper())); }
Creates a new DefaultTypeMapper using the given TypeAliasAccessor and TypeInformationMapper s.
Params:
  • accessor – must not be null.
  • mappers – must not be null.
/** * Creates a new {@link DefaultTypeMapper} using the given {@link TypeAliasAccessor} and {@link TypeInformationMapper} * s. * * @param accessor must not be {@literal null}. * @param mappers must not be {@literal null}. */
public DefaultTypeMapper(TypeAliasAccessor<S> accessor, List<? extends TypeInformationMapper> mappers) { this(accessor, null, mappers); }
Creates a new DefaultTypeMapper using the given TypeAliasAccessor, MappingContext and additional TypeInformationMappers. Will register a MappingContextTypeInformationMapper before the given additional mappers.
Params:
  • accessor – must not be null.
  • mappingContext –
  • additionalMappers – must not be null.
/** * Creates a new {@link DefaultTypeMapper} using the given {@link TypeAliasAccessor}, {@link MappingContext} and * additional {@link TypeInformationMapper}s. Will register a {@link MappingContextTypeInformationMapper} before the * given additional mappers. * * @param accessor must not be {@literal null}. * @param mappingContext * @param additionalMappers must not be {@literal null}. */
public DefaultTypeMapper(TypeAliasAccessor<S> accessor, @Nullable MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext, List<? extends TypeInformationMapper> additionalMappers) { Assert.notNull(accessor, "Accessor must not be null!"); Assert.notNull(additionalMappers, "AdditionalMappers must not be null!"); List<TypeInformationMapper> mappers = new ArrayList<>(additionalMappers.size() + 1); if (mappingContext != null) { mappers.add(new MappingContextTypeInformationMapper(mappingContext)); } mappers.addAll(additionalMappers); this.mappers = Collections.unmodifiableList(mappers); this.accessor = accessor; this.typeCache = new ConcurrentHashMap<>(); this.getAlias = key -> { for (TypeInformationMapper mapper : mappers) { TypeInformation<?> typeInformation = mapper.resolveTypeFrom(key); if (typeInformation != null) { return Optional.of(typeInformation); } } return Optional.empty(); }; } /* * (non-Javadoc) * @see org.springframework.data.convert.TypeMapper#readType(java.lang.Object) */ @Nullable public TypeInformation<?> readType(S source) { Assert.notNull(source, "Source object must not be null!"); return getFromCacheOrCreate(accessor.readAliasFrom(source)); }
Tries to lookup a TypeInformation for the given alias from the cache and return it if found. If none is found it'll consult the TypeInformationMappers and cache the value found.
Params:
  • alias –
Returns:
/** * Tries to lookup a {@link TypeInformation} for the given alias from the cache and return it if found. If none is * found it'll consult the {@link TypeInformationMapper}s and cache the value found. * * @param alias * @return */
@Nullable private TypeInformation<?> getFromCacheOrCreate(Alias alias) { Optional<TypeInformation<?>> typeInformation = typeCache.get(alias); if (typeInformation == null) { typeInformation = typeCache.computeIfAbsent(alias, getAlias); } return typeInformation.orElse(null); } /* * (non-Javadoc) * @see org.springframework.data.convert.TypeMapper#readType(java.lang.Object, org.springframework.data.util.TypeInformation) */ public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> basicType) { Assert.notNull(source, "Source must not be null!"); Assert.notNull(basicType, "Basic type must not be null!"); Class<?> documentsTargetType = getDefaultedTypeToBeUsed(source); if (documentsTargetType == null) { return basicType; } Class<T> rawType = basicType.getType(); boolean isMoreConcreteCustomType = rawType == null || rawType.isAssignableFrom(documentsTargetType) && !rawType.equals(documentsTargetType); if (!isMoreConcreteCustomType) { return basicType; } ClassTypeInformation<?> targetType = ClassTypeInformation.from(documentsTargetType); return (TypeInformation<? extends T>) basicType.specialize(targetType); }
Returns the type discovered through readType(Object) but defaulted to the one returned by getFallbackTypeFor(Object).
Params:
  • source –
Returns:
/** * Returns the type discovered through {@link #readType(Object)} but defaulted to the one returned by * {@link #getFallbackTypeFor(Object)}. * * @param source * @return */
@Nullable private Class<?> getDefaultedTypeToBeUsed(S source) { TypeInformation<?> documentsTargetTypeInformation = readType(source); documentsTargetTypeInformation = documentsTargetTypeInformation == null ? getFallbackTypeFor(source) : documentsTargetTypeInformation; return documentsTargetTypeInformation == null ? null : documentsTargetTypeInformation.getType(); }
Returns the type fallback TypeInformation in case none could be extracted from the given source.
Params:
  • source – will never be null.
Returns:
/** * Returns the type fallback {@link TypeInformation} in case none could be extracted from the given source. * * @param source will never be {@literal null}. * @return */
@Nullable protected TypeInformation<?> getFallbackTypeFor(S source) { return null; } /* * (non-Javadoc) * @see org.springframework.data.convert.TypeMapper#writeType(java.lang.Class, java.lang.Object) */ public void writeType(Class<?> type, S dbObject) { writeType(ClassTypeInformation.from(type), dbObject); } /* * (non-Javadoc) * @see org.springframework.data.convert.TypeMapper#writeType(org.springframework.data.util.TypeInformation, java.lang.Object) */ public void writeType(TypeInformation<?> info, S sink) { Assert.notNull(info, "TypeInformation must not be null!"); Alias alias = getAliasFor(info); if (alias.isPresent()) { accessor.writeTypeTo(sink, alias.getValue()); } }
Returns the alias to be used for the given TypeInformation.
Params:
  • info – must not be null
Returns:the alias for the given TypeInformation or null of none was found or all mappers returned null.
/** * Returns the alias to be used for the given {@link TypeInformation}. * * @param info must not be {@literal null} * @return the alias for the given {@link TypeInformation} or {@literal null} of none was found or all mappers * returned {@literal null}. */
protected final Alias getAliasFor(TypeInformation<?> info) { Assert.notNull(info, "TypeInformation must not be null!"); for (TypeInformationMapper mapper : mappers) { Alias alias = mapper.createAliasFor(info); if (alias.isPresent()) { return alias; } } return Alias.NONE; } }