Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#196/#197] implement caching for unproxy results #198

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

### Fixed

- Issue [#196](https://github.com/42BV/beanmapper/issues/196) **Calls to the logger should not contain a call to String.formatted(Object...)**; Fixed by removing any formatting from performance logging. Trace-logging should keep the level of detail provided by the formatting.
- Issue [#197](https://github.com/42BV/beanmapper/issues/197) **Implement caching for Unproxy results.**; Created UnproxyResultStore, allowing for thread-safe caching and retrieval of unproxied classes.

## [4.1.3] - 2024-03-27

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import io.beanmapper.core.collections.CollectionHandler;
import io.beanmapper.core.unproxy.BeanUnproxy;
import io.beanmapper.core.unproxy.UnproxyResultStore;

public class CollectionHandlerStore {

Expand All @@ -28,7 +29,8 @@ public CollectionHandler getCollectionHandlerFor(Class<?> clazz, BeanUnproxy bea
return collectionHandler;
}
// Unproxy the collection class in case it was anonymous and try again
return getCollectionHandlerFor(beanUnproxy.unproxy(clazz));
Class<?> unproxiedClass = UnproxyResultStore.getInstance().getOrComputeUnproxyResult(clazz, beanUnproxy);
return getCollectionHandlerFor(unproxiedClass);
}

private CollectionHandler getCollectionHandlerFor(Class<?> clazz) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/beanmapper/config/OverrideField.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

public class OverrideField<T> {

private static final String LOGGING_STRING = "OverrideField#get(void) -> OverrideField#get(void)";
private final Supplier<T> supplier;

private boolean block = false;
Expand Down Expand Up @@ -34,7 +35,7 @@ public T get() {
return null;
}
if (this.value == null) {
this.value = BeanMapperPerformanceLogger.runTimed("%s#%s -> %s#%s".formatted(this.getClass().getSimpleName(), "get(void)", this.getClass().getSimpleName(), "get(void)"), this.supplier);
this.value = BeanMapperPerformanceLogger.runTimed(LOGGING_STRING, this.supplier);
sptdevos marked this conversation as resolved.
Show resolved Hide resolved
}
return value;
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/io/beanmapper/config/StrictMappingProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import io.beanmapper.core.unproxy.BeanUnproxy;
import io.beanmapper.core.unproxy.SkippingBeanUnproxy;
import io.beanmapper.core.unproxy.UnproxyResultStore;
import io.beanmapper.utils.BeanMapperPerformanceLogger;

import static io.beanmapper.utils.CanonicalClassName.determineCanonicalClassName;

public class StrictMappingProperties {

private static final String LOGGING_STRING = "StrictMappingProperties#createBeanPair(Class, Class) -> BeanUnproxy#unproxy(Class)";

private BeanUnproxy beanUnproxy;

/**
Expand Down Expand Up @@ -82,9 +85,10 @@ public void setApplyStrictMappingConvention(boolean applyStrictMappingConvention
}

public BeanPair createBeanPair(Class<?> sourceClass, Class<?> targetClass) {
BeanPair beanPair = BeanMapperPerformanceLogger.runTimed("%s#%s".formatted(this.getClass().getSimpleName(), "createBeanPair(Class, Class) -> BeanUnproxy#unproxy(Class)"), () -> {
Class<?> unproxiedSource = beanUnproxy.unproxy(sourceClass);
Class<?> unproxiedTarget = beanUnproxy.unproxy(targetClass);
UnproxyResultStore unproxyResultStore = UnproxyResultStore.getInstance();
BeanPair beanPair = BeanMapperPerformanceLogger.runTimed(LOGGING_STRING, () -> {
Class<?> unproxiedSource = unproxyResultStore.getOrComputeUnproxyResult(sourceClass, beanUnproxy);
Class<?> unproxiedTarget = unproxyResultStore.getOrComputeUnproxyResult(targetClass, beanUnproxy);
return new BeanPair(unproxiedSource, unproxiedTarget);
});
if (!isApplyStrictMappingConvention()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

public abstract class AbstractCollectionHandler<C> implements CollectionHandler<C> {

private static final String LOGGING_STRING = "%s#mapItem(BeanMapper, Class, Object) -> BeanMapper#map(Object)";

private final Class<C> type;
private final DefaultBeanInitializer beanInitializer = new DefaultBeanInitializer();

Expand All @@ -33,14 +35,12 @@ public Object mapItem(
BeanMapper beanMapper,
Class<?> collectionElementClass,
Object source) {
return BeanMapperPerformanceLogger.runTimed("%s#%s"
.formatted(this.getClass().getSimpleName(), "mapItem(BeanMapper, Class, Object) -> BeanMapper#map(Object)"),
() -> beanMapper.wrap()
return BeanMapperPerformanceLogger.runTimed(() -> beanMapper.wrap()
.setTargetClass(collectionElementClass)
.setCollectionClass(null)
.setConverterChoosable(true)
.build()
.map(source));
.map(source), LOGGING_STRING, getClass().getSimpleName());
}

@Override
Expand Down Expand Up @@ -91,4 +91,4 @@ public int getGenericParameterIndex() {
return 0;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

public class CollectionConverter implements BeanConverter {

private static final String LOGGING_STRING = "%s#convert(BeanMapper, Object, Class, BeanPropertyMatch) -> BeanMapper#map(Object)";

private final CollectionHandler<?> collectionHandler;

public CollectionConverter(CollectionHandler<?> collectionHandler) {
Expand All @@ -25,9 +27,7 @@ public <R, U> U convert(
return targetClass.cast(source);
}

return BeanMapperPerformanceLogger.runTimed("%s#%s"
.formatted(this.getClass().getSimpleName(), "convert(BeanMapper, Object, Class, BeanPropertyMatch) -> BeanMapper#map(Object)"),
() -> beanMapper.wrap()
return BeanMapperPerformanceLogger.runTimed(() -> beanMapper.wrap()
.setCollectionClass(collectionHandler.getType())
.setCollectionUsage(beanPropertyMatch.getCollectionInstructions().getBeanCollectionUsage())
.setPreferredCollectionClass(beanPropertyMatch.getCollectionInstructions().getPreferredCollectionClass().getAnnotationClass())
Expand All @@ -36,7 +36,7 @@ public <R, U> U convert(
.setTarget(beanPropertyMatch.getTargetObject())
.setUseNullValue()
.build()
.map(source));
.map(source), LOGGING_STRING, getClass().getSimpleName());
}

@Override
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/io/beanmapper/core/unproxy/UnproxyResultStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.beanmapper.core.unproxy;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Singleton responsible for storing the results of unproxy-results. Allows for fast and thread-safe retrieval of results.
*/
public final class UnproxyResultStore {

private static UnproxyResultStore INSTANCE;

private final Map<Class<?>, Class<?>> unproxyResultClassStore;

private UnproxyResultStore() {
unproxyResultClassStore = new ConcurrentHashMap<>();
}

/**
* Gets or computes the result of an unproxy-operation.
*
* @param source The class for which the unproxy-result needs to be retrieved or computed.
* @param unproxy The BeanUnproxy that will be used to compute an unproxy-result if none exist for the given source-class.
* @return The unproxied class.
*/
public Class<?> getOrComputeUnproxyResult(Class<?> source, BeanUnproxy unproxy) {
return unproxyResultClassStore.computeIfAbsent(source, unproxy::unproxy);
}

public static synchronized UnproxyResultStore getInstance() {
if (INSTANCE == null) {
INSTANCE = new UnproxyResultStore();
}
return INSTANCE;
}

}
10 changes: 7 additions & 3 deletions src/main/java/io/beanmapper/strategy/AbstractMapStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import io.beanmapper.core.BeanMatch;
import io.beanmapper.core.BeanPropertyMatch;
import io.beanmapper.core.converter.BeanConverter;
import io.beanmapper.core.unproxy.BeanUnproxy;
import io.beanmapper.core.unproxy.UnproxyResultStore;
import io.beanmapper.exceptions.BeanConversionException;
import io.beanmapper.exceptions.BeanPropertyNoMatchException;
import io.beanmapper.utils.BeanMapperTraceLogger;
Expand Down Expand Up @@ -65,8 +67,9 @@ public <S> ConstructorArguments getConstructorArguments(S source, BeanMatch bean
}

public <T, S> BeanMatch getBeanMatch(Class<S> sourceClazz, Class<T> targetClazz) {
Class<?> sourceClass = getConfiguration().getBeanUnproxy().unproxy(sourceClazz);
Class<?> targetClass = getConfiguration().getBeanUnproxy().unproxy(targetClazz);
BeanUnproxy unproxy = getConfiguration().getBeanUnproxy();
Class<?> sourceClass = UnproxyResultStore.getInstance().getOrComputeUnproxyResult(sourceClazz, unproxy);
Class<?> targetClass = UnproxyResultStore.getInstance().getOrComputeUnproxyResult(targetClazz, unproxy);
return getConfiguration().getBeanMatchStore().getBeanMatch(
configuration.getStrictMappingProperties().createBeanPair(sourceClass, targetClass)
);
Expand Down Expand Up @@ -131,7 +134,8 @@ private void dealWithMappableNestedClass(BeanPropertyMatch beanPropertyMatch) {
*/
public Object convert(Object value, Class<?> targetClass, BeanPropertyMatch beanPropertyMatch) {

Class<?> valueClass = getConfiguration().getBeanUnproxy().unproxy(beanPropertyMatch.getSourceClass());
BeanUnproxy unproxy = getConfiguration().getBeanUnproxy();
Class<?> valueClass = UnproxyResultStore.getInstance().getOrComputeUnproxyResult(beanPropertyMatch.getSourceClass(), unproxy);
BeanConverter converter = getConverterOptional(valueClass, targetClass);

if (converter != null) {
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/io/beanmapper/strategy/MapToClassStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import io.beanmapper.config.Configuration;
import io.beanmapper.core.BeanMatch;
import io.beanmapper.core.converter.BeanConverter;
import io.beanmapper.core.unproxy.BeanUnproxy;
import io.beanmapper.core.unproxy.UnproxyResultStore;
import io.beanmapper.utils.BeanMapperTraceLogger;

public class MapToClassStrategy extends MapToInstanceStrategy {
Expand All @@ -17,7 +19,8 @@ public <S, T> T map(S source) {
Class<?> targetClass = getConfiguration().getTargetClass();

if (getConfiguration().isConverterChoosable() || source instanceof Record) {
Class<?> valueClass = getConfiguration().getBeanUnproxy().unproxy(source.getClass());
BeanUnproxy unproxy = getConfiguration().getBeanUnproxy();
Class<?> valueClass = UnproxyResultStore.getInstance().getOrComputeUnproxyResult(source.getClass(), unproxy);
BeanConverter converter = getConverterOptional(valueClass, targetClass);
if (converter != null) {
BeanMapperTraceLogger.log("Converter called for source of class {}, while mapping to class {}\t{}->", source.getClass(), targetClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

public class MapToDynamicClassStrategy extends AbstractMapStrategy {

private static final String LOGGING_STRING = "Recursively calling BeanMapper#map(Object), to map source of type %s, to type %s.";

public MapToDynamicClassStrategy(BeanMapper beanMapper, Configuration configuration) {
super(beanMapper, configuration);
}
Expand Down Expand Up @@ -49,25 +51,21 @@ public <S, T> T downsizeSource(S source, List<String> downsizeSourceFields) {
Class<?> targetClass = getConfiguration().getTargetClass();
Object target = getConfiguration().getTarget();

Object dynSource = BeanMapperPerformanceLogger.runTimed("Recursively calling BeanMapper#map(Object), to map source of type %s, to target of type %s."
.formatted(source.getClass().getCanonicalName(), dynamicClass.getCanonicalName()),
() -> getBeanMapper()
Object dynSource = BeanMapperPerformanceLogger.runTimed(() -> getBeanMapper()
.wrap()
.downsizeSource(null)
.setTarget(target)
.setTargetClass(dynamicClass)
.build()
.map(source));
.map(source), LOGGING_STRING, source.getClass().getSimpleName(), targetClass != null ? targetClass.getSimpleName() : null);

return BeanMapperPerformanceLogger.runTimed("Recursively calling BeanMapper#map(Object), to map source of type %s, to type %s."
.formatted(dynSource.getClass().getCanonicalName(), targetClass != null ? targetClass.getCanonicalName() : "null"),
() -> getBeanMapper()
return BeanMapperPerformanceLogger.runTimed(() -> getBeanMapper()
.wrap()
.downsizeSource(null)
.setTarget(target)
.setTargetClass(targetClass)
.build()
.map(dynSource));
.map(dynSource), LOGGING_STRING, dynSource.getClass().getSimpleName(), targetClass != null ? targetClass.getSimpleName() : null);
}

public <S, T> T downsizeTarget(S source, List<String> downsizeTargetFields) {
Expand All @@ -76,13 +74,12 @@ public <S, T> T downsizeTarget(S source, List<String> downsizeTargetFields) {
downsizeTargetFields,
getConfiguration().getStrictMappingProperties());
Class<?> collectionClass = getBeanMapper().getConfiguration().getCollectionClass();
return BeanMapperPerformanceLogger.runTimed("Recursively calling BeanMapper#map(Object), to map source of type %s, to type %s."
.formatted(source.getClass().getCanonicalName(), dynamicClass.getCanonicalName()), () -> getBeanMapper()
return BeanMapperPerformanceLogger.runTimed(() -> getBeanMapper()
.wrap()
.downsizeTarget(null)
.setCollectionClass(collectionClass)
.setTargetClass(dynamicClass)
.build()
.map(source));
.map(source), LOGGING_STRING, source.getClass().getSimpleName(), dynamicClass.getSimpleName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public static <T> T runTimed(String taskName, Supplier<T> task) {
return result;
}

public static <T> T runTimed(Supplier<T> task, String unformattedTaskName, Object... messageArguments) {
if (log.isDebugEnabled()) {
String taskName = unformattedTaskName.formatted(messageArguments);
return runTimed(taskName, task);
}
return task.get();
}

private static class Stopwatch {

private final Instant started;
Expand Down
Loading