DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.

Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.

The contents of this file are subject to the terms of either the GNU General Public License Version 2 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html or packager/legal/LICENSE.txt. See the License for the specific language governing permissions and limitations under the License.

When distributing the software, include this License Header Notice in each file and include the License file at packager/legal/LICENSE.txt.

GPL Classpath Exception: Oracle designates this particular file as subject to the "Classpath" exception as provided by Oracle in the GPL Version 2 section of the License file that accompanied this code.

Modifications: If applicable, add the following below the License Header, with the fields enclosed by brackets [] replaced by your own identifying information: "Portions Copyright [year] [name of copyright owner]"

Contributor(s): If you wish your version of this file to be governed by only the CDDL or only the GPL Version 2, indicate your decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version 2] license." If you don't indicate a single choice of license, a recipient has the option to distribute your version of this file under either the CDDL, the GPL Version 2 or to extend the choice of license to its licensees as provided above. However, if you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then the option applies only if the new code is made subject to such option by the copyright holder.

------ Introduction to Configuration ------ Jerome Dochez ------ ---

Most server side software store configuration in xml file, and naturally HK2 offers such support out of the box. However, extensions hooks are provided to save configuration to other means that an xml file as the configuration structure definition is agnostic of the underlying persistence model. For that particular reasons, it was not practical to define our configuration mappings using a technology like JAXB due to its reliance to the xml format.

However, the description of such mappings ressembles the JAXB interfaces definition.

Mapping

The mapping from Java to a persisted configuration is using a tree of plain old Java interfaces definitions that represent the document structure. Each tree node can have attributes which can only be a String or a type that maps directly to a String (with a toString() method and a String parameter based constructor), and can have sub-elements which are tree nodes themselves. Such elements can have various cardinality, like 0|1, 1, 1..n, 0..n.

@Configured

Any interface defining a configuration mapping must be annotated with Configured so the necessary metadata be generated by the HK2 APT plugin.

@Configured
public interface HttpServer extends ConfigBeanProxy {
        // mappings for the http-server element defined here
}

will result in the following xml snippet :

<....>
        <http-server ... />
</...>

ConfigBeanProxy

For performance reasons (faster lookup), all configuration related interfaces ( ** Attributes

Attributes are represented in the configuration definition with the Attribute annotation.

@Configured
public interface HttpServer extends ConfigBeanProxy {

        @Attribute
        String getName();
        void setName(String name);

}

will result in the following xml snippet : +-------------------------------+ .... http-server name="..." ... / /... +-------------------------------+

Elements

Sub elements of a configuration node are represented in the configuration definition with Element annotation.

Let's imagine another configuration mapping :

@Configured 
public interface HttpListener extends ConfigBeanProxy {
        @Attribute
        String getName();
}

now our example can be enhanced with :

@Configured
public interface HttpServer extends ConfigBeanProxy {

        @Attribute
        String getName();
        void setName(String name);
        
        @Attribute 
        String getPort();
        void setPort(String port);

        @Element
        HttpListener getHttpListener();
        void setHttpListener(HttpListener listener);
}

will result in the following xml snippet :

<....>
        <http-server name="..." ... >
                <http-listener name="..." port="8090" .../>
        </http-server-name>
</...>  

Validation

XML validation cannot be performed using normal XML validation tools like a DTD or a schema, mainly because of the extensible nature of server configuration. Since we use a stax parser implementation, it would be possible to add a functionality to hk2 in order to provide XML validation but the feature is not implemented at this point.

To help validation of xml element values, it is possible to use the Validation APIs to decorate the @Configured annotated interfaces. The entire extend of the validation specification is available, from validating attributes to adding Validators for types.

Here is an example of field validation, where we ensure the field is not null and follows particular regex pattern :

   @Configured
   public interface ClusterRef extends ConfigBeanProxy, Injectable, Ref  {

       /**
        * Gets the value of the ref property.
        *
        * @return possible object is
        *         {@link String }
        */
       @Override
       @Attribute(key=true)
       @NotNull
       @Pattern(regexp=NAME_SERVER_REGEX)
       public String getRef();

Transaction

In order to change any configuration elements, one must start a configuration transaction that ensures restricted access to the configuration. The configuration uses a view mechanism to give access to the underlying configuration bean. By default, injected code gets a readable view while code using a transaction to mutate configuration will obtain a writeable view automatically. While a transaction is in effect, other threads will continue seeing the un-modified values through their redable views while threads having access to the writeable view will be able to change the configuration and see the mutated values. As soon as the transaction is successfully commited, the readable views will give access to the changed configuration.

Single configuration object mutation

In the simple case, code needs to mutate one configuration object during a transaction. The simple ConfigSupport.apply method should be used :

    /**
     * Execute some logic on one config bean of type T protected by a transaction
     *
     * @param code code to execute
     * @param param config object participating in the transaction
     * @return list of events that represents the modified config elements.
     * @throws TransactionFailure when code did not run successfully
     */
 public static <T extends ConfigBeanProxy> Object apply(final SingleConfigCode<T> code, T param)
        throws TransactionFailure 
Example

In the example below, let's change our HttpListener port value :

before the transaction is started, the xml looks like :

<....>
        <http-server name="..." ... >
                <http-listener name="..." port="8090".../>
        </http-server-name>
</...>  

the following code is the actually transaction invocation :

@Inject
HttpListener httpListener;

ConfigSupport.apply(new SingleCodeCode<HttpListener>() {
        public Object run(HttpListener wListener) {
                wListener.setPort("8080");
        }, httpListener);       

will have the resulting effect on the xml

<....>
        <http-server name="..." ... >
                <http-listener name="..." port="8080".../>
        </http-server-name>
</...>  

Vetoing changes

Any code can register itself to listen to changes as they happen within a transaction. Such listeners have the ability to raise a PropertyVetoException that will force the pending transaction to be rolled back (all changes to all beans will then be discarded).

Note the code below should be simplified :

        ((ConfigBean) ConfigSupport.getImpl(target)).getOptionalFeature(ConstrainedBeanListener.class).addVetoableChangeListener(this);

Changes notification

Any code that wishes to receive notification of changes of configuration bean can implement the ConfigListener interface.

        public class Foo implements ConfigListener {

            @Inject
            SomeConfigObject firstConfig;

            @Inject
            SomeOtherConfigObject secondConfig;

            // This instance is automatically registered for all the configuration
            // objects that were injected (in this example, firstConfig and secondConfig)
            public UnprocessedChangeEvents changed(PropertyChangeEvent[] changes) {

            }
        }
Manual Registration

Sometimes, it is necessary to manually register a listener to a particular configuration instance that was not injected or it is not interesting to be listening for changes on all the objects that were injected. In this case, the configuration proxy must be unwrapped to access the underlying ConfigBean instance and register the listener using the register/unregister methods.

    ((ConfigBean) ConfigSupport.getImpl(target).registerListener(new ConfigListener() {

            // will be notified when target is changing.
            public UnprocessedChangeEvents changed(PropertyChangeEvent[] changes) {

            }
    });

Extensibility

Like anything in HK2, configuration can be extended.

To extend configuration, you can use normal Java mechanisms like sub-classing. The idea, is to use a super interface definition for the extension point and as a parent type for all the configuration extensions. Let's take an example, suppose you want to add an extension point for all the containers related configuration. The first thing to do is to define the super interface, let's call it Container :

@Configured
public interface Container extends ConfigBeanProxy {

    // this can be an empty interface, or it can contain some
}

Once the super interface is defined, you can add the extension point in the container type, which in my example is a type called Containers :

@Configured
public interface Containers extends ConfigBeanProxy {

    @Element("*")
    List<Container> getContainers();
}

A few things to understand, the presence of "*" as the element is necessary as the extension point does not know all the possible XML element names that can be used under Containers. So the configuration parsing code will use the XML element name with all defined Container sub types to match which type handles the XML element name.

Finally, the extensions themselves can be defined as subclasses of Container :

@Configured
public interface WebContainer extends Container {

    @Attribute("http-port")
    String getHttpPort();
    void setHttpPort(String port);
}

@Configured
public interface EjbContainer extends Container {

    @Attribute("stateless-default-number")
    String getStatelessDefaultNumber();
    void setStatelessDefaultNumber(String port);
}

The resulting XML snippet will be :

containers web-container http-port="8080"/ ejb-container stateless-default-number="10"/ /containers

There are however a few limitations (that could be overcome with some imaginative, reflective code). The current limitations are :

  • Each configuration object can only have one extension point * Each extension point defines the super interface for all possible extensions. * Each extension point must be defined with @Element("*")

Implementation details

Implementation details of the configuration backend can be found here.