From 80bbea4283f9a194879f4bf4e29d0175f126c4b6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 25 Sep 2024 17:18:35 +0200 Subject: [PATCH] HHH-18131 Composite identifiers with associations stopped working with @IdClass --- .../internal/DefaultMergeEventListener.java | 2 +- .../org/hibernate/event/spi/MergeContext.java | 4 ++++ .../internal/util/EntityPrinter.java | 3 ++- .../jpa/internal/PersistenceUnitUtilImpl.java | 3 ++- .../mapping/EntityIdentifierMapping.java | 10 ++++++++ ...InverseNonAggregatedIdentifierMapping.java | 23 ++++++++++++++++--- .../NonAggregatedIdentifierMappingImpl.java | 22 +++++++++++++++--- .../entity/AbstractEntityPersister.java | 6 +++++ .../persister/entity/EntityPersister.java | 15 ++++++++++-- ...mousTupleBasicEntityIdentifierMapping.java | 6 +++++ ...sTupleEmbeddedEntityIdentifierMapping.java | 6 +++++ ...eNonAggregatedEntityIdentifierMapping.java | 7 ++++++ .../main/java/org/hibernate/type/AnyType.java | 2 +- .../java/org/hibernate/type/EntityType.java | 10 ++++---- ...sticalLoggingSessionEventListenerTest.java | 10 +++----- 15 files changed, 105 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 452abc16e9..3dbcb8dc11 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -164,7 +164,7 @@ public class DefaultMergeEventListener final Object originalId; if ( entry == null ) { final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); - originalId = persister.getIdentifier( entity, source ); + originalId = persister.getIdentifier( entity, copiedAlready ); if ( originalId != null ) { final EntityKey entityKey; if ( persister.getIdentifierType() instanceof ComponentType ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/MergeContext.java b/hibernate-core/src/main/java/org/hibernate/event/spi/MergeContext.java index c9a83e3e45..17cf332e61 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/MergeContext.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/MergeContext.java @@ -366,4 +366,8 @@ public class MergeContext implements Map { // Entity was not found in current persistence context. Use Object#toString() method. return "[" + entity + "]"; } + + public EventSource getEventSource() { + return session; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java index 12ccc44062..78618d8293 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/EntityPrinter.java @@ -15,6 +15,7 @@ import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.TypedValue; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -53,7 +54,7 @@ public final class EntityPrinter { result.put( entityPersister.getIdentifierPropertyName(), entityPersister.getIdentifierType().toLoggableString( - entityPersister.getIdentifier( entity, null ), + entityPersister.getIdentifier( entity, (SharedSessionContractImplementor) null ), factory ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java index 2cfbe054cd..b3572934bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/PersistenceUnitUtilImpl.java @@ -13,6 +13,7 @@ import org.hibernate.Hibernate; import org.hibernate.MappingException; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.LazyInitializer; @@ -106,7 +107,7 @@ public class PersistenceUnitUtilImpl implements PersistenceUnitUtil, Serializabl catch (MappingException ex) { throw new IllegalArgumentException( entityClass.getName() + " is not an entity", ex ); } - return persister.getIdentifier( entity, null ); + return persister.getIdentifier( entity, (SharedSessionContractImplementor) null ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java index f6612255e8..92ea4430b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java @@ -10,6 +10,7 @@ import org.hibernate.TransientObjectException; import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Id; @@ -68,6 +69,15 @@ public interface EntityIdentifierMapping extends ValuedModelPart { */ Object getIdentifier(Object entity); + /** + * Extract the identifier from an instance of the entity + * + * It's supposed to be use during the merging process + */ + default Object getIdentifier(Object entity, MergeContext mergeContext){ + return getIdentifier( entity ); + } + /** * Return the identifier of the persistent or transient object, or throw * an exception if the instance is "unsaved" diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java index 5e47f34d21..46589709d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java @@ -14,6 +14,7 @@ import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -196,6 +197,11 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp @Override public Object getIdentifier(Object entity) { + return getIdentifier( entity, null ); + } + + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { if ( hasContainingClass() ) { final Object id = identifierValueMapper.getRepresentationStrategy().getInstantiator().instantiate( null, @@ -218,16 +224,17 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp //JPA 2 @MapsId + @IdClass points to the pk of the entity else if ( attributeMapping instanceof ToOneAttributeMapping && !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) { + final Object toOne = getIfMerged( o, mergeContext ); final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( toOneAttributeMapping.getSideNature().inverse() ); if ( targetPart.isEntityIdentifierMapping() ) { - propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( o ); + propertyValues[i] = ( (EntityIdentifierMapping) targetPart ) + .getIdentifier( toOne, mergeContext ); } else { - propertyValues[i] = o; - assert false; + propertyValues[i] = toOne; } } else { @@ -242,6 +249,16 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp } } + private static Object getIfMerged(Object o, MergeContext mergeContext) { + if ( mergeContext != null ) { + final Object merged = mergeContext.get( o ); + if ( merged != null ) { + return merged; + } + } + return o; + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()]; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java index 54871a77f9..243fb06e6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.RootClass; @@ -237,6 +238,11 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif @Override public Object getIdentifier(Object entity) { + return getIdentifier( entity, null ); + } + + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { if ( hasContainingClass() ) { final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entity ); if ( lazyInitializer != null ) { @@ -259,16 +265,16 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif //JPA 2 @MapsId + @IdClass points to the pk of the entity else if ( attributeMapping instanceof ToOneAttributeMapping && !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) { + final Object toOne = getIfMerged( o, mergeContext ); final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( toOneAttributeMapping.getSideNature().inverse() ); if ( targetPart.isEntityIdentifierMapping() ) { - propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( o ); + propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( toOne, mergeContext ); } else { - propertyValues[i] = o; - assert false; + propertyValues[i] = toOne; } } else { @@ -285,6 +291,16 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif } } + private static Object getIfMerged(Object o, MergeContext mergeContext) { + if ( mergeContext != null ) { + final Object merged = mergeContext.get( o ); + if ( merged != null ) { + return merged; + } + } + return o; + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()]; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 5d5265d169..4e9a79f9ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -100,6 +100,7 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; +import org.hibernate.event.spi.MergeContext; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; @@ -4724,6 +4725,11 @@ public abstract class AbstractEntityPersister return identifierMapping.getIdentifier( entity ); } + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { + return identifierMapping.getIdentifier( entity, mergeContext ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { identifierMapping.setIdentifier( entity, id, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index a86e5f133d..05011c3b0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -30,6 +30,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.MergeContext; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; @@ -1130,8 +1131,18 @@ public interface EntityPersister extends EntityMappingType, EntityMutationTarget */ Object getIdentifier(Object entity, SharedSessionContractImplementor session); - /** - * Inject the identifier value into the given entity. + /** + * Get the identifier of an instance from the object's identifier property. + * Throw an exception if it has no identifier property. + * + * It's supposed to be use during the merging process + */ + default Object getIdentifier(Object entity, MergeContext mergeContext) { + return getIdentifier( entity, mergeContext.getEventSource() ); + } + + /** + * Inject the identifier value into the given entity. */ void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session); diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicEntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicEntityIdentifierMapping.java index d1f65dc0b8..f2ee788ff6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicEntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicEntityIdentifierMapping.java @@ -9,6 +9,7 @@ package org.hibernate.query.derived; import org.hibernate.Incubating; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; @@ -51,6 +52,11 @@ public class AnonymousTupleBasicEntityIdentifierMapping return delegate.getIdentifier( entity ); } + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { + return delegate.getIdentifier( entity, mergeContext ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { delegate.setIdentifier( entity, id, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddedEntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddedEntityIdentifierMapping.java index 64be6083d4..abd8f49da8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddedEntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddedEntityIdentifierMapping.java @@ -12,6 +12,7 @@ import java.util.Set; import org.hibernate.Incubating; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; @@ -70,6 +71,11 @@ public class AnonymousTupleEmbeddedEntityIdentifierMapping extends AnonymousTupl return delegate.getIdentifier( entity ); } + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { + return delegate.getIdentifier( entity, mergeContext ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { delegate.setIdentifier( entity, id, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleNonAggregatedEntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleNonAggregatedEntityIdentifierMapping.java index beee4fecd2..c2d7db73fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleNonAggregatedEntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleNonAggregatedEntityIdentifierMapping.java @@ -14,6 +14,7 @@ import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.MergeContext; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable; @@ -79,6 +80,12 @@ public class AnonymousTupleNonAggregatedEntityIdentifierMapping extends Anonymou return delegate.getIdentifier( entity ); } + + @Override + public Object getIdentifier(Object entity, MergeContext mergeContext) { + return delegate.getIdentifier( entity, mergeContext ); + } + @Override public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { delegate.setIdentifier( entity, id, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index 67fc1cb09c..f508d441da 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -149,7 +149,7 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT final EntityPersister concretePersister = guessEntityPersister( entity, factory ); return concretePersister == null ? null - : concretePersister.getIdentifier( entity, null ); + : concretePersister.getIdentifier( entity, (SharedSessionContractImplementor) null ); } private EntityPersister guessEntityPersister(Object object, SessionFactoryImplementor factory) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index bc6cec2cdd..3426169140 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -284,7 +284,7 @@ public abstract class EntityType extends AbstractType implements AssociationType final EntityPersister concretePersister = getAssociatedEntityPersister( factory ); return concretePersister == null ? null - : concretePersister.getIdentifier( entity, null ); + : concretePersister.getIdentifier( entity, (SharedSessionContractImplementor) null ); } @Override @@ -353,7 +353,7 @@ public abstract class EntityType extends AbstractType implements AssociationType else { final Class mappedClass = persister.getMappedClass(); if ( mappedClass.isAssignableFrom( x.getClass() ) ) { - id = persister.getIdentifier( x, null ); + id = persister.getIdentifier( x, (SharedSessionContractImplementor) null ); } else { id = x; @@ -387,7 +387,7 @@ public abstract class EntityType extends AbstractType implements AssociationType } else { if ( mappedClass.isAssignableFrom( x.getClass() ) ) { - xid = persister.getIdentifier( x, null ); + xid = persister.getIdentifier( x, (SharedSessionContractImplementor) null ); } else { //JPA 2 case where @IdClass contains the id and not the associated entity @@ -402,7 +402,7 @@ public abstract class EntityType extends AbstractType implements AssociationType } else { if ( mappedClass.isAssignableFrom( y.getClass() ) ) { - yid = persister.getIdentifier( y, null ); + yid = persister.getIdentifier( y, (SharedSessionContractImplementor) null ); } else { //JPA 2 case where @IdClass contains the id and not the associated entity @@ -564,7 +564,7 @@ public abstract class EntityType extends AbstractType implements AssociationType id = lazyInitializer.getInternalIdentifier(); } else { - id = persister.getIdentifier( value, null ); + id = persister.getIdentifier( value, (SharedSessionContractImplementor) null ); } result.append( '#' ) diff --git a/hibernate-core/src/test/java/org/hibernate/engine/internal/StatisticalLoggingSessionEventListenerTest.java b/hibernate-core/src/test/java/org/hibernate/engine/internal/StatisticalLoggingSessionEventListenerTest.java index c87ec5b837..e8c9065c69 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/internal/StatisticalLoggingSessionEventListenerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/internal/StatisticalLoggingSessionEventListenerTest.java @@ -1,16 +1,12 @@ /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.engine.internal; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.CoreMessageLogger; @@ -183,7 +179,7 @@ public class StatisticalLoggingSessionEventListenerTest { // Number of lines assertThat( sessionMetricsLog.lines().count() ) .as( "The StatisticalLoggingSessionEventListener should write a line per metric (" - + numberOfMetrics + " lines) plus a header and a footer (2 lines)" ) + + numberOfMetrics + " lines) plus a header and a footer (2 lines)" ) .isEqualTo( numberOfMetrics + 2 ); // Total time long sumDuration = metricList.stream().map( SessionMetric::getDuration ).mapToLong( Long::longValue ).sum();