1.5. Custom Services

So far we have focused on the Hibernate provided services. But applications and integrations can provide their own services as well, either

  • providing a new implementation of a standard Service (overriding)

  • providing a whole new Service role (extending)

1.5.1. Custom Service Implementations (overriding)

We discussed swappability of Service implementations above. Lets look at an example in practice. For the sake of illustration, lets say that we have developed a new ConnectionProvider integrating with the wonderful new latest-and-greatest connection pooling library. Let’s look at the steps necessary to make that happen.

The first step is to develop the actual integration by implementing the ConnectionProvider contract.

Example 1. Custom ConnectionProvider implementation

  1. import java.lang.Override;
  2. public class LatestAndGreatestConnectionProviderImpl
  3. implements ConnectionProvider, Startable, Stoppable, Configurable {
  4. private LatestAndGreatestPoolBuilder lagPoolBuilder;
  5. private LatestAndGreatestPool lagPool;
  6. private boolean available = false;
  7. @Override
  8. public void configure(Map configurationValues) {
  9. // extract our config from the settings map
  10. lagPoolBuilder = buildBuilder( configurationValues );
  11. }
  12. @Override
  13. public void start() {
  14. // start the underlying pool
  15. lagPool = lagPoolBuilder.buildPool();
  16. available = true;
  17. }
  18. @Override
  19. public void stop() {
  20. available = false;
  21. // stop the underlying pool
  22. lagPool.shutdown();
  23. }
  24. @Override
  25. public Connection getConnection() throws SQLException {
  26. if ( !available ) {
  27. throwException(
  28. "LatestAndGreatest ConnectionProvider not available for use" )
  29. }
  30. return lagPool.borrowConnection();
  31. }
  32. @Override
  33. public void closeConnection(Connection conn) throws SQLException {
  34. if ( !available ) {
  35. warn(
  36. "LatestAndGreatest ConnectionProvider not available for use" )
  37. }
  38. if ( conn == null ) {
  39. return;
  40. }
  41. lagPool.releaseConnection( conn );
  42. }
  43. ...
  44. }

At this point we have a decision about how to integrate this new ConnectionProvider into Hibernate. As you might guess, there are multiple ways.

As a first option, we might just require that the code bootstrapping the StandardServiceRegistry do the integration.

Example 2. Overriding service implementation via StandardServiceRegistryBuilder

  1. StandardServiceRegistryBuilder builder = ...;
  2. ...
  3. builder.addService(
  4. ConnectionProvider.class,
  5. new LatestAndGreatestConnectionProviderImpl()
  6. );
  7. ...

A second option, if our LatestAndGreatestConnectionProviderImpl should always be used, would be to provide a org.hibernate.service.spi.ServiceContributor implementation as well to handle the integration on the users behalf.

Example 3. LatestAndGreatestConnectionProviderImplContributor

  1. public class LatestAndGreatestConnectionProviderImplContributor1
  2. implements ServiceContributor {
  3. @Override
  4. public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) {
  5. serviceRegistryBuilder.addService(
  6. ConnectionProvider.class,
  7. new LatestAndGreatestConnectionProviderImpl()
  8. );
  9. }
  10. }

We still need to be able to tell Hibernate to perform this integration for us. To do that we leverage Java’s ServiceLoader. When building the StandardServiceRegistry, Hibernate will look for JDK Service providers of type org.hibernate.service.spi.ServiceContributor and automatically integrate them. We discussed this behavior above. Here we’d define a classpath resource named META-INF/services/org.hibernate.service.spi.ServiceContributor. This file will have just a single line naming our impl.

Example 4. META-INF/services/org.hibernate.service.spi.ServiceContributor

  1. fully.qualified.package.LatestAndGreatestConnectionProviderImplContributor1

A third option, if we simply want to make our LatestAndGreatestConnectionProviderImpl available as a configuration choice, we would again use a ServiceContributor but in a slightly different way.

Example 5. LatestAndGreatestConnectionProviderImplContributor variation

  1. public class LatestAndGreatestConnectionProviderImplContributor
  2. implements ServiceContributor {
  3. @Override
  4. public void contribute(
  5. standardserviceregistrybuilder serviceregistrybuilder) {
  6. // here we will register a short-name for our service strategy
  7. strategyselector selector = serviceregistrybuilder
  8. .getbootstrapserviceregistry().
  9. .getservice( strategyselector.class );
  10. selector.registerstrategyimplementor(
  11. connectionprovider.class,
  12. "lag"
  13. latestandgreatestconnectionproviderimpl.class
  14. );
  15. }
  16. }

That all allows the application to pick our LatestAndGreatestConnectionProviderImpl by a short-name.

