App Client Container Design Notes

[

Unknown macro: {TableOfContents title='App Client Container Design'}

|(TableOfContentstitle='AppClientContainerDesign')]

NOTE: This document continues to evolve.


Jspwiki style: center

1. Introduction

Date of This Document:
initial 6 Mar 2009

1.1 Module and submodule structure

  • appclient/ - new top-level module
    • server/
      • connecto/r - contains only the AppClientSniffer
      • core/ - contains AppClientDeployer and other related classes
      • jws/ - contains server-side classes related to Java Web Start support
    • client/
      • acc/ - builds the OSGi client-side module for app client support
      • acc-config/ - classes generated by JAXB for the sun-acc.xml format - also used by appclient.security module
      • acc-scripts/ - the Windows and non-Windows appclient scripts
      • acc-standalone/ - builds a static non-OSGi JAR for non-OSGi launches
      • jws/ - builds a JAR containing the client-side classes and resources used only during Java Web Start launches

Notes

Currently the jws modules are empty. That support will come after MS1. To minimize the number of module JARs the server must load, we might merge the content of the server-side jws module into the core module.

I have talked to Jerome about generalizing the current app server bootstrapping classes (ASMain, AbstractMain, etc.) so they would meet the ACC's needs also. I have looked into this and decided that we should revisit this post MS1.

Ideally the appclient/client/acc module would build both the OSGi JAR and the static JAR, the two sharing the same dependencies but just recording them differently in their manifests. It might be possible to do this by using the default config and execution for the hk2-jar plug-in and specifying a non-default config and execution for the jar plug-in to create the static JAR. We will need to avoid placing copies of the same resources and classes into the two JARs which we can do using "exclude." Short-term it was simpler to create the acc-standalone submodule which depends on acc.

The current plug-in implementations (jar and hk2-jar) seem to create the Class-Path we need in the static JAR from the dependencies we declare. But there are some additional JARs that are not OSGi JARs that we need to add to the class path. To handle this the appclient/client/acc-standalone module adds to the automatically-generated class path.

1.2 Overview of Deployment, Download, and Launch

1.2.1 Review of v2

During deployment of an EAR, v2 expands the EAR into a directory under the applications/javaee tree within the domain's directory. It then expands each submodule JAR from the EAR into its own subdirectory before deleting the submodule JARs.

If the EAR contains at least one app client v2 generates an umbrella JAR containing

  • each app client submodule JAR (reconstituted because the original was deleted, possibly with stub classes added if the users requested that)
  • any JARs declared in the app client JARs' Class-Paths
  • any JARs from the EAR's library directory
  • all JARs at the top-level of the EAR
  • any EJB submodule JARs

This generated JAR is what "deploy --retrieve" and "get-client-stubs" download and what the user launches using the appclient script or Java Web Start. During either type of app client launch the ACC expands this JAR into a temp directory, then constructs and uses a class path that refers to the JARs in the temp directory tree. Users can copy this single JAR to other locations and launch it from there.

Problems/Issues

  • Each launch of the same client re-expands the generated umbrella JAR.
  • If the developer signed any of the submodule JARs v2 loses that information in expanding (and deleting) and then recreating them to include them in the umbrella JAR.
  • If the developer changes any relevant JAR in the EAR (perhaps the app client JAR itself or perhaps one the client depends on) and then redeploys it, v2 creates a new umbrella JAR. Java Web Start launches download the entire JAR again. If the EAR contains large library JARs then the umbrella JAR will be large, even if the developer's change was to a small JAR.
  • If the EAR contains multiple app clients (probably rare but certainly supported) deployment cannot predict which one the user will launch - in fact that may change over different launches. So it cannot generate the umbrella in a way that supports the java launcher's fast splash screen support.

With deployment expanding and then recreating submodules to generate the umbrella EAR, with Java Web Start having to refresh the entire umbrella JAR after a change, and with the ACC re-expanding during each launch, there is a lot of wasted motion during both deployment and app client launch. In particular users have complained about launch delays when starting large app clients.

1.2.2 Deployment in v3

EAR deployment retains the submodule JARs.

Deployment does not generate an umbrella JAR that itself contains all the application JARs the app client requires.

App client deployment generates, only if needed (see below), a small facade JAR for each app client. This app client facade refers to, rather than replaces, the developer's app client JAR.

Implementation decision

