Skip to content

Commit 72bd0e7

Browse files
committed
HHH-19849 Add an SPI that allows attaching session-scoped "extensions" to the session/statelesssession implementors
1 parent 66e6184 commit 72bd0e7

File tree

5 files changed

+132
-0
lines changed

5 files changed

+132
-0
lines changed

hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,16 @@ public RootGraphImplementor<?> getEntityGraph(String graphName) {
517517
return delegate.getEntityGraph( graphName );
518518
}
519519

520+
@Override
521+
public void attachExtension(String extensionName, Object extension) {
522+
delegate.attachExtension( extensionName, extension );
523+
}
524+
525+
@Override
526+
public <T> T retrieveExtension(String extensionName, Class<T> extensionType) {
527+
return delegate.retrieveExtension( extensionName, extensionType );
528+
}
529+
520530
@Override
521531
public <T> QueryImplementor<T> createQuery(CriteriaSelect<T> selectQuery) {
522532
return delegate.createQuery( selectQuery );

hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,4 +621,26 @@ default boolean isStatelessSession() {
621621

622622
@Override
623623
RootGraphImplementor<?> getEntityGraph(String graphName);
624+
625+
/**
626+
* Allows attaching session scoped extensions to the particular session instance they are based on.
627+
*
628+
* @param extensionName The name of the extension serves as a "key" for its retrival from a session instance.
629+
* @param extension The extension to attach to the current session.
630+
*/
631+
@Incubating
632+
void attachExtension(String extensionName, Object extension);
633+
634+
/**
635+
* Returns the extensions attached to the current session.
636+
*
637+
* @param extensionName The name of the extension to retrieve.
638+
* @param extensionType The type of the extension to retrieve.
639+
* @param <T> The type of the extension to retrieve.
640+
* @return The extension instance attached to the current session,
641+
* or {@code null} if there is no extension with the requested name attached to the session.
642+
* @throws ClassCastException if the requested extension cannot be cast to the requested type.
643+
*/
644+
@Incubating
645+
<T> T retrieveExtension(String extensionName, Class<T> extensionType);
624646
}

hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,16 @@ public RootGraphImplementor<?> getEntityGraph(String graphName) {
663663
return delegate.getEntityGraph( graphName );
664664
}
665665

666+
@Override
667+
public void attachExtension(String extensionName, Object extension) {
668+
delegate.attachExtension( extensionName, extension );
669+
}
670+
671+
@Override
672+
public <T> T retrieveExtension(String extensionName, Class<T> extensionType) {
673+
return delegate.retrieveExtension( extensionName, extensionType );
674+
}
675+
666676
@Override
667677
public <T> List<EntityGraph<? super T>> getEntityGraphs(Class<T> entityClass) {
668678
return delegate.getEntityGraphs( entityClass );

hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,10 @@
111111
import java.io.Serial;
112112
import java.sql.Connection;
113113
import java.sql.SQLException;
114+
import java.util.HashMap;
114115
import java.util.List;
115116
import java.util.Locale;
117+
import java.util.Map;
116118
import java.util.Objects;
117119
import java.util.TimeZone;
118120
import java.util.UUID;
@@ -186,6 +188,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
186188
private transient ExceptionConverter exceptionConverter;
187189
private transient SessionAssociationMarkers sessionAssociationMarkers;
188190

191+
private transient Map<String, Object> extensions;
192+
189193
public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) {
190194
this.factory = factory;
191195

@@ -1704,6 +1708,23 @@ public SessionAssociationMarkers getSessionAssociationMarkers() {
17041708
return sessionAssociationMarkers;
17051709
}
17061710

1711+
@Override
1712+
public <T> T retrieveExtension(String extensionName, Class<T> extensionType) {
1713+
if ( extensions != null ) {
1714+
Object extension = extensions.get( extensionName );
1715+
return extension == null ? null : extensionType.cast( extension );
1716+
}
1717+
return null;
1718+
}
1719+
1720+
@Override
1721+
public void attachExtension(String extensionName, Object extension) {
1722+
if ( extensions == null ) {
1723+
extensions = new HashMap<>();
1724+
}
1725+
extensions.put( extensionName, extension );
1726+
}
1727+
17071728
@Serial
17081729
private void writeObject(ObjectOutputStream oos) throws IOException {
17091730
SESSION_LOGGER.serializingSession( getSessionIdentifier() );
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.engine.spi;
6+
7+
import jakarta.persistence.Id;
8+
import org.hibernate.testing.orm.junit.DomainModel;
9+
import org.hibernate.testing.orm.junit.SessionFactory;
10+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
11+
import org.junit.jupiter.api.Test;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
15+
16+
@DomainModel(annotatedClasses = {
17+
SessionExtensionTest.UselessEntity.class,
18+
})
19+
@SessionFactory
20+
public class SessionExtensionTest {
21+
22+
@Test
23+
public void smoke(SessionFactoryScope scope) {
24+
final String extensionName = "my-extension-key";
25+
scope.inSession( sessionImplementor -> {
26+
sessionImplementor.attachExtension( extensionName, new Extension( 1 ) );
27+
28+
assertThat( sessionImplementor.retrieveExtension( extensionName, Extension.class ) )
29+
.isNotNull()
30+
.isEqualTo( new Extension( 1 ) );
31+
} );
32+
33+
scope.inStatelessSession( sessionImplementor -> {
34+
sessionImplementor.attachExtension( extensionName, new Extension( 1 ) );
35+
36+
assertThat( sessionImplementor.retrieveExtension( extensionName, Extension.class ) )
37+
.isNotNull()
38+
.isEqualTo( new Extension( 1 ) );
39+
} );
40+
}
41+
42+
@Test
43+
public void cast(SessionFactoryScope scope) {
44+
final String extensionName = "my-extension-key";
45+
scope.inSession( sessionImplementor -> {
46+
sessionImplementor.attachExtension( extensionName, new Extension( 1 ) );
47+
48+
assertThatThrownBy(
49+
() -> sessionImplementor.retrieveExtension( extensionName, SessionExtensionTest.class ) )
50+
.isInstanceOf( ClassCastException.class );
51+
} );
52+
53+
scope.inStatelessSession( sessionImplementor -> {
54+
sessionImplementor.attachExtension( extensionName, new Extension( 1 ) );
55+
56+
assertThatThrownBy(
57+
() -> sessionImplementor.retrieveExtension( extensionName, SessionExtensionTest.class ) )
58+
.isInstanceOf( ClassCastException.class );
59+
} );
60+
}
61+
62+
private record Extension(int number) {
63+
}
64+
65+
static class UselessEntity {
66+
@Id
67+
Long id;
68+
}
69+
}

0 commit comments

Comments
 (0)