| [//]: # " " |
| [//]: # " 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} |
| |
| # Configuration Example |
| |
| This page shows an example of how to use the HK2 configuration service in order to inject |
| values into HK2 services from a properties file. It is designed to show how end users |
| would interact with the system, and as such does not demonstrate how the internals of the |
| configuration system interact with each other. |
| |
| The example itself is of a fake web server, which 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. The web server |
| can also be associated with one or more SSL certificates and private keys, which are themselves |
| configured. In this specific example the web server is associated with two SSL certificates. |
| |
| ## The Web Server |
| |
| Lets first take a look at the Web Server. The Web Server service implements this HK2 [Contract][contract]: |
| |
| ```java |
| @Contract |
| public interface WebServer { |
| /** |
| * Gets the name of this web server |
| */ |
| public String getName(); |
| |
| /** |
| * Opens the admin port, and returns the number |
| * of the port open |
| */ |
| public int openAdminPort(); |
| |
| /** |
| * Opens the SSL port, and returns the number |
| * of the port open |
| */ |
| public int openSSLPort(); |
| |
| /** |
| * Opens the non-SSL port, and returns the number |
| * of the port open |
| */ |
| public int openPort(); |
| |
| /** |
| * Gets the current admin port, or -1 |
| * if the port is not open |
| */ |
| public int getAdminPort(); |
| |
| /** |
| * Gets the current SSL port, or -1 |
| * if the port is not open |
| */ |
| public int getSSLPort(); |
| |
| /** |
| * Gets the current HTTP port, or -1 |
| * if the port is not open |
| */ |
| public int getPort(); |
| |
| /** |
| * Gets the list of certificates that are |
| * used by this web server |
| * |
| * @return A non-null but possibly empty set |
| * of Files pointing to the public certificates |
| * of the web server |
| */ |
| public List<File> getCertificates(); |
| } |
| ``` |
| |
| Several fields of the WebServer are meant to be configured from an external source. In this example that source will be |
| a properties file, which we will look at in detail later. All the configuration properties of the WebServer are encapsulated |
| in a WebServerBean, which you can see here: |
| |
| ```java |
| /** |
| * This bean describes a WebServer |
| */ |
| public class WebServerBean { |
| private String name; |
| private String address; |
| private int adminPort; |
| private int sslPort; |
| private int port; |
| |
| /** |
| * @return the name |
| */ |
| public String getName() { |
| return name; |
| } |
| /** |
| * @param name the name to set |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| /** |
| * @return the address |
| */ |
| public String getAddress() { |
| return address; |
| } |
| /** |
| * @param address the address to set |
| */ |
| public void setAddress(String address) { |
| this.address = address; |
| } |
| /** |
| * @return the adminPort |
| */ |
| public int getAdminPort() { |
| return adminPort; |
| } |
| /** |
| * @param adminPort the adminPort to set |
| */ |
| public void setAdminPort(int adminPort) { |
| this.adminPort = adminPort; |
| } |
| /** |
| * @return the sslPort |
| */ |
| public int getSSLPort() { |
| return sslPort; |
| } |
| /** |
| * @param sslPort the sslPort to set |
| */ |
| public void setSSLPort(int sslPort) { |
| this.sslPort = sslPort; |
| } |
| /** |
| * @return the port |
| */ |
| public int getPort() { |
| return port; |
| } |
| /** |
| * @param sshPort the port to set |
| */ |
| public void setPort(int port) { |
| this.port = port; |
| } |
| } |
| ``` |
| |
| The implementation of the WebServer contract is the WebServerImpl. The WebServerImpl is in the @ConfiguredBy scope, and injects |
| several fields from the WebServerBean. The interesting parts of this code can be seen below. You can find the full source for |
| this code [here][webserverimpl]. |
| |
| ```java |
| @Service @ConfiguredBy("WebServerBean") |
| public class WebServerImpl implements WebServer { |
| @Configured |
| private String name; |
| |
| @Configured |
| private int adminPort; |
| private int openAdminPort = -1; |
| |
| @Configured(dynamicity=Configured.Dynamicity.FULLY_DYNAMIC) |
| private String address; |
| |
| private int sslPort; |
| private int openSSLPort = -1; |
| private int port; |
| private int openPort = -1; |
| |
| private boolean opened = false; |
| |
| /** |
| * These are configured services that can be used to get other |
| * variable information about the WebServer. In this case |
| * it is getting information about the certificates that |
| * this server can use for SSL |
| */ |
| @Inject |
| private IterableProvider<SSLCertificateService> certificates; |
| |
| /** |
| * This method is called to set the port and sshPort. It is guaranteed that |
| * the server will not have these ports open at the time this method is called. |
| * That is guaranteed since the ports are not open until the postConstruct method |
| * is called on boot, and it is only called between the startDynamicConfiguration |
| * and finishDynamicConfiguration methods when a dynamic configuration change is |
| * made |
| * |
| * @param sshPort The sshPort to use |
| * @param port The port to use |
| */ |
| @SuppressWarnings("unused") |
| private void setUserPorts( |
| @Configured(value="SSLPort", dynamicity=Configured.Dynamicity.FULLY_DYNAMIC) int sslPort, |
| @Configured(value="port", dynamicity=Configured.Dynamicity.FULLY_DYNAMIC) int port) { |
| this.sslPort = sslPort; |
| this.port = port; |
| |
| if (opened) { |
| openSSLPort = sslPort; |
| openPort = port; |
| } |
| } |
| |
| @PostConstruct |
| private void postConstruct() { |
| opened = true; |
| } |
| |
| @PreDestroy |
| private void preDestroy() { |
| openPort = -1; |
| openSSLPort = -1; |
| openAdminPort = -1; |
| } |
| |
| // ... (uninteresting code) |
| |
| /* (non-Javadoc) |
| * @see org.glassfish.examples.configuration.webserver.WebServer#getCertificates() |
| */ |
| @Override |
| public List<File> getCertificates() { |
| LinkedList<File> retVal = new LinkedList<File>(); |
| |
| for (SSLCertificateService certService : certificates) { |
| retVal.add(certService.getCertificate()); |
| } |
| |
| return retVal; |
| } |
| } |
| ``` |
| |
| The above implementation of the WebServer contract is an HK2 [Service][service], and it is in the [ConfiguredBy][configuredby] scope. The |
| [ConfiguredBy][configuredby] annotation requires the name of the type upon which instances of the service are based, in this case |
| "WebServerBean". This implies that for every instance of the type "WebServerBean" found in the HK2 management |
| [Hub][hub] a new instance of the WebServerImpl class will be created. Lets now look at the interesting fields and methods of the |
| WebServerImpl. |
| |
| The first configured parameter is a field called "name". In theory there could be several web servers configured, each with a different name. In |
| this example we don't do that, but the ability to do so is there. The admin port is also configured, like this: |
| |
| ```java |
| @Configured |
| private int adminPort; |
| ``` |
| |
| By putting [@Configured][configured] on the field, that tells the HK2 configuration system to look for a getter named getAdminPort |
| on the bean, and to get the value for the adminPort from that property. By the time the postConstruct method of the WebServerImpl |
| has been called it is guaranteed that this field will have been filled in. Notice that the [@Configured][configured] annotation did |
| not take a value here, instead taking the name of the property from the name of the field. In contrast consider this method, which |
| sets the ssl and http ports of the server: |
| |
| ```java |
| private void setUserPorts( |
| @Configured(value="SSLPort", dynamicity=Configured.Dynamicity.FULLY_DYNAMIC) int sslPort, |
| @Configured(value="port", dynamicity=Configured.Dynamicity.FULLY_DYNAMIC) int port) { |
| this.sslPort = sslPort; |
| this.port = port; |
| |
| if (opened) { |
| openSSLPort = sslPort; |
| openPort = port; |
| } |
| } |
| ``` |
| |
| There are several interesting things to notice about this method. One thing is that we had to put @Configured on all the parameters |
| that were to come from beans. If there had been other parameters on this method they would have been filled in with hk2 services |
| like a normal hk2 initializer method. Another interesting thing is to notice that we set the dynamicity of the configured fields |
| to FULLY_DYNAMIC. This means that if either of these two fields change, this method will get called with the new values. The |
| dynamicity value of all @Configured annotated parameters on a method must be the same. One other subtle thing to notice is that |
| unlike a normal HK2 intialization method, this method can now get called AFTER the postConstruct method has been called. In fact, |
| this method works slightly different depending on whether it has been called before or after the postConstruct method. |
| |
| Any HK2 service can also injected into WebServerImpl. This includes other configured services. For example, in the WebServerImpl |
| there is this injection point: |
| |
| ```java |
| @Inject |
| private IterableProvider<SSLCertificateService> certificates; |
| ``` |
| |
| Let us look in detail at the SSLCertificateService to more fully understand the above usage. |
| |
| ## The SSLCertificateService |
| |
| SSLCertificateService is an HK2 service that deals with SSL certificates. This is the full implementation of the service: |
| |
| ```java |
| @Service @ConfiguredBy("SSLCertificateBean") |
| public class SSLCertificateService { |
| @Configured("$bean") |
| private SSLCertificateBean certificateBean; |
| |
| /** |
| * Returns the location of the public certificate |
| * |
| * @return The public certificate for this SSL service |
| */ |
| public File getCertificate() { |
| return certificateBean.getCertificateLocation(); |
| } |
| |
| } |
| ``` |
| |
| Like the WebServerImpl this service is in the [@ConfiguredBy][configuredby] scope. Instances of this service will be created for each instance |
| of type "SSLCertificateBean" found in the [Hub][hub]. Look at the [@Configured][configured] injection point: |
| |
| ```java |
| @Configured("$bean") |
| private SSLCertificateBean certificateBean; |
| ``` |
| |
| In this case the [@Configured][configured] annotation has a value of "$bean". When a [@Configured][configured] annotation has a |
| value of "$bean" it means to inject the whole bean rather than a value from the bean. The code for the SSLCertificateBean will |
| be found below in the discussion about how the property file is translated into beans. Here is the injection point in the WebServerImpl: |
| |
| ```java |
| @Inject |
| private IterableProvider<SSLCertificateService> certificates; |
| ``` |
| |
| This injection point will be able to get all of the configured SSKCertificateService instances created. So if there are two SSLCertificateBeans |
| in the [Hub][hub] then this iterator will return two SSLCertificateServices. |
| |
| ## Configuring the Services |
| |
| This example has a specific configuration that it will read and then modify to ensure that the system is working properly. The initial |
| property file for the example can be found here: |
| |
| ```java |
| WebServerBean.Acme.name=Acme |
| WebServerBean.Acme.address=localhost |
| WebServerBean.Acme.adminPort=7070 |
| WebServerBean.Acme.sslPort=81 |
| WebServerBean.Acme.port=80 |
| |
| SSLCertificateBean.Corporate.certificateLocation=Corporatex509.cert |
| SSLCertificateBean.Corporate.secretKeyLocation=Corporate.pem |
| |
| SSLCertificateBean.HR.certificateLocation=HRx509.cert |
| SSLCertificateBean.HR.secretKeyLocation=HR.pem |
| ``` |
| |
| The way the HK2 Properties interpreter works is that property keys have three parts, which is the type name followed by the instance name |
| followed by the parameter name. So the above properties file means to add an instance of the WebServerBean named "Acme" to the |
| [Hub][hub] with the properties given. Also add two instances of the SSLCertificateBean, one named "Corporate" and one |
| named "HR" also with the given properties. |
| |
| Properties are generally strings, but the HK2 Properties interpreter is able to translate those strings to the types found in the associated |
| JavaBeans. This includes being able to translate the String if the type associated with that parameter name has a public constructor that |
| takes a string. For example, consider the SSLCertificateBean: |
| |
| ```java |
| public class SSLCertificateBean { |
| private File certificateLocation; |
| private File secretKeyLocation; |
| /** |
| * @return the certificateLocation |
| */ |
| public File getCertificateLocation() { |
| return certificateLocation; |
| } |
| /** |
| * @param certificateLocation the certificateLocation to set |
| */ |
| public void setCertificateLocation(File certificateLocation) { |
| this.certificateLocation = certificateLocation; |
| } |
| /** |
| * @return the secretKeyLocation |
| */ |
| public File getSecretKeyLocation() { |
| return secretKeyLocation; |
| } |
| /** |
| * @param secretKeyLocation the secretKeyLocation to set |
| */ |
| public void setSecretKeyLocation(File secretKeyLocation) { |
| this.secretKeyLocation = secretKeyLocation; |
| } |
| } |
| ``` |
| |
| The SSLCertificateBean has File as the type for both the "secretKeyLocation" and "CertificateLocation" parameters. HK2 |
| knows how to translate the strings in the properties file because File has a public constructor that takes a string, which is what |
| it uses to fill in the fields of this bean. |
| |
| In order to read the properties file into the [Hub][hub] this example uses the [PropertyFileService][propertyfileservice]. |
| The [PropertyFileService][propertyfileservice] is used to create [PropertyFileHandle][propertyfilehandle] instances. This |
| is how the test code reads the above property file into the [Hub][hub]: |
| |
| ```java |
| Properties configuration = new Properties(); |
| |
| URL configURL = getClass().getClassLoader().getResource("config.prop"); |
| InputStream configStream = configURL.openConnection().getInputStream(); |
| try { |
| // Read the property file |
| configuration.load(configStream); |
| } |
| finally { |
| configStream.close(); |
| } |
| |
| // In order to read the Properties object into HK2 we need to get a PropertyFileHandle |
| PropertyFileService propertyFileService = locator.getService(PropertyFileService.class); |
| PropertyFileHandle propertyFileHandle = propertyFileService.createPropertyHandleOfAnyType(); |
| |
| // Now read the configuration into hk2 |
| propertyFileHandle.readProperties(configuration); |
| ``` |
| |
| The reason for using a [PropertyFileHandle][propertyfilehandle] is because the handle keeps the state |
| of the property file the last time it was read. In this way if the property file is modified |
| and read in again the propertyFileHandle would know which values had changed, which instances |
| were added and which instances were removed (which is the difficult part of the whole |
| thing!). |
| |
| ## Dynamic changes |
| |
| In this example we simply modify the Properties object itself rather than reading a second file for |
| simplicity. This is the test code that changes the properties and then tells hk2 about the |
| changes: |
| |
| ```java |
| // Change the ports so that they look like this in the properties file: |
| // adminPort = 8082 |
| // sslPort = 8081 |
| // port = 8080 |
| configuration.put("WebServerBean.Acme.adminPort", "8082"); |
| configuration.put("WebServerBean.Acme.sslPort", "8081"); |
| configuration.put("WebServerBean.Acme.port", "8080"); |
| |
| // Tell hk2 about the change |
| propertyFileHandle.readProperties(configuration); |
| ``` |
| |
| The propertyFileHandle will notice the changes and inform the [Hub][hub], which will then notify the backing |
| WebServerImpl about the modified dynamic properties. Even though the adminPort has changed the WebServerImpl |
| will NOT be notified about that parameter changing because that field is NOT marked as being dynamic. |
| |
| ## Initialization and setup |
| |
| In order to use the various parts of the HK2 configuration subsystem they must be initialized. This is |
| done via static methods on helper classes. This is the test initialization code (junit @Before block): |
| |
| ```java |
| // Enable HK2 service integration |
| ConfigurationUtilities.enableConfigurationSystem(locator); |
| |
| // Enable Properties service, to get service properties from a Properties object |
| PropertyFileUtilities.enablePropertyFileService(locator); |
| ``` |
| |
| The [PropertyFileService][propertyfileservice] also needs the mapping from type name to Java Bean class. In order |
| to do this we must configure the [PropertyFileService][propertyfileservice] with a [PropertyFileBean][propertyfilebean]. |
| This is the code that maps the type named "SSLCertificateBean" to the SSLCertificateBean class and the |
| type named "WebServerBean" to the WebServerBean class: |
| |
| ```java |
| // The propertyFileBean contains the mapping from type names to Java Beans |
| PropertyFileBean propertyFileBean = new PropertyFileBean(); |
| propertyFileBean.addTypeMapping("WebServerBean", WebServerBean.class); |
| propertyFileBean.addTypeMapping("SSLCertificateBean", SSLCertificateBean.class); |
| |
| // Add in the mapping from type name to bean classes |
| PropertyFileService propertyFileService = locator.getService(PropertyFileService.class); |
| propertyFileService.addPropertyFileBean(propertyFileBean); |
| ``` |
| |
| The last thing to do in order to setup the environment is to tell HK2 about the backing HK2 services. This is |
| done with a simple call to addClasses: |
| |
| ```java |
| // Add the test services themselves |
| ServiceLocatorUtilities.addClasses(locator, |
| SSLCertificateService.class, |
| WebServerImpl.class); |
| ``` |
| |
| The @Before block of the test code has now setup HK2 for the test run. |
| |
| ## Running the test |
| |
| This is the full test that shows that the WebServerImpl is properly running and gets the proper values from the |
| properties file: |
| |
| ```java |
| /** |
| * This test demonstrates adding and the modifying the http and |
| * ssl ports of the web server |
| */ |
| @Test // @org.junit.Ignore |
| public void testDemonstrateWebServerConfiguration() throws IOException { |
| // Before we add a configuration there is no web server |
| WebServer webServer = locator.getService(WebServer.class); |
| Assert.assertNull(webServer); |
| |
| Properties configuration = new Properties(); |
| |
| // Gets the URL of the configuration property file. This |
| // file contains one web server and two SSL certificate |
| // configuration objects |
| URL configURL = getClass().getClassLoader().getResource("config.prop"); |
| InputStream configStream = configURL.openConnection().getInputStream(); |
| try { |
| // Read the property file |
| configuration.load(configStream); |
| } |
| finally { |
| configStream.close(); |
| } |
| |
| // In order to read the Properties object into HK2 we need to get a PropertyFileHandle |
| PropertyFileService propertyFileService = locator.getService(PropertyFileService.class); |
| PropertyFileHandle propertyFileHandle = propertyFileService.createPropertyHandleOfAnyType(); |
| |
| // Now read the configuration into hk2 |
| propertyFileHandle.readProperties(configuration); |
| |
| // We should now have a web server! |
| webServer = locator.getService(WebServer.class); |
| Assert.assertNotNull(webServer); |
| |
| // Lets open all the ports, and check that they have the expected values |
| // In this case the ports are: |
| // adminPort = 7070 |
| // sslPort = 81 |
| // port = 80 |
| Assert.assertEquals((int) 7070, webServer.openAdminPort()); |
| Assert.assertEquals((int) 81, webServer.openSSLPort()); |
| Assert.assertEquals((int) 80, webServer.openPort()); |
| |
| // Now lets check that we have two SSL certificates |
| List<File> certs = webServer.getCertificates(); |
| |
| // The two certificates should be Corporatex509.cert and HRx509.cert |
| Assert.assertEquals(2, certs.size()); |
| |
| HashSet<String> foundCerts = new HashSet<String>(); |
| for (File cert : certs) { |
| foundCerts.add(cert.getName()); |
| } |
| |
| Assert.assertTrue(foundCerts.contains("Corporatex509.cert")); |
| Assert.assertTrue(foundCerts.contains("HRx509.cert")); |
| |
| // OK, we have verified that all of the parameters of the |
| // webserver are as expected. We are now going to dynamically |
| // change all the ports. In the webserver however only |
| // the ssl and http ports are dynamic, so after the change |
| // only the ssl and http ports should have their new values, |
| // while the admin port should remain with the old value |
| |
| // Change the ports so that they look like this in the properties file: |
| // adminPort = 8082 |
| // sslPort = 8081 |
| // port = 8080 |
| configuration.put("WebServerBean.Acme.adminPort", "8082"); |
| configuration.put("WebServerBean.Acme.sslPort", "8081"); |
| configuration.put("WebServerBean.Acme.port", "8080"); |
| |
| // Tell hk2 about the change |
| propertyFileHandle.readProperties(configuration); |
| |
| // Now lets check the web server, make sure the ports have been modified |
| |
| // The adminPort is NOT dynamic in the back end service, so it did not change |
| Assert.assertEquals(7070, webServer.getAdminPort()); |
| |
| // But the SSL and HTTP ports have changed dynamically |
| Assert.assertEquals(8081, webServer.getSSLPort()); |
| Assert.assertEquals(8080, webServer.openPort()); |
| } |
| ``` |
| |
| ## Final thoughts |
| |
| This example is a simple demonstration of the HK2 configuration subsystem from an end-user perspective. The end-users write |
| HK2 services such as WebServerImpl and SSLCertificateService. They annotate those services with [ConfiguredBy][configuredby] |
| and [Configured][configured]. They can either be injected with parameters from beans or with the entire bean. |
| |
| The mechanism demonstrated in this example used a properties file that was read in using the [PropertyFileService][propertyfileservice]. |
| Any other mechanism can be used to add java beans to the [Hub][hub], making the persistence of the configuration |
| data a pluggable mechanism. Dynamic changes to fields are also supported. More information about the HK2 configuration |
| service can be found [here][configurationdoc]. |
| |
| [webserverimpl]: https://github.com/hk2-project/hk2/blob/master/examples/configuration/webserver/src/main/java/org/glassfish/examples/configuration/webserver/internal/WebServerImpl.java |
| [service]: apidocs/org/jvnet/hk2/annotations/ServiceLocator.html |
| [contract]: apidocs/org/jvnet/hk2/annotations/Contract.html |
| [configuredby]: apidocs/org/glassfish/hk2/configuration/api/ConfiguredBy.html |
| [configured]: apidocs/org/glassfish/hk2/configuration/api/Configured.html |
| [hub]: apidocs/org/glassfish/hk2/configuration/hub/api/Hub.html |
| [propertyfileservice]: apidocs/org/glassfish/hk2/configuration/persistence/properties/PropertyFileService.html |
| [propertyfilehandle]: apidocs/org/glassfish/hk2/configuration/persistence/properties/PropertyFileHandle.html |
| [propertyfilebean]: apidocs/org/glassfish/hk2/configuration/persistence/properties/PropertyFileBean.html |
| [configurationdoc]: configuration.html |