(Disclaimer: I work for Oracle doing Java EE architecture and other things, but none of my writings here or anywhere else on this site have anything to do with my day job there. In short, as always, these are just the writings of a Java EE hacker using publicly available stuff.)
In the last post, we saw how you could write a portable extension that starts up a JAX-RS runtime after the CDI container is open for business. (I recommend reading all the posts in this series, starting with the first one.)
Here’s the extension again:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright © 2016–2017 Laird Nelson. Released under the terms of the MIT license: https://opensource.org/licenses/MIT | |
*/ | |
package com.serverco; | |
import java.io.IOException; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Type; | |
import java.util.Set; | |
import java.util.concurrent.ExecutionException; | |
import javax.enterprise.context.spi.CreationalContext; | |
import javax.enterprise.inject.event.Observes; | |
import javax.enterprise.inject.spi.AfterDeploymentValidation; | |
import javax.enterprise.inject.spi.Bean; | |
import javax.enterprise.inject.spi.BeanManager; | |
import javax.enterprise.inject.spi.BeforeShutdown; | |
import javax.enterprise.inject.spi.Extension; | |
import org.glassfish.grizzly.http.server.HttpServer; | |
public class HttpServerStartingExtension implements Extension { | |
private volatile HttpServer server; | |
private void startHttpServer(@Observes final AfterDeploymentValidation event, final BeanManager beanManager) { | |
if (beanManager != null && this.server == null) { | |
final HttpServer server = get(beanManager, HttpServer.class); | |
if (server != null) { | |
Runtime.getRuntime().addShutdownHook(new Thread() { | |
@Override | |
public final void run() { | |
shutdown(server); | |
} | |
}); | |
if (!server.isStarted()) { | |
try { | |
server.start(); | |
} catch (final IOException ioException) { | |
event.addDeploymentProblem(ioException); | |
} | |
} | |
this.server = server; | |
} | |
} | |
} | |
private void shutdownHttpServer(@Observes final BeforeShutdown event, final BeanManager beanManager) { | |
if (this.server != null) { | |
try { | |
// You should really run this thing on another thread; | |
// join()ing here just for expediency | |
Thread.currentThread().join(); | |
} catch (final InterruptedException interruptedException) { | |
Thread.currentThread().interrupt(); | |
} | |
shutdown(server); | |
} | |
} | |
private static final <T> T get(final BeanManager beanManager, final Type c, final Annotation… qualifiers) { | |
T returnValue = null; | |
if (beanManager != null && c != null) { | |
final Set<Bean<?>> beans = beanManager.getBeans(c, qualifiers); | |
if (beans != null && !beans.isEmpty()) { | |
final Bean<?> bean = beanManager.resolve(beans); | |
assert bean != null; | |
final CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean); | |
assert creationalContext != null; | |
@SuppressWarnings("unchecked") | |
final T reference = (T)beanManager.getReference(bean, c, creationalContext); | |
returnValue = reference; | |
} | |
} | |
return returnValue; | |
} | |
private static final void shutdown(final HttpServer server) { | |
if (server != null && server.isStarted()) { | |
try { | |
server.shutdown().get(); | |
} catch (final ExecutionException ignore) { | |
} catch (final InterruptedException interruptedException) { | |
Thread.currentThread().interrupt(); | |
} | |
} | |
} | |
} |
For this extension to work, though, line 34 has to return a non-null HttpServer. As you can see, there’s nothing in this extension that makes such a thing. The extension is, fortunately, relatively fault-tolerant (or at least that’s the idea 😀), so if no such HttpServer is around, then the extension should just silently do nothing.
In CDI in general, you write things so that the supplying of an object you need is SEP (someone else’s problem). That is, quite apart from the mechanics of injection and so on, the important part of dependency injection is that if you need something, you just presume that it will be handed to you. So here we presume that somehow, some way, an HttpServer will be available in the CDI container. If it turns out that no such object exists, well, OK, we just do no harm.
So how could an HttpServer get in to the container so that it would be picked up by this extension? Well, it could be a bean itself. Maybe we’ll get lucky? But probably not, as the Javadoc shows. Sure enough, although HttpServer has a zero argument constructor, and hence could be a CDI bean, there are no further injection points on it, and furthermore the Grizzly module of which it is a part does not feature a META-INF/beans.xml descriptor, suggesting, though not proving, that we’re not going to get lucky. So HttpServer is technically speaking a valid CDI bean, but a pretty limited one if discovered or added programmatically to a CDI container as-is.
How else could it get in there? Well, there could be a producer method. This seems like a good way to go. A producer method makes bean instances out of raw materials available in the CDI container. That sounds like exactly what we want. So let’s say that ServerCo, writes one to make an HttpServer using the features of the GrizzlyHttpServerFactory class present in Jersey. Let’s say it looks like this, and, just for kicks, lives in a different jar file than the one that houses the portable extension:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2016 Laird Nelson. Released under the terms of the MIT license: https://opensource.org/licenses/MIT | |
*/ | |
package serverco; | |
import java.net.URI; | |
import java.net.URISyntaxException; | |
import javax.enterprise.context.ApplicationScoped; | |
import javax.enterprise.context.Dependent; | |
import javax.enterprise.inject.CreationException; | |
import javax.enterprise.inject.Instance; | |
import javax.enterprise.inject.Produces; | |
import javax.ws.rs.core.Application; | |
import org.glassfish.grizzly.http.server.HttpServer; | |
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer; | |
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; | |
import org.glassfish.jersey.server.ContainerFactory; | |
@ApplicationScoped | |
class Producers { | |
private Producers() { | |
super(); | |
} | |
@Produces | |
@Dependent | |
private static final GrizzlyHttpContainer produceGrizzlyHttpContainer(final Instance<Application> applicationInstance) { | |
final GrizzlyHttpContainer returnValue; | |
if (applicationInstance == null || applicationInstance.isUnsatisfied()) { | |
returnValue = null; | |
} else { | |
returnValue = ContainerFactory.createContainer(GrizzlyHttpContainer.class, applicationInstance.get()); | |
} | |
return returnValue; | |
} | |
@Produces | |
@Dependent | |
private static final HttpServer produceHttpServer(final Instance<GrizzlyHttpContainer> handlerInstance) { | |
final HttpServer returnValue; | |
if (handlerInstance == null || handlerInstance.isUnsatisfied()) { | |
returnValue = null; | |
} else { | |
URI uri = null; | |
try { | |
uri = new URI("ignored", null /* no userInfo */, "localhost", 80, null, null /* no query */, null /* no fragment */); | |
} catch (final URISyntaxException uriSyntaxException) { | |
throw new CreationException(uriSyntaxException); | |
} | |
returnValue = GrizzlyHttpServerFactory.createHttpServer(uri, handlerInstance.get(), false, null, false); | |
} | |
return returnValue; | |
} | |
} |
Let’s look at line 46 above. This is a producer method that creates an HttpServer if one is needed. To do so, it requires a GrizzlyHttpContainer, but such a thing might not exist. To express this kind of optional behavior, it requests that an Instance<GrizzlyHttpContainer> be supplied to it. The CDI container will make such a thing available whether its underlying “payload” exists or not, so we can test things about it here. See line 48: if the Instance is unsatisfied—that is, if there isn’t a GrizzlyHttpContainer in the CDI container anywhere, nor a means for one to be synthesized or manufactured—then we can return null here, and the portable extension we saw earlier will effectively quietly become one big no-op.
On the other hand, starting at line 51, if there is a GrizzlyHttpContainer we can work with, well, then, it’s a pretty simple matter to use it to construct a new HttpServer (line 57).
Very cool. OK, so where does the GrizzlyHttpContainer come from?
That’s the purpose of the first producer method, that you can see at line 34. That method says how a GrizzlyHttpContainer can be created, provided that someone supplies a javax.ws.rs.core.Application object. Obviously, if no such object is available in the CDI container then the method should effectively quietly do nothing, so you can see at lines 36 and 37 that’s what happens.
OK, so clearly the first producer method makes something that the second producer method needs. And the first producer method works if there’s an Application. And the portable extension consumes the output of the second producer method and uses it to start a server when the container comes up.
So where does the Application come from?
Let us turn back to our poor developer, who, you recall from my last post, was done. She had written a JAX-RS application and a root resource class and—right at that point—wanted to be finished. She simply wanted some magic to happen that would let her application start. She didn’t want to write a main method. She didn’t want to go start an application server and run some complicated deployment recipe.
Recall also that if you’re keeping track we have several notional jar files (or directory locations—classpath roots, really) that we’ve stuck in fictional corners. We have:
- A CDI 2.0 EDR2 implementation (like weld-se-core version 3.0.0.Alpha17 or later)
- the developer’s classpath root, containing nothing but her Application subclass and her root resource class
- ServerCo’s portable extension jar file described in Part 2
- A classpath root that contains the boilerplate main class that brings a CDI container up and shuts it down, described in my first post
- ServerCo’s jar file containing the producer methods noted above
What’s really neat is if you run this:
java -classpath /path/to/weld-se-core-3.0.0.Alpha17.jar:/path/to/weld-se-core/dependencies:/path/to/developer/code:/path/to/serverco/portable-extension-1.0.jar:/path/to/boilerplate/code:/path/to/serverco/producers-1.0.jar:/path/to/serverco/dependencies com.foobar.Main
…then I hope to have shown that you will get an HTTP endpoint up and running on localhost port 80 that runs our developer’s JAX-RS application.