XML Configuration Example
This page shows an example of how to use the HK2 XML configuration service in order to inject
values into HK2 services from an XML file.
The example itself is that of a fake application that can boot multiple web servers,
Each web server has three ports it can open: an admin port, a SSL port and a normal HTTP port.
The SSL port and HTTP port can be dynamically changed while the web server is up and running, but the
admin port cannot. Individual web servers can be added and removed at runtime to the
application. Changes made dynamically at runtime can be written out to an XML file in order
to save the current state of the application.
The XML Configuration File
The application is to be configured with an XML file. An example XML file is found in the
src/test/resources directory of the example and is named webserverExample1.xml.
It looks like this:
<application>
<web-server name="Development Server">
<adminPort>8001</adminPort>
<port>8002</port>
<SSLPort>8003</SSLPort>
</web-server>
<web-server name="QA Server">
<adminPort>9001</adminPort>
<port>9002</port>
<SSLPort>9003</SSLPort>
</web-server>
<web-server name="External Server">
<adminPort>10001</adminPort>
</web-server>
</application>
The Example Application
This application will have three web servers started. The properties of each of the servers is defined in the XML file.
The HK2 XML Service uses JAXB to parse XML files. In most JAXB applications the users must supply an
annotated Java concrete class or use the automatically generated implementations JAXB can generate from schema.
However the HK2 XML Service allows the user to use an interface annotated with JAXB annotations (and a few optional
HK2 specific annotations) rather than a concrete Java class. As an example, lets look at the WebServerBean, which
is an interface that is annotated with JAXB annotations. Comments have been removed for brevity.
package org.glassfish.examples.configuration.xml.webserver;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import org.glassfish.hk2.xml.api.annotations.Hk2XmlPreGenerate;
import org.glassfish.hk2.xml.api.annotations.XmlIdentifier;
import org.jvnet.hk2.annotations.Contract;
@Contract
@Hk2XmlPreGenerate
public interface WebServerBean {
@XmlAttribute(required=true)
@XmlIdentifier
public String getName();
public void setName(String name);
@XmlElement
public String getAddress();
public void setAddress(String address);
@XmlElement(defaultValue="1007")
public int getAdminPort();
public void setAdminPort(int adminPort);
@XmlElement(name="SSLPort", defaultValue="81")
public int getSSLPort();
public void setSSLPort(int sslPort);
@XmlElement(defaultValue="80")
public int getPort();
public void setPort(int port);
}
XmlAttribute and XmlElement are standard JAXB annotations that would normally only go onto concrete classes.
XmlIdentifier and Hk2XmlPreGenerate are HK2 extensions that will be
explained later. The HK2 XML service will generate a proxy for this interface, copying over all annotations
(and in some cases making slight modifications to them in order to ensure JAXB can parse the XML properly).
Contract is the standard HK2 annotation that denotes that this interface should be included when
doing automatic service analysis.
XmlIdentifier is an HK2 XML Service annotation that tells the HK2 XML Service that the attribute represented
by the getter or setter can be used as the key for this bean when the bean is a child of another bean.
It is very much like the JAXB annotation XmlID except that in the case of the XmlID the key must be unique
within the scope of the entire document, whereas with XmlIdentifier the scope of uniqueness only needs
to be within the xpath of the stanza.
Hk2XmlPreGenerate is an HK2 XML Service annotation that tells HK2 that the proxy
for this bean should be generated at build time and placed within the JAR being built. This uses the
standard JSR-269 annotation processor and so should work with most build systems including ant, maven,
gradle and others. The only requirement is that the HK2 XML Service jar be in the classpath of the compiler.
Note that if the Hk2XmlPreGenerate annotation is NOT put on the interface class then the
proxy will be generated dynamically at runtime, so using the Hk2XmlPreGenerate annotation
is mainly a matter of runtime performance, since proxy generation can be a heavy operation.
Lets take a look at the ApplicationBean, which has the WebServer bean as a child:
package org.glassfish.examples.configuration.xml.webserver;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.jvnet.hk2.annotations.Contract;
@Contract
@XmlRootElement(name="application")
public interface ApplicationBean {
@XmlElement(name="web-server")
public WebServerBean[] getWebServers();
public void setWebServers(WebServerBean[] webServers);
public void addWebServers(WebServerBean addMe);
public void removeWebServers(String removeMe);
public WebServerBean lookupWebServers(String findMe);
}
The ApplicationBean has a standard JAXB getter and setter for the WebServerBean which is annotated with the
JAXB standard annotation XmlElement. Note that like all JAXB annotations the annotation can be placed
on the setter method or the getter method. Most JAXB annotations can also be placed on fields of concrete
classes, but since this is an interface they are placed on either the getter or setter. The
methods addWebServers, removeWebServers and lookupWebServers are methods that the HK2 XML Service understands
are part of the web-server family of methods and allows the users of the API to add, remove and
locate WebServers. The XmlRootElement annotation is a standard JAXB annotation that is placed on classes
that can be at the root of the XML file.
Note that in this example we chose to use an array type as the return from getWebServers and as the
input to setWebServers but we could have also used a qualified java.util.List.
NOTE: At the current time there is no support for plurals. This is a bug that should be fixed.
Now we need to parse our XML into these Java Beans. To do this we use the XmlService.
You add the XmlService to any locator by using the method enableXmlService in
XmlServiceUtilities.
The tests cases for this example use the HK2Runner, which allows the test
case itself (WebServerXmlTest.java) to directly inject the XmlService after the HK2Runner
has been initialized. The following junit @Before block initializes the HK2Runner
and then initializes the XmlService using the XmlServiceUtilities.
@Before
public void before() {
initialize();
XmlServiceUtilities.enableXmlService(testLocator);
}
The testLocator is from the HK2Runner and is available after the initialize call,
which is also responsible for injecting the XmlService provider so that it
can be used by the individual tests:
@Inject
private Provider<XmlService> xmlServiceProvider;
We can now look at how to unmarshall the XML into the ApplicationBean in the first test
in WebServerXmlTest:
@Test
public void testParseWebServerXmlFile() throws Exception {
XmlService xmlService = xmlServiceProvider.get();
URI webserverFile = getClass().getClassLoader().getResource(EXAMPLE1_FILENAME).toURI();
XmlRootHandle<ApplicationBean> applicationRootHandle =
xmlService.unmarshall(webserverFile, ApplicationBean.class);
ApplicationBean root = applicationRootHandle.getRoot();
WebServerBean webservers[] = root.getWebServers();
Assert.assertEquals(3, webservers.length);
{
WebServerBean developmentServer = webservers[0];
Assert.assertEquals("Development Server", developmentServer.getName());
Assert.assertEquals(8001, developmentServer.getAdminPort());
Assert.assertEquals(8002, developmentServer.getPort());
Assert.assertEquals(8003, developmentServer.getSSLPort());
}
{
WebServerBean qaServer = webservers[1];
Assert.assertEquals("QA Server", qaServer.getName());
Assert.assertEquals(9001, qaServer.getAdminPort());
Assert.assertEquals(9002, qaServer.getPort());
Assert.assertEquals(9003, qaServer.getSSLPort());
}
{
WebServerBean externalServer = webservers[2];
Assert.assertEquals("External Server", externalServer.getName());
Assert.assertEquals(10001, externalServer.getAdminPort());
Assert.assertEquals(80, externalServer.getPort());
Assert.assertEquals(81, externalServer.getSSLPort());
}
}
The XML file has been unmarshalled into the ApplicationBean and into the three children WebServerBeans.
Note that even the default values have been filled into the getPort and setSSLPort methods from
the External Server WebServer, whose values were not specified directly in the XML file.
The XmlService generated an XmlRootHandle which can be used to
get at the root bean of the JavaBean tree.
The example above illustrates one way to get the data from your XML configuration file and
into your application, which is to directly use the root Java Bean that is produced from the
XmlRootHandle. There are two other ways to get the data from your XML file
into your application. They are:
- The individual Java Beans are put into the HK2 Service registry and so can be used like normal services
- You can use the ConfiguredBy scope to have HK2 services whose lifecycle is managed by the Java Bean tree
The following two sections will explain each option.
Unmarshalled Java Beans as HK2 services
When you unmarshall XML with the XmlService unmarshall method and the interfaces
in the Java Bean tree are marked with Contract then those beans will be
added to the HK2 service registry. If the Java Bean has a field marked with either
XmlIdentifier or the standard JAXB XmlID annotation then the service
will be put into the HK2 service registry with that field as its name. So in this
example three services would be put into the Service registry with contract
WebServerBean, each one with a different name ("Development Server",
"QA Server" and "External Server").
The following example has a WebServerManager that injects an [IterableProvider][iterableprovider]
of WebServerBean. The named method of the [IterableProvider][iterableprovider] allows
the WebServerManager to find the correct WebServerBean given the desired name.
Here is the code for the WebServerManager:
@Service
public class WebServerManager {
@Inject
private IterableProvider<WebServerBean> allWebServers;
/**
* Gets the WebServer bean with the given name, or null if
* none can be found with that name
*
* @param name The non-null name of the web server to find in
* the HK2 service registry
* @return the WebServerBean HK2 service with the given name
*/
public WebServerBean getWebServer(String name) {
return allWebServers.named(name).get();
}
}
The test file for this use case is WebServersAsHK2ServicesTest and is mostly the
same as the previous example. In this case the test gets the WebServerManager
service and uses that to query for WebServersBeans as HK2 services from
the WebServerManager. It then validates that the expected values for each
WebServerBean are as expected:
```java
@Test
public void testWebServerBeansAreHK2Services() throws Exception {
XmlService xmlService = xmlServiceProvider.get();
URI webserverFile = getClass().getClassLoader().getResource(EXAMPLE1_FILENAME).toURI();
XmlRootHandle<ApplicationBean> applicationRootHandle =
xmlService.unmarshall(webserverFile, ApplicationBean.class);
WebServerManager manager = testLocator.getService(WebServerManager.class);
Assert.assertNotNull(manager);
{
WebServerBean developmentServer = manager.getWebServer("Development Server");
Assert.assertEquals("Development Server", developmentServer.getName());
Assert.assertEquals(8001, developmentServer.getAdminPort());
Assert.assertEquals(8002, developmentServer.getPort());
Assert.assertEquals(8003, developmentServer.getSSLPort());
}
{
WebServerBean qaServer = manager.getWebServer("QA Server");
Assert.assertEquals("QA Server", qaServer.getName());
Assert.assertEquals(9001, qaServer.getAdminPort());
Assert.assertEquals(9002, qaServer.getPort());
Assert.assertEquals(9003, qaServer.getSSLPort());
}
{
WebServerBean externalServer = manager.getWebServer("External Server");
Assert.assertEquals("External Server", externalServer.getName());
Assert.assertEquals(10001, externalServer.getAdminPort());
Assert.assertEquals(80, externalServer.getPort());
Assert.assertEquals(81, externalServer.getSSLPort());
}
} ```s
Of course a service can always just use javax.inject.Named to inject a specific WebServerBean if
wants to hard-code the name in the code.
An HK2 Service Per web-server XML stanza
In the next example the system will generate a new WebServer for every web-server XML stanza
in the file, and will inject that WebServer with the values found from that stanza.