Downloading (both as part of deployment --retrieve and during get-client-stubs) will need to know the set of all JARs required by an app client. The Java Web Start support - perhaps in a server-side "app client container" (we'll need a different name desperately!) - will also need to know this in generating the JNLP and in responding to requests for JAR downloads. Probably best if deployment maintains an in-memory map of this information so get-client-stubs and Java Web Start support can use it without having to recompute it. The key to this map should probably be app-name/app-client-URI-with-EAR for clients in EARs and app-name for stand-alone app clients.

EAR deployment creates a small app client group facade JAR for the EAR. The app client group facade does not contain the app client and library JARs as in v2 but rather refers to the app client JARs or their facades. This lets users continue to do what they have done in v2: specify a generated EAR-level JAR file in

appclient -client
commands and use the -mainclass or -name appclient options to select from multiple app clients.

To be consistent with v2, v3 will name the EAR facade $

Unknown macro: {EAR-app-name}

Client.jar.

1.2.3 Download in v3

During "deploy --retrieve" and "get-client-stubs" the server refers to the list of JARs which each app client uses and, as part of a single download operation, sends each JAR individually. The admin client already contains the logic to save each of these JARs in the correct place in the user-specified directory tree, mirroring the structure in the original EAR. This ensures that relative references in JARs' Class-Path settings to other JARs remain valid.

Some terms:

  • download directory - the directory the user specifies in deploy --retrieve downloadDir or get-client-stubs --appName myApp downloadDir
  • anchor directory - a subdirectory within the download directory dedicated to a single application

GlassFish v2 users could download different generated JAR files into the same download directory. We needed to support the same behavior in v3, even though v3 downloads multiple JARs per application. So if the user deploys using

deploy --retrieve downloadDir myApp.ear

he or she will see

downloadDir/
    myAppClient.jar
    myAppClient/
        ... various JARs ...

More on this later.

For Java Web Start the generated JNLP document for each app client lists each required JAR separately. Java Web Start can refresh, individually, only those JARs which are truly out-of-date. (This is an improvement over v2 in which any change, large or small, triggered the regeneration of the large, all-encompassing JAR file which Java Web Start would download again in its entirety.)

By default, "deploy --retrieve" and "get-client-stubs" download all app clients in the specified app. We plan to add to the command syntax so users may (but are not required to) select a subset of all the clients in an app to download. This enhancement is not there yet.

Note: Users who want to copy downloaded clients need to copy the downloaded directory subtree, not just a single file as in v2. We have not published or documented the contents and layout of the download directory for v2, but certainly users can see what's there. We should encourage users to use the get-client-stubs command from each client that needs a particular client rather than doing file copies. At a minimum we should urge users to view the layout and contents of the download directory as opaque, owned by GlassFish, and subject to change. This is not an issue for Java Web Start launches because the download mechanism and storage are completely different.

1.2.4 Launch in v3

The ACC does not expand any JARs. It constructs the runtime classpath for the client simply by referring to the downloaded client JAR. (see below - If deployment generated a facade app client JAR then the Class-Path in that JAR's manifest refers to all the relevant JARs.)

  • Support for java command syntax on the appclient command

The appclient script now supports a blend of java command syntax with the v2 appclient syntax while remaining backward-compatible. The enhanced format is

appclient [jvm-options] [acc-options] main-class-selector-expression [app arguments]

where main-class-selector-expression is one of:

Expression New in v3? Notes  
-jar myClient.jar X JVM-style  
fully.qualified.class.name X JVM-style selection by class name  
-client myClient.jar   when used by itself (without -mainclass or -name) equivalent to -jar myClient.jar  
a class file      
-mainclass fully.qualified.class.name   without -client: equivalent to the JVM-style selection by class name with -client: selects from among several clients in the JAR  
-name (display name of app client)   selects from among several app clients deployed in the same EAR  

In fact, to remain compatible with v2, the user can mix acc-options in among the app arguments although we should encourage users to follow the format above for clarity.

  • Support for direct java command launches

Users can use

java -javaagent:${install-dir}/modules/gf-client.jar=mainclass=<main class of client> [rest of the java command]

The -javaagent option gets the ACC JAR on the class path and its mainclass agent argument tells the ACC what the main class is. Unfortunately there is no supported or reliable way for a running Java program, even an agent, to find out what main class the java launcher chose to execute. The ACC needs to know that to do injection, so we need the user to tell the agent explicitly. I have filed an enhancement against Java for them to expose a way for a running program to find out which was the main class.

2. Server-side

2.1 General Approach

Use an app client sniffer and deployer, just as with other module types. We do not need a server-side container initially but may use one to provide Java Web Start support.

2.2 Sniffer

Extends GenericSniffer. We have identified how GenericSniffer can be generalized to take on more of the likely common behavior, such as handling multiple stigmas. We can do this without breaking existing sniffers which extend it. We will not make these changes prior to M1.

  • There are two stigmas (entries in an archive which unambiguously identify the module's type): META-INF/application-client.xml and META-INF/sun-application-client.xml. The AppClientSniffer.handles method returns true as soon as it detects either stigma.
  • Otherwise, the sniffer looks for the module's META-INF/MANIFEST.MF. If the manifest exists and specifies Main-Class then return true.
  • Return false.

2.3 Deployer

The app client deployer has these main jobs:

  1. Keep track of the JARs that should be downloaded for each app client and for each EAR containing app clients.
  2. If necessary, c Create a small facade app client JAR to front-end the developer's app client JAR.

The EAR deployer has an added job:

  1. If the EAR contains at least one app client, create a small facade for the EAR.

2.3.1 Tracking required JARs

If this is a stand-alone app client deployment, the only required JAR is the app client JAR itself.

If this is an app client nested inside an EAR, the app client deployer starts with the Class-Path entry in the app client JAR and all JARs in the EAR's library directory (if any). With this list of JARs as a starting point the deployer builds the transitive closure of the required JARs (by checking each JAR's Class-Path entry and adding any as-yet-unseen JARs to the overall list to process). All these JARs will be within the directory the EAR deployer created when it expanded the EAR and the app client deployer records the URIs to each of these JARs relative to the location of the app client JAR in that expanded directory.

The app client deployer stores the result in an in-memory map. (The key is <application-name>/<app-client-URI-within-the-EAR> for nested app clients and <application-name> for stand-alone app clients. There is currently no unique identification of app clients within an EAR using a name; the URI path to the app client JAR within the EAR is unique so we use that).

The deployer.clean method should invoke super.clean() and then remove that app client's entries from the map.

For the EAR case with multiple app clients to be downloaded, each individual app client deployer can use outboundPayload.attachFile to arrange for that file's download. The EAR deployer can also use outboundPayload.attachFile to arrange for the EAR facade to be downloaded. The order of calls to attachFile is not important. This is all conditional on whether the deployment request specifies --retrieve.

2.3.2 Creating a facade app client JAR

We will always create a facade app client JAR. This will allow the user to launch a deployed app client JAR using a simple java command because we will specify a main class in the facade that prepares the ACC and then launches the developer's original main class.

The spec obligates us to make all EAR library JARs available to nested app clients at runtime. If the transitive closure starting from the app client JAR's Class-Path does not include all the EAR's library JARs then deployment will make sure they appear in the manifest Class-Path for the client facade.

Creating the client facade:

  • Derive the name of the facade (developer's-app-client-jar-name)Client.jar
  • Create a new facade app client JAR using this name within the generated directory for the app:
    • Same relative URI location as the developer's app client JAR
    • Manifest
      • Class-Path: (developer's client JAR) (all entries from the Class-Path of the original app client JAR) (library JARs in the EAR)
      • Main-Class: org.glassfish.appclient.client.AppClientFacade
      • GlassFish-AppClient-Main-Class: (original Main-Class from the developer's app client JAR)
      • GlassFish-AppClient: (unique name generated for the original app client JAR)
      • SplashScreen-Image: copied from original
    • Copy the splash screen image JarEntry from the original app client JAR and create a new JarEntry for it in the facade JAR
    • Store a copy of the GlassFish-provided AppClientFacade.class as a new JarEntry in the facade JAR (same name and contents). (This class is in the appclient/client/gf-acc-module module which is accessible to the server.)

See notes in the v3 download section.

2.3.3 Creating an "app client group" facade JAR file (a facade at the EAR level)

Why would we do this? Because in v2 users can launch app clients using

appclient -client umbrella.jar -mainclass "main-class ...

or

appclient -client umbrella.jar -name "client-name" ...

to select which of several clients from the EAR to start. To support this technique in v3 we will need to create a facade at the EAR level.

The EAR deployer will generate the app client group facade if the EAR contains at least one app client:

  • Manifest
    • Main-Class: org.glassfish.appclient.client.AppClientGroupFacade
    • (no "Class-Path:" entry)
    • GlassFish-AppClient-Group: (the URIs relative to the top of the EAR for each facade app client JAR)
    • (no SplashScreen-Image entry)
  • (no splash screen image JarEntry)
  • A copy of AppClientGroupFacade.class

By building the app client group facade this way we let users launch it using either the appclient script as in v2 or a direct java command. Either way the behavior is the same as in v2.

In the future if the EAR contains only one app client we might consider copying the splash screen image and the corresponding manifest entry into the EAR facade. No urgency to this, though.

Note:

In the EAR case, the deployer generates each app client facade JAR in the same position relative to the top of the EAR that the developer's original app client JAR occupies. That is, after the files are downloaded to the client, the facade app client JAR and the the original one will be in the same directory. This means that in the facade's manifest, the two references to the original - the first part of the generated Class-Path and the GlassFish-AppClient - should be just the name and file type of the original; no relative path information should be there even if the original JAR was in a subdirectory of the EAR.

Further, for app clients nested inside an EAR, the Class-Path for the app client facade JAR must refer to library JARs relative to where the client is within the EAR. Basically, this means that for every "/" in the relative URI for the app client JAR within the EAR, each Class-Path entry for a library JAR needs to have an additional "../" prefix in its URI. See the example below.

Note that the app client group facade will reside at the top of the download directory for the app, so its references to the generated app client-level facade JARs will add a prefix to the URIs for the original app client JARs within the EAR, the prefix pointing to the app's download directory. That is, given an original EAR myApp.ear that looks like this

foo/
   myClient.jar

   bar/
      otherClient.jar
lib/
   math.jar

the deployer should generate the files so that the download directory and the manifests look like this:

downloadDir/
   myAppClient.jar (app client group facade - naming follows the convention from v2 but content is different)
   myAppClient/ (new in v3 - collects everything for the myApp download except the app client group facade
      foo/
         myClient.jar (original developer's app client JAR)
         myClientClient.jar (app client facade)

         bar/
            otherClient.jar (original developer's app client JAR)
            otherClientClient.jar (app client facade)

      lib/
         math.jar (unchanged by deployer)

Manifests:

Generated facade Partial Manifest  
myAppClient.jar ~UWC_TOKEN_START~1278095716120~UWC_TOKEN_END|
foo/myClientClient.jar ~UWC_TOKEN_START~1278095716121~UWC_TOKEN_END|
foo/bar/otherClientClient.jar ~UWC_TOKEN_START~1278095716122~UWC_TOKEN_END|

Note that for a stand-alone app client also the Class-Path and GlassFish-AppClient manifest entries will refer to the original JAR only by name and type (no path). So the logic which generates the app client facade works the same in both cases.

2.4 deploy and redeploy commands

When the user specifies --retrieve, the deploy and redeploy commands send the required files back to the client in the response payload.

  • If an EAR was deployed, the app client group facade and the transitive closure of all JARs referenced from any app client.
  • For each app client:
    • The app client facade (with name xxxClient.jar).
    • The original app client JAR xxx.jar.

This approach allows us to leave the developer's original app client JAR completely alone - not even copying it - while giving.

2.4.1 Downloading a subset of app clients during EAR deployment

(post M1 at least)
We might expand the

--retrieve local-dir-name
option to, for example,
--retrieve:client-URIs local-dir-name
where the client-URIs expression could be a comma-separated list of URIs to the clients of interest, perhaps excluding the trailing .jar file type. We could support wildcarding.

The added syntax would be valid only during an EAR deployment – stand-alone app clients contain only one client. The download would include the set union of the JARs required by each individual selected client.

In reality this syntax is rather ugly and not intuitive. We could surely find a better solution but providing this enhancement is a low priority.

2.5 get-client-stubs command

If the user identifies a stand-alone app client then GlassFish downloads the JARs required by that app client as recorded in the in-memory data structure.

If the user identifies an EAR and nothing else, GlassFish downloads all JARs needed by all app clients.

Post-M1 we'll propose syntax changes to get-client-stubs so users can select a subset of the clients in an EAR. Might be so simple as the

--appName
option would accept
appName/clientName
for example.

2.6 Java Web Start support

3. Client-side

3.1 App Client Container

3.2 Java Web Start