com.yahoo.jdisc.application.package-info Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* Provides classes and interfaces for implementing an {@link com.yahoo.jdisc.application.Application
* 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 {@link com.yahoo.jdisc.application.ContainerBuilder
* ContainerBuilder} interface to load OSGi {@link org.osgi.framework.Bundle Bundles}, install Guice {@link
* com.google.inject.Module Modules}, create and start {@link com.yahoo.jdisc.service.ServerProvider ServerProviders},
* inject a {@link com.yahoo.jdisc.application.BindingSetSelector BindingSetSelector}, and configure {@link
* com.yahoo.jdisc.application.BindingRepository BindingSets} with {@link com.yahoo.jdisc.handler.RequestHandler
* RequestHandlers} and {@link com.yahoo.jdisc.service.ClientProvider ClientProviders}. Once the ContainerBuilder is
* appropriately configured, it is passed to the local {@link com.yahoo.jdisc.application.ContainerActivator} to perform
* an atomic switch from current to new {@link com.yahoo.jdisc.Container 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 {@link com.yahoo.jdisc.Request 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 {@link
* com.yahoo.jdisc.application.DeactivatedContainer 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 continuously 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();
try (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 {@link
* org.osgi.framework.BundleActivator 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 com.yahoo.jdisc
* @see com.yahoo.jdisc.handler
* @see com.yahoo.jdisc.service
*/
package com.yahoo.jdisc.application;