HK2 One-Pager

1. Introduction

1.1. Project/Component Working Name:

Hk2 for the Hundred Kilobytes Kernel

1.2. Name(s) and e-mail address of Document Author(s)/Supplier:

Jerome Dochez

Name: jerome.dochez@sun.com

1.3. Date of This Document:

12/17/08

2. Project Summary

2.1. Project Description:

Hk2 is aimed at providing the basic infrastructure for writing server side software. It provides the following features :

  1. Modular subsystem abstraction : Hk2 provides classes and interfaces aimed at isolating code from directly depending on a modular subsystem like OSGi. This allow replacement of full fledge modular subsystem with mock up implementations to run in static environment.
  2. Service Based Lookup : Provides hooks and abstraction to help developers write code using a service based design pattern by defining a simple component model with dependency injection capability.
  3. Configuration loading and writing : Coupled with the component model, hk2 can read and write configuration and bind it directly to specific components.

2.2. Risks and Assumptions:

It is assumed that hk2 can run only on JDK 5 and above.

3. Problem Summary

3.1. Problem Area:

Writing modular code is complicated, it requires to understand the code path and dependencies of your software. On top of that, resolving dependencies and wiring code parts together would quickly become tedious without a serious infrastructure to help the developer's productivity. HK2 is aimed at providing such infrastructure, a simple component based infrastructure offering module resolution, services lookup and wiring while concentrating on java code only.

3.2. Justification:

Without the HK2 infrastructure, the V3 codebase and modules writing would be a lot more complicated for the average programmer resulting in a loss of productivity.

4. Technical Description:

4.1. Details:

4.1.1 Hk2 Modules

The first feature offered by Hk2 is an abstraction of the module subsystem will allow for its replaceability or absence through a mock up implementation. This abstraction is not aimed at providing all the features that a sophisticated module subsystem would offer but enough to write a component based modular application without tights to the underlying modular APIs.

The features offered are in threw folds :

  • Module description

Hk2 will provide an abstraction for describing module, versions and dependencies between modules. Such dependency will use metadata from the underlying module delivery (eg a jar file) to present such information in a portable manner. Such abstraction is implemented in the com.sun.enterprise.module package of the hk2-core module. Classes like ModuleDefinition, ModuleDependency represent the basic interfaces for module metadata.

  • Repository Management

Hk2 provides a simple repository management allowing modules to be discovered, added, removed as well as providing a abstraction of the underlying file system or network used to procure modules to the runtime.

  • Startup infrastructure.

Some effort has been spent to allow starting an executable with our without a module subsystem, using classes in the com.sun.enterprise.module.bootstrap package.

All the code related to these features is documented at Module APIS

4.1.2 Component model

A simple Component Model has been developed to help developers being more productive and focus on their code rather than decorations like META-INF/services files. In order to achieve plugability in a modular environment, we use services, where clients can lookup services implementation in a central location called the Habitat.

In order to define a new type of service, developer need to annotate an abstract class or interface with the Contract annotation. Implementations of such contracts are called services and such concrete classes need to be annotated with the Service annotation.

Simplest example of this concept is this contract definition

@Contract
public interface Greetings { 
    public String sayHello();
}

and the implementation

@Service
public class EnFrancais implements Greetings {
   public String sayHello() {
      return "bonjour";
   }
}

Contracts and implementation do not need to reside in the same module, however when they are packaged in separate modules, care must be exercised that all clients and implementations of the contract use the same module's classloader to load the contract class. This is usually ensure by setting the dependencies correctly so that only one API/SPI module is loaded.

4.1.3 Dependency

Once a network of contracts and services exist, it's obvious that some services implementation will require other services to function properly, such higher level services which are called Composite Services can be assembled automatically by the system using dependency injection. Service dependency is identified in the composite definition using the Inject annotation. The annotation optional attribute can be used to make the service resolution mandatory or not. a name can also be used to disambiguate different implementations of a contract.

public class Diplomacy {
    @Inject
    Greetings greetings;
}

Sometimes, it is interesting to lazily reference other services because the service resolution might be expensive and should be deferred until it is used or because the service use may be only necessary under certain conditions. To achieve that, developers can either use the Habitat or they can inject a Holder pattern :

public class Diplomacy {

    @Inject
    Holder<Greetings> greetings;

    public void introduction() {
        greetings.get().sayHello();
    }
}

4.1.4 Configuration support

Building on the component model, another managed component are the configured objects. Configured object represent configuration that will be used by the server, it is usually store in an xml file (like the domain.xml in glassfish). Such configuration is described using interfaces definition that are binding interfaces (like JAXB interfaces) using annotations in the Config module. An interface is annotated with the @Configured annotation, and xml attributes can be described using the @Attribute annotation while xml sub elements are defined with a @Element annotation.