Example 6. Custom service short-name

  1. StandardServiceRegistryBuilder builder = ...;
  2. ...
  3. builder.applySetting( "hibernate.connection.provider_class", "lag" );
  4. ...

1.5.2. Custom Service Roles (extending)

We can also have the ServiceRegistry host custom services (completely new Service roles). As an example, let’s say our application publishes Hibernate events to a JMS Topic and that we want to leverage the Hibernate ServiceRegistry to host a Service representing our publishing of events. So, we will expand the ServiceRegistry to host this completely new Service role for us and manage its lifecycle.

Example 7. The EventPublishingService service role

  1. public interface EventPublishingService extends Service {
  2. public void publish(Event theEvent);
  3. }

Example 8. The EventPublishingService implementation

  1. public class EventPublishingServiceImpl
  2. implements EventPublishingService, Configurable, Startable, Stoppable,
  3. ServiceRegistryAwareService {
  4. private ServiceRegistryImplementor serviceRegistry;
  5. private String jmsConnectionFactoryName;
  6. private String destinationName;
  7. private Connection jmsConnection;
  8. private Session jmsSession;
  9. private MessageProducer publisher;
  10. @Override
  11. public void injectServices(ServiceRegistryImplementor serviceRegistry) {
  12. this.serviceRegistry = serviceRegistry;
  13. }
  14. public void configure(Map configurationValues) {
  15. this.jmsConnectionFactoryName = configurationValues
  16. .get( JMS_CONNECTION_FACTORY_NAME_SETTING );
  17. this.destinationName = configurationValues
  18. .get( JMS_DESTINATION_NAME_SETTING );
  19. }
  20. @Override
  21. public void start() {
  22. final JndiService jndiService = serviceRegistry
  23. .getService( JndiService.class );
  24. final ConnectionFactory jmsConnectionFactory = jndiService
  25. .locate( jmsConnectionFactoryName );
  26. this.jmsConnection = jmsConnectionFactory.createConnection();
  27. this.jmsSession = jmsConnection.createSession(
  28. true,
  29. Session.AUTO_ACKNOWLEDGE
  30. );
  31. final Destination destination = jndiService.locate( destinationName );
  32. this.publisher = jmsSession.createProducer( destination );
  33. }
  34. @Override
  35. public void publish(Event theEvent) {
  36. publisher.send( theEvent );
  37. }
  38. @Override
  39. public void stop() {
  40. publisher.close();
  41. jmsSession.close();
  42. jmsConnection.close();
  43. }
  44. }

Example 9. An alternative EventPublishingService implementation

  1. public class DisabledEventPublishingServiceImpl implements EventPublishingService {
  2. public static DisabledEventPublishingServiceImpl INSTANCE =
  3. new DisabledEventPublishingServiceImpl();
  4. private DisabledEventPublishingServiceImpl() {
  5. }
  6. @Override
  7. public void publish(Event theEvent) {
  8. // nothing to do...
  9. }
  10. }

Because we have alternative implementations, it is a good idea to develop an initiator as well that can choose between them at runtime.

Example 10. The EventPublishingServiceInitiator

  1. public class EventPublishingServiceInitiator
  2. implements StandardServiceInitiator<EventPublishingService> {
  3. public static EventPublishingServiceInitiator INSTANCE =
  4. new EventPublishingServiceInitiator();
  5. public static final String ENABLE_PUBLISHING_SETTING =
  6. "com.acme.EventPublishingService.enabled";
  7. @Override
  8. public Class<R> getServiceInitiated() {
  9. return EventPublishingService.class;
  10. }
  11. @Override
  12. public R initiateService(
  13. Map configurationValues,
  14. ServiceRegistryImplementor registry) {
  15. final boolean enabled = extractBoolean(
  16. configurationValues,
  17. ENABLE_PUBLISHING_SETTING
  18. );
  19. if ( enabled ) {
  20. return new EventPublishingServiceImpl();
  21. }
  22. else {
  23. return DisabledEventPublishingServiceImpl.INSTANCE;
  24. }
  25. }
  26. ...
  27. }

We could have the application register the EventPublishingServiceInitiator with the StandardServiceRegistryBuilder, but it is much nicer to write a ServiceContributor to handle this for the application.

Example 11. The EventPublishingServiceContributor

  1. public class EventPublishingServiceContributor
  2. implements ServiceContributor {
  3. @Override
  4. public void contribute(StandardServiceRegistryBuilder builder) {
  5. builder.addinitiator( eventpublishingserviceinitiator.instance );
  6. // if we wanted to allow other strategies (e.g. a jms
  7. // queue publisher) we might also register short names
  8. // here with the strategyselector. the initiator would
  9. // then need to accept the strategy as a config setting
  10. }
  11. }