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.
-
ClassDescriptionThis class is a convenient parent class for
Application
developers that require simple access to the most commonly used jDISC APIs.This interface defines the API of the singleton Application that runs in a jDISC instance.This exception is used to signal that noApplication
has been configured.BindingMatch<T>This class holds the result of aBindingSet.match(URI)
operation.This is a mutable repository of bindings fromUriPattern
s to some target type T.BindingSet<T>This is an immutable set of ordered bindings fromUriPattern
s to some target type T.This interface defines the component that is used by theCurrentContainer
to assign aBindingSet
to a newly createdContainer
based on the givenURI
.A bundle's symbolic name and version.This exception is thrown byOsgiFramework.installBundle(String)
if installation failed.This is a utility class to help with installing, starting, stopping and uninstalling OSGi Bundles.This interface defines the API for changing the activeContainer
of a jDISC application.This is the inactive, mutableContainer
.This class decoratesThread
to allow for internal jDISC optimizations.This class implements theThreadFactory
interface on top of aProvider
forMetricConsumer
instances.This interface represents aContainer
which has been deactivated.This is a repository ofModule
s.This interface defines the consumer counterpart of theMetric
interface.This is an abstraction of the OSGi framework that hides the actual implementation details.This interface acts as a namespace for the supported OSGi bundle headers.This is a utility class to help manageSharedResource
s while configuring aContainerBuilder
.This is a repository ofServerProvider
s.This class holds a regular expression designed so that it only matches certainURI
s.This class holds the result of aUriPattern.match(URI)
operation.