Package com.yahoo.jdisc.application


package com.yahoo.jdisc.application

Provides classes and interfaces for implementing an Application.

Application

In every jDISC process there is exactly one Application instance, it is created during jDISC startup, and it is destroyed during jDISC shutdown. The Application uses the ContainerBuilder interface to load OSGi Bundles, install Guice Modules, create and start ServerProviders, inject a BindingSetSelector, and configure BindingSets with RequestHandlers and ClientProviders. Once the ContainerBuilder is appropriately configured, it is passed to the local ContainerActivator to perform an atomic switch from current to new Container.

@Inject
MyApplication(ContainerActivator activator) {
    ContainerBuilder builder = activator.newContainerBuilder();
    builder.guiceModules().install(new MyBindings());
    Bundle bundle = builder.osgiBundles().install("file:$VESPA_HOME/lib/jars/jdisc_http.jar");
    builder.serverProviders().install(bundle, "com.yahoo.disc.service.http.HttpServer");
    builder.serverBindings().bind("http://localhost/admin/*", new MyAdminHandler());
    builder.serverBindings().bind("http://localhost/*", new MyRequestHandler());
    activator.activateContainer(builder);
}

Because the Request owns a reference to the Container that was active on Request- construction, jDISC is able to guarantee that no component is shut down as long as there are pending Requests that can reach them. When activating a new Container, the previous Container is returned as a DeactivatedContainer instance - an API that can be used by the Application to asynchronously wait for Container termination in order to completely shut down components that are no longer required. This activation pattern is used both for Application startup, runtime reconfigurations, as well as for Application shutdown. It allows all jDISC Application to continously serve Requests during reconfiguration, causing no down time other than what the Application itself explicitly enforces.

void reconfigureApplication() {
   (...)
   reconfiguredContainerBuilder.handlers().install(myRetainedClients);
   reconfiguredContainerBuilder.servers().install(myRetainedServers);
   myExpiredServers.close();
   DeactivatedContainer deactivatedContainer = containerActivator.activateContainer(reconfiguredContainerBuilder);
   deactivatedContainer.notifyTermination(new Runnable() {
       void run() {
           myExpiredClients.destroy();
           myExpiredServers.destroy();
       }
   });
}

Application and OSGi

At the heart of jDISC is an OSGi framework. An Application is always packaged as an OSGi bundle. The OSGi technology itself is a set of specifications that define a dynamic component system for Java. These specifications enable a development model where applications are (dynamically) composed of many different (reusable) components. The OSGi specifications enable components to hide their implementations from other components while communicating through common interfaces (in our case, defined by jDISC's core API) or services (which are objects that are explicitly shared between components). Initially this framework is used to load and bootstrap the application from an OSGi bundle specified on deployment, but because it is exposed through the ContainerBuilder interface, an Application itself can load other bundles as required.

The OSGi integration in jDISC adds the following manifest instructions:

X-JDisc-Privileged-Activator
if "true", this tells jDISC that this bundle requires root privileges for its BundleActivator. If privileges can not be provided, this bundle should not be installed. Only the Application bundle and its dependencies can ever be given privileges, as jDISC itself drops its privileges after the bootstrapping step.
X-JDisc-Preinstall-Bundle
a comma-separated list of bundle locations that must be installed prior to this. Because the named bundles are loaded through the same framework, all transitive dependencies are also resolved. This is an extension to the standard OSGi instruction "Require-Bundle" which simply states that this bundle requires another. It is fairly tricky to get this right during integration testing, since dependencies might be part of the build tree instead of being installed on the host. To facilitate this, JDisc will prefix any non-schemed location (e.g. "my_dependency.jar") with the system property "jdisc.bundle.path". This property defaults to the current directory when running inside an IDE, but is set to "$VESPA_HOME/lib/jars/" by the jdisc startup scripts. One may also reference system properties in a bundle location using the syntax "${propertyName}". If the property is not found, it defaults to an empty string.
X-JDisc-Application
the name of the Application class to load from the bundle. This instruction is ignored unless it is part of the first loaded bundle.

One of the benefits of using OSGi is that it provides Classloader isolation, meaning that one bundle can not inadvertently affect the inernals of another. jDISC leverages this to isolate the different implementations of RequestHandlers, ServerProviders, and jDISC's core internals.

The OSGi manifest instruction "X-JDisc-Application" tells jDISC the name of the Application class to inject from the loaded bundle during startup. To this end, it is necessary for the named Application to offer an injection-enabled constructor (annotated with the Inject keyword). At a minimum, an Application typically needs to have the ContainerActivator injected and saved to a member variable. Because of jDISC's additional OSGi manifest instruction "X-JDisc-Preinstall-Bundle", an Application bundle can be built with compile-time dependencies on other OSGi bundles (using the "provided" scope in maven) without having to repack those dependency into the application itself. Unless incompatible API changes are made to 3rd party jDISC components, it should be possible to upgrade dependencies without having to recompile and redeploy the Application.

Application deployment

jDISC allows a single binary to execute any application without having to change the command line parameters. Instead of modifying the parameters of the single application binary, changing the application is achieved by setting a single environment variable. The planned method of deployment is therefore to 1) install the application's OSGi bundle, 2) set the necessary "jdisc.application" environment variable, and 3) restart the package.

$ install myapp_jar
$ set jdisc.application="myapp.jar"
$ restart jdisc

It is the responsibility of the Application itself to create, configure and activate a Container instance. Although jDISC offers an API that allows for- and manages the change of an active Container instance, making the necessary calls to do so is also considered Application logic. When jDISC receives an external signal to shut down, it instructs the running Application to initiate a graceful shutdown, and waits for it to terminate. Any in-flight Requests should complete, and all services will close.

Because jDISC runs as a Daemon it has the opportunity to run code with root privileges, and it can be configured to provide these privileges to an application's initialization code. However, 1) deployment-time configuration must explicitly enable this capability (by setting the environment variable "jdisc.privileged" to "true"), and 2) the application bundle must explicitly declare that it requires privileges (by including the manifest header "X-JDisc-Privileged-Activator" with the value "true"). If privileges are required but unavailable, deployment of the application will fail. Code that requires privileges will never be run WITHOUT privileges, and code that does not explicitly request privileges will never be run WITH privileges. Finally, the code snippet that is run with privileges is separate from the Application class to avoid unintentionally passing privileges to third-party code.

See Also: