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
import java.lang.Override;
public class LatestAndGreatestConnectionProviderImpl
implements ConnectionProvider, Startable, Stoppable, Configurable {
private LatestAndGreatestPoolBuilder lagPoolBuilder;
private LatestAndGreatestPool lagPool;
private boolean available = false;
@Override
public void configure(Map configurationValues) {
// extract our config from the settings map
lagPoolBuilder = buildBuilder( configurationValues );
}
@Override
public void start() {
// start the underlying pool
lagPool = lagPoolBuilder.buildPool();
available = true;
}
@Override
public void stop() {
available = false;
// stop the underlying pool
lagPool.shutdown();
}
@Override
public Connection getConnection() throws SQLException {
if ( !available ) {
throwException(
"LatestAndGreatest ConnectionProvider not available for use" )
}
return lagPool.borrowConnection();
}
@Override
public void closeConnection(Connection conn) throws SQLException {
if ( !available ) {
warn(
"LatestAndGreatest ConnectionProvider not available for use" )
}
if ( conn == null ) {
return;
}
lagPool.releaseConnection( conn );
}
...
}
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
StandardServiceRegistryBuilder builder = ...;
...
builder.addService(
ConnectionProvider.class,
new LatestAndGreatestConnectionProviderImpl()
);
...
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
public class LatestAndGreatestConnectionProviderImplContributor1
implements ServiceContributor {
@Override
public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) {
serviceRegistryBuilder.addService(
ConnectionProvider.class,
new LatestAndGreatestConnectionProviderImpl()
);
}
}
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
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
public class LatestAndGreatestConnectionProviderImplContributor
implements ServiceContributor {
@Override
public void contribute(
standardserviceregistrybuilder serviceregistrybuilder) {
// here we will register a short-name for our service strategy
strategyselector selector = serviceregistrybuilder
.getbootstrapserviceregistry().
.getservice( strategyselector.class );
selector.registerstrategyimplementor(
connectionprovider.class,
"lag"
latestandgreatestconnectionproviderimpl.class
);
}
}
That all allows the application to pick our LatestAndGreatestConnectionProviderImpl
by a short-name.
Example 6. Custom service short-name
StandardServiceRegistryBuilder builder = ...;
...
builder.applySetting( "hibernate.connection.provider_class", "lag" );
...
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
public interface EventPublishingService extends Service {
public void publish(Event theEvent);
}
Example 8. The EventPublishingService
implementation
public class EventPublishingServiceImpl
implements EventPublishingService, Configurable, Startable, Stoppable,
ServiceRegistryAwareService {
private ServiceRegistryImplementor serviceRegistry;
private String jmsConnectionFactoryName;
private String destinationName;
private Connection jmsConnection;
private Session jmsSession;
private MessageProducer publisher;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
public void configure(Map configurationValues) {
this.jmsConnectionFactoryName = configurationValues
.get( JMS_CONNECTION_FACTORY_NAME_SETTING );
this.destinationName = configurationValues
.get( JMS_DESTINATION_NAME_SETTING );
}
@Override
public void start() {
final JndiService jndiService = serviceRegistry
.getService( JndiService.class );
final ConnectionFactory jmsConnectionFactory = jndiService
.locate( jmsConnectionFactoryName );
this.jmsConnection = jmsConnectionFactory.createConnection();
this.jmsSession = jmsConnection.createSession(
true,
Session.AUTO_ACKNOWLEDGE
);
final Destination destination = jndiService.locate( destinationName );
this.publisher = jmsSession.createProducer( destination );
}
@Override
public void publish(Event theEvent) {
publisher.send( theEvent );
}
@Override
public void stop() {
publisher.close();
jmsSession.close();
jmsConnection.close();
}
}
Example 9. An alternative EventPublishingService
implementation
public class DisabledEventPublishingServiceImpl implements EventPublishingService {
public static DisabledEventPublishingServiceImpl INSTANCE =
new DisabledEventPublishingServiceImpl();
private DisabledEventPublishingServiceImpl() {
}
@Override
public void publish(Event theEvent) {
// nothing to do...
}
}
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
public class EventPublishingServiceInitiator
implements StandardServiceInitiator<EventPublishingService> {
public static EventPublishingServiceInitiator INSTANCE =
new EventPublishingServiceInitiator();
public static final String ENABLE_PUBLISHING_SETTING =
"com.acme.EventPublishingService.enabled";
@Override
public Class<R> getServiceInitiated() {
return EventPublishingService.class;
}
@Override
public R initiateService(
Map configurationValues,
ServiceRegistryImplementor registry) {
final boolean enabled = extractBoolean(
configurationValues,
ENABLE_PUBLISHING_SETTING
);
if ( enabled ) {
return new EventPublishingServiceImpl();
}
else {
return DisabledEventPublishingServiceImpl.INSTANCE;
}
}
...
}
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
public class EventPublishingServiceContributor
implements ServiceContributor {
@Override
public void contribute(StandardServiceRegistryBuilder builder) {
builder.addinitiator( eventpublishingserviceinitiator.instance );
// if we wanted to allow other strategies (e.g. a jms
// queue publisher) we might also register short names
// here with the strategyselector. the initiator would
// then need to accept the strategy as a config setting
}
}