Example :

@Configured
public interface SomeConfig extends ConfigBeanProxy {

    @Attribute
    String getAttributeName();

    @Element
    SubConfig getSub();
}

SubConfig is another interface annotated with the @Configured annotation.

All of these interfaces are actually implemented by only one object in hk2, called the ConfigBean. This class which most developers will never see or have access is responsible for binding the interface definition to underlying XML fragments using the Stax parser.

Annotated @Attribute can optionally specify a target data type, a default value, wether their value is used a key or index in the resulting xml file, and if it is optional or not. Complete reference is Here

Clients of the @Configured interfaces can use injection like any other hk2 components, they injected type will be @Configured interface type. Hk2 will create Java SE proxies for these interfaces which are implemented by the ConfigBean class described above. Therefore the access to the configuration data is completely typed.

Example :

@Service
public class Diplomacy {

    @Inject
    SomeConfig myConfig;

    public void foo { 
       String configStr  = myConfig.getAttributeName();
       SubConfig subConfig = myConfig.getSub();
    }
}

4.1.6 Configuration Views

Because the configuration is represented with plain Java interfaces, it's pretty easy using the JDK proxy facilities to front end these interfaces with different implementations. In hk2, such implementations are called Views. Although most developers will never have to knowingly switch views, there are 3 types of views currently available, also remember that this is an extensible model, new Views can be added if necessary.

  1. TranslatedView

This is the default view people get when injected with configured components. This view allow for a translated access to the configuration data, translation mean that configuration data of the form ${com.sun.aas.instanceRoot} will be translated using the current value for that system property. The translation is done through a service lookup hence it's also pluggable. Note that this view will only give you access to get accessors of the configured data, you cannot mutate in any way the configuration using a TranslatedView, this is essentially a read-only view.

  1. RawView

RawView is still a read-only view, however translation will not be applied therefore attributes with a system property will be returned as is. This is useful when people want to have access to the raw configuration rather than its meaning.

  1. WritableView

WriteableView can only be created by the system (see Transaction below) and will give access to the set accessors making it possible to mutate the underlying configuration. WriteableViews are meant to be short lived objects allowing code to modify configuration within the semantics of a transaction.

4.1.5 Transaction support

As mentioned above, in order to mutate configuration, code has to start a Transaction within which he is guaranteed that all or none of his changes will be applied. By no means, transactions are meant to follow the ACID standards, this is conceptually just an object locking mechanism to ensure that concurrent access to the same configuration data is impossible.

For example, let's take the following @Configured interface

@Configured
public interface SomeConfig {

    @Attribute
    public String getAttr();
    public void setAttr(String attr);
}

Now let's imagine some code needs to change the value of "attr", the low level APIs (which nobody uses looks like this)

@Inject
    SomeConfig target;

    Transaction t = new Transaction();
    WritableView<SomeConfig> wTarget = ConfigSupport.getWritableView(target);
    wTarget.joint(t);

    wTarget.setAttr("New Value");
    try { 
        t.commit();
        // it passed.
    } catch(TransactionException e) {
        // it failed
    }

However using some simpler pattern, the code can be reduced to :

@Inject
    SomeConfig target;

    ConfigSupport.apply(new SingleConfigCode<SomeConfig>() {
         public Object run(SomeConfig wTarget) {
             wTarget.setAttr("Some Value");
         } 
    }

Each transaction is then synchronously processed by the transaction manager where each object is validated then the new configuration is written to the configuration file ensuring the following :

  1. all or none of the configuration will succeed.
  2. the configuration file will be written synchronously ensure than several concurrent transactions cannot threaten the configuration file integrity.
  3. several objects can be part of the same transactions, all these objects modification will be applied as one single transaction.
  4. while in a transaction, developer is guaranteed than no other transaction on the same objects can be started, the lock on the object is exclusive, however the transaction system will allow concurrent modification of unrelated configuration data.

Code can register interest in changes so it can veto such changes before the transaction is committed. Such veto will make the transaction rollback and all related changes will be discarded. After the transaction is successfully committed, transaction events are sent to notify of such changes but those events are informational only, no rollback is possible at that point.

4.1.6 Transaction Events

Transaction success will trigger a number of events to be sent to listeners. Listeners are notified (so far asynchronously but a synchronous mode will be added and will become the default to be compatible with v2). Such events are just plain java beans events delivered to implementors of the ConfigListener interface.

4.1.5 Validation

Validation is also possible within the semantics of a transaction, validator are called as part of the transaction commit of they have a chance to cause the transaction failure if the invariants are not respected.

@Configured(validator=SomeConfigValidator.class)
public interface SomeConfig extends ConfigBean {

    @Attribute
    public String getAttr();
    public String setAttr();
}

and the validator looks like :

@Service
public class SomeConfigValidator implements Validator<SomeConfig> {

    public void validate(SomeConfig target) throws PropertyVetoException {
        if (!target.getAttr().matches(myRegex)) {
           throw new PropertyVetoException("Value is not a US-phone number");
        }
    }
}

4.1.6 Extensibility

The configuration is extensible through some predefined hooks (complete list tbd), external users or extensions can leverage such hooks to have their configuration automatically added to the configuration tree.

@Configured
public interface Config {

    /**
     * list of extension container configuration, will be replicated automatically following
     * glassfish clustering rules.
     */
    @Element("*")
    public List<Container> containers;
}

An extension can then define is configuration with the following pattern :

@Configured
public interface MyContainerConfig extends Container {

    @Attribute 
    public int threadPoolSize()
}

The resulting domain.xml will then look like that

<domain>
    <configs>
       ....
       <config>
            <my-container-config thread-pool-size=10/>
       </config>
       ....
    </config>
</domain>

4.1.6 Generic command support

It will be possible to attach generic command support to add, list or delete elements of the configuration. To trigger command generation, code should be annotated with @Operations

@Configured
public interface Http-Service extends ConfigBeanProxy {

    @Element
    @Operations(create="create-http-listener, delete="delete-http-listener, list="list-http-listener")
    public List<Http-Listener> getHttpListeners();
}

Parameters for the different commands will follow this simple rules :

  • scoping : overall scoping will be used with --domain --config to identify which configuration the command is applying to.
  • create : all @Attribute annotated definition of the created Configured object will be used as a parameter to the command, the presence of a default-value on the attribute makes the attribute an optional command parameter.
  • delete : the @Attribute denoted with the key annotation attribute will be used to identify the element
  • list : no parameters so far. (future : --detailLevel ?)

4.2.5 OSGi mapping

Singleton hk2 services will be registered in the OSGi service registry for availability to plain OSGi bundles. Conversely, the OSGi services are available in the Habitat as singleton services and can be used/injected like any other HK2 service.

4.2. Bug/RFE Number(s):

none

4.3. In Scope:

4.4. Out of Scope:

4.5. Interfaces:

4.5.1 Exported Interfaces

All exported interfaces are tagged as Evolving

Modules infrastructure classes

Component classes

Configuration classes

4.5.2 Imported interfaces

JDK 5 dependencies :

<groupId>javax.xml.stream</groupId>
            <artifactId>stax-api</artifactId>
            <version>1.0-2</version>
            <scope>provided</scope>

All JDK dependencies :

<groupId>com.sun.enterprise</groupId>
            <artifactId>tiger-types-osgi</artifactId>
            <version>$\{project.version}</version>

OSGi mapping service (optional, used in GlassFish) :

<dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi_R4_core</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi_R4_compendium</artifactId>
            <version>1.0</version>
            <scope>provided</scope>
        </dependency>

4.5.3 Other interfaces (Optional)

4.6. Doc Impact:

Extensibility guide, GlassFish developer's guide.

4.7. Admin/Config Impact:

Extensively, HK2 is implementing the low level binding between the repository (domain.xml) and memory, using the stax interfaces and a set of generic objects based on annotations described in 4.1.3

Identify changes to GUIs, CLI, agents, plugins...
none

4.8. HA Impact:

none

4.9. I18N/L10N Impact:

none

4.10. Packaging & Delivery:

What packages, clusters or metaclusters does this proposal impact? What is its impact on install/upgrade?

4.11. Security Impact:

How does this proposal interact with security-related APIs or interfaces? Does it rely on any Java policy or platform user/permissions implication? If the feature exposes any new ports, Or any similar communication points which may have security implications, note these here.

4.12. Compatibility Impact

Incompatible changes to interfaces that others expect to be stable may cause other parts of application server or
other dependent products to break.

Discuss changes to the imported or exported interfaces.Describe how an older version of the interface would
be handled.

List any requirements on upgrade tool and migration tool.

4.13. Dependencies:

List all dependencies that this proposal has on other proposals, components or products. Include interface
specifics above in the interfaces section; LIST dependency component version requirements here.

5. Reference Documents:

List of related documents, if any (BugID's, RFP's, papers).

Explain how/where to obtain the documents, and what each contains, not just their titles.

6. Schedule:

6.1. Projected Availability:

Dates in appropriate precision (quarters, years)