| [//]: # " " |
| [//]: # " Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. " |
| [//]: # " " |
| [//]: # " This program and the accompanying materials are made available under the " |
| [//]: # " terms of the Eclipse Public License v. 2.0, which is available at " |
| [//]: # " http://www.eclipse.org/legal/epl-2.0. " |
| [//]: # " " |
| [//]: # " This Source Code may also be made available under the following Secondary " |
| [//]: # " Licenses when the conditions for such availability set forth in the " |
| [//]: # " Eclipse Public License v. 2.0 are satisfied: GNU General Public License, " |
| [//]: # " version 2 with the GNU Classpath Exception, which is available at " |
| [//]: # " https://www.gnu.org/software/classpath/license.html. " |
| [//]: # " " |
| [//]: # " SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 " |
| [//]: # " " |
| |
| * TOC |
| {:toc} |
| |
| # 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: |
| |
| ```xml |
| <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. |
| |
| ```java |
| 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][xmlidentifier] and [Hk2XmlPreGenerate][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][contract] is the standard HK2 annotation that denotes that this interface should be included when |
| doing automatic service analysis. |
| |
| [XmlIdentifier][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][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][hk2xmlpregenerate] annotation is NOT put on the interface class then the |
| proxy will be generated dynamically at runtime, so using the [Hk2XmlPreGenerate][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: |
| |
| ```java |
| 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. |
| |
| ## [XmlService][xmlservice] |
| |
| Now we need to parse our XML into these Java Beans. To do this we use the [XmlService][xmlservice]. |
| You add the XmlService to any locator by using the method enableXmlService in |
| [XmlServiceUtilities][xmlserviceutilities]. |
| |
| The tests cases for this example use the [HK2Runner][hk2runner], which allows the test |
| case itself (WebServerXmlTest.java) to directly inject the XmlService after the [HK2Runner][hk2runner] |
| has been initialized. The following junit @Before block initializes the [HK2Runner][hk2runner] |
| and then initializes the [XmlService][xmlservice] using the [XmlServiceUtilities][xmlserviceutilities]. |
| |
| ```java |
| @Before |
| public void before() { |
| initialize(); |
| |
| XmlServiceUtilities.enableXmlService(testLocator); |
| } |
| ``` |
| |
| The testLocator is from the [HK2Runner][hk2runner] and is available after the initialize call, |
| which is also responsible for injecting the [XmlService][xmlservice] provider so that it |
| can be used by the individual tests: |
| |
| ```java |
| @Inject |
| private Provider<XmlService> xmlServiceProvider; |
| ``` |
| |
| We can now look at how to unmarshall the XML into the ApplicationBean in the first test |
| in WebServerXmlTest: |
| |
| ```java |
| @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][xmlservice] generated an [XmlRootHandle][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][xmlroothandle]. There are two other ways to get the data from your XML file |
| into your application. They are: |
| |
| 1. The individual Java Beans are put into the HK2 Service registry and so can be used like normal services |
| 2. You can use the [ConfiguredBy][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][xmlservice] unmarshall method and the interfaces |
| in the Java Bean tree are marked with [Contract][contract] then those beans will be |
| added to the HK2 service registry. If the Java Bean has a field marked with either |
| [XmlIdentifier][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: |
| |
| ```java |
| @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. |
| |
| [xmlservice]: apidocs/org/glassfish/hk2/xml/api/XmlService.html |
| [xmlserviceutilities]: apidocs/org/glassfish/hk2/xml/api/XmlServiceUtilities.html |
| [hk2runner]: apidocs/org/jvnet/hk2/testing/junit/HK2Runner.html |
| [xmlroothandle]: apidocs/org/glassfish/hk2/xml/api/XmlRootHandle.html |
| [configuredby]:apidocs/org/glassfish/hk2/configuration/api/ConfiguredBy.html |
| [contract]: apidocs/org/jvnet/hk2/annotations/Contract.html |
| [xmlidentifier]: apidocs/org/glassfish/hk2/xml/api/annotations/XmlIdentifier.html |
| [hk2xmlpregenerate]: apidocs/org/glassfish/hk2/xml/api/annotations/Hk2XmlPreGenerate.html |