PageBox for Java: Web application deployment using Java PageBox

for
 PageBox for Java 
 API 
 Demo 
 Background 
Presentation Install User guide Developer guide Programming Port Repository PageBox Release notes

PageBox for Java

Foreword

Objective

This document presents the implementation of PageBox.

To present the design we use a representation that loosely follows UML.

Audience

Programmers who want:

  1. To participate to the PageBox development

  2. To adapt or extend PageBox for their needs

  3. To understand the PageBox design

This documentation assumes some knowledge of UML, Java and J2EE.

Design choices

  1. Data stored in XML

  2. Implementation split in two parts: PageBoxLib, a library shared between PageBox and the PageBox API and PageBox itself that only contains the Audit and Update servlets and the Reinstall class

Use cases

The Repository interfaces with:

  1. PageBox administrators (Update servlet)

  2. Repositories for deployment

We distinguish:

  • The archive deployment initiated by a PageBox Repository

  • The archive installation, which is performed by the PageBox

Update

The PageBox administrator uses an Update servlet to list the archives installed on a PageBox.

The Update servlet applies an XSL transformation to PbArchives.xml with the update.xsl stylesheet.

We describes below what is PbArchives.xml and how it is updated. Because this use case is simple we don't need to look at it in more details.

Deployment

PageBox implements a Deploy Web service.

The Repositories can call the add and delete methods of the Deploy Web service to deploy and undeploy archives. A PageBox can install archives from different Repositories.

Relay

When it must deploy an archive on a large number of PageBoxes, a Repository can use relayed deployment. In that model the Repository piggybacks a list of PageBoxes where the archive should also be deployed in a deployment request. The receiving PageBox forwards the PageBox list to a Relay thread and handles normally the deployment request. The Relay thread deploys the archive on the PageBoxes in the list - on PageBox3 on the diagram. If the list is longer the Relay also piggybacks a list of PageBoxes where the archive should be deployed. When it has completed its deployments the Relay calls the Notify method of the Repository's RepoQuery Web service to send the status of the deployments to the Repository.

Installation

In the most complex case the installation is a three-step process:

  1. Copy. The PageBox uncompresses the archive at a location defined by the PageBox administrator.

  2. Install. The PageBox calls a dbcreate script or program defined by the PageBox administrator and loads and calls an Install class defined by the Publisher.

  3. Deploy. The PageBox calls the Application Server to dynamically add or update the archive (a Web application in that case)

The Publisher chooses to include an Install class in its archive and indicates if its archive should be deployed on the Application server (if the archive is a Web archive).

The PageBox administrator defines if it trusts enough the Repository and the Publisher to allow the Install step or only the Copy step. The PageBox also decides to support or not the Deploy step.

We focus in the rest of the document on the handling of deployment and undeployment requests (Deploy Web service). For more information about the Application Server deployment look at the Porting guide.

DeployImpl

The Deploy Web service is implemented in the DeployImpl class.

The DeployImpl class implements the DeployIF class, which is defined like this:

interface DeployIF {

Status add(String arch, String downloadURL, String owner,

byte[] archData, String date, String docURL, String user, boolean isUpdate,

boolean runInstall, UrlUser[] relayed);

Status delete(String arch, String downloadURL, String owner,

String user, boolean runRemove, boolean keepDir);

String rename(String oldDownloadURL, String newDownloadURL, String user);

String getArchPath(String arch);

String getAudit(String arch, String owner, String user, String downloadURL);

}

Where:

  • arch is the name of the archive to deploy.

  • downloadURL is the URI of the Repository's RepoQuery Web service

  • owner is the user ID of the archive Publisher

  • archData is the archive

  • date is the date when the archive was published

  • docURL is the URL of the archive documentation

  • user is the subscriber who subscribed the PageBox

  • isUpdate is true if it is a delta (jardiff) update

  • runInstall is true if the archive is a Web archive and should be dynamically deployed on the Application server

  • runRemove is true if the archive is a Web archive and should be dynamically undeployed from the Application server

  • keepDir is true if the archive directory and content should not be remove when the archive is undeployed

  • oldDownloadURL is the old URI of a Repository's RepoQuery Web service

  • newDownloadURL is the new URI of a Repository's RepoQuery Web service

  • relayed is an array of UrlUser object

The UrlUser class is defined like this:

class UrlUser {

String url;

String user;

String pbUser;

String pbPasswd;

}

Where

  • url is the URI of the target PageBox Deploy Web service

  • user is the subscriber who subscribed the target PageBox

  • pbUser is the user ID used by Relay PageBoxes to authenticate on target PageBoxes

  • pbPasswd is the password used by Relay PageBoxes to authenticate on target PageBoxes

add deploys a new archive or an archive update. It can also include a relayed list of deployment requests.

delete undeploys an archive

rename changes the URI of the RepoQuery Web service of a Repository.

getArchPath returns the URL of an installed archive.

getAudit returns the log records of a PageBox about an archive installation.

Except the getArchPath and getAudit methods, DeployIF methods call internal methods:

DeployIF method

Internal method

add

addToPbArchs

delete

deleteFromPbArchs

rename

renameRepository

DeployImpl uses two XML files:

  • rules.xml set by the PageBox administrator and read by DeployImpl where the security and installation settings are defined

  • PbArchives.xml read and updated by DeployImpl where the archives currently installed on the PageBox are described

The format of these files is described in the Installation guide.

rules.xml is read in memory by the initialize method using a SAX handler, RuleHandler. rules.xml is represented in memory by a RuleHandler.Rule object called rule and defined like this:

class Rule {

DefaultAuth defaultAuth = new DefaultAuth();

HashMap repositoryRules = new HashMap();

String target = null;

String rootPath = "";

HashMap resources = new HashMap();

HashMap extensions = new HashMap();

JDBCinfo jdbcInfo = new JDBCinfo();

String dbCreateScript = null;

String dbDropScript = null;

}

The table below describes the members of the Rule class:

Member variable

Content

defaultAuth

DefaultAuth object

repositoryRules

Map whose key is the URI of a Repository RepoQuery Web service and value is an RepositoryAuth object

target

Directory where Web archives are inflated. Also a parameter used in Install.class invocation.

jdbcInfo

JDBC parameters. Used by the PageBox API.

resources

HashMap describing the Application server resources. The key is a resource name and the value is a ResourceInfo object.

extensions

HashMap describing the PageBox extensions. The key is an extension name and the value is an extension class implementing the ExtensionIF interface.

rootPath

An archive called myarchive.jar will be installed on rootPath/myarchive if rootPath is a full path and on ApplicationServerURL/rootPath/myarchive otherwise

dbCreateScript

Path of a script or program used to initialize the archive environment and typically create the archive database

dbDropScript

Path of a script or program used to cleanup the archive environment and typically drop the archive database

We must now introduce authorization classes. The root class is PublisherAuth that defines the installation rule and allowed extensions and resources of a publisher (user who originally published the archive). PublisherAuth is defined like this:

class PublisherAuth {

int rule;

HashMap extensions = new HashMap();

HashMap resources = new HashMap();

}

Where:

  • Rule contains either NONE (deployment forbidden) or COPY (Install.class not called) or INSTALL (Install.class called)

  • extensions is the list of the permitted extensions

  • resources is the list of the permitted resources

Two classes inherit from PublisherAuth:

  • DefaultAuth that defines the installation rule and allowed extensions and resources when neither the publisher nor the Repository is found in repositoryRules

  • RepositoryAuth that defines the installation rule and allowed extensions and resources of a Repository

DefaultAuth is defined like this:

class DefaultAuth extends PublisherAuth {

boolean unauthenticated = false;

String deployerClass = "PageBoxLib.JWSDPDeployer";

String querierClass = "PageBoxLib.JWSDPQuerier";

}

Where:

  • unauthenticated is true when a Repository is allowed to deploy even if the user who subscribed the PageBox is not identified

  • deployerClass is the default client class to use to invoke the DeployIF service (can be overridden at RepositoryAuth level)

  • querierClass is the default client class to use to invoke the RepoQueryIF service (can be overridden at RepositoryAuth level)

RepositoryAuth is defined like this:

class RepositoryAuth extends PublisherAuth {

String subName = null;

String subPassword = null;

HashMap publisherRules = new HashMap();

String deployerClass = "PageBoxLib.JWSDPDeployer";

String querierClass = "PageBoxLib.JWSDPQuerier";

}

The table below describes the members of the RepositoryAuth class:

Member variable

Content

subName

Subscriber name - used to check if user is valid in Deploy requests. Also used by Relayer to authenticate in RepoQuery.Notify calls.

subPassword

Subscriber password. Used by Relayer to authenticate in RepoQuery.Notify calls.

publisherRules

Map whose key is the name of a publisher and value is a PublisherAuth object

deployerClass

Default client class to use to invoke the DeployIF service

querierClass

Default client class to use to invoke the RepoQueryIF

PbArchives.xml is read in memory by the restorePbArchs method using a SAX handler, DeployHandler and (re)written by the savePbArchs method. PbArchives.xml is represented in memory by an archives map whose key is the archive name and value is an Archive object:

class Archive {

String downloadURL;

String owner;

String size;

String date;

String status;

String docURL;

boolean runInstall;

}

Where

  • downloadURL is the URL of the RepoQueryIF Web service of the repository that deployed the archive

  • owner is the publisher who published the archive

  • size is the size of the archive

  • date is the date and time when the archive was published

  • status contains "installed" if the archive was successfully installed, "setting pb" if the archive was deployed but not installed because of a setting problem on the PageBox problem, "archive pb" if the archive was deployed but not installed because of an archive error

  • docURL is the URL of the archive documentation

  • runInstall contains true if the archive is a Web archive and must be dynamically installed on the Application server

An archive is defined in the archive map and in the PbArchives.xml file if it was deployed (transferred and inflated) on the PageBox. PageBox keeps archives that were not successfully installed,

  1. For archive errors because it allows the deployment of the fixed archive in a delta update

  2. For PageBox setting problems because retrying the installation once the setting problem is fixed without involving network access except for notifying the Repository of the status changes

Status

Starting with version 0.0.9 the add and delete methods return an object of class DeployIF.Status defined like this:

class Status {

final static int OK = 0;

final static int NOTDEPLOYED = 1;

final static int NOTRELAYED = 2;

final static int ARCHPB = 3;

final static int PBPB = 4;

final static int NOTUNDEPLOYED = 5;

final static int NOTCONTACTED = 6;

int code = OK;

String msg;

Status(int code, String msg) {

this.code = code;

this.msg = msg;

}

Status() {}

}

The Status class stores two member variables, a code that can take an OK, NOTDEPLOYED, NOTRELAYED, ARCHPB, PBPB, NOTUNDEPLOYED or NOTCONTACTED value and an msg string that contains the reason of the error.

add

add can return a Status object with a code OK, NOTCONTACTED, NOTDEPLOYED, NOTRELAYED, ARCHPB or PBPB.

The archive map is set accordingly:

Code

Archive object

Meaning

OK

status = "installed"

The archive was successfully installed.

NOTCONTACTED

Not created

The PageBox is not ready.

NOTDEPLOYED

Not created

The archive was not deployed.

NOTRELAYED

Not created

The archive was not deployed. The PageBox was also unable to relay piggybacked deployment requests.

ARCHPB

status = " archive pb"

The archive was not installed because the Install.install method of the archive threw an exception.

PBPB

status = "setting pb"

The archive was not installed because the Install.install method of the archive returned an error or the dynamic deployment of the Web application failed.

add

  1. Calls the exercise method of a RepoTest class at the beginning and at the end. We describe RepoTest below.

  2. Implements configuration refresh and protects against race conditions

  3. Calls addToPbArchs

Here is the explanation of the second item:

  1. At startup the PageBox retries the installation of the archives in "setting pb" and dynamically reinstall the Web applications. At this time the PageBox cannot process add requests. Therefore as far as a PageBoxState context variable is not set add method returns a NOTCONTACTED Status object. Then the Repository will put the archive in pending state and periodically retry the deployment.

  2. The user can click on a Retry button of the Update form to retry the installation of the archives in PBPB state. In this case add must read again the configuration of the PageBox, which should have been fixed. Therefore if PageBoxState is "reparse" add reads and parses again rules.xml.

delete

delete can return a Status object with a code OK, NOTCONTACTED, NOTUNDEPLOYED, ARCHPB or PBPB.

The archive map is set accordingly:

Code

Archive object

Meaning

OK

Removed

The archive was successfully uninstalled.

NOTCONTACTED

Not modified

The PageBox is not ready.

NOTUNDEPLOYED

Not modified

The archive was not undeployed.

ARCHPB

Removed

The archive may have not been properly uninstalled because the Install.uninstall method of the archive threw an exception.

PBPB

Removed

The archive may have not been properly uninstalled because the Install.install method of the archive returned an error or the dynamic undeployment of the Web application failed.

delete

  1. Calls the exercise method of a RepoTest class at the beginning and at the end. We describe RepoTest below.

  2. Implements configuration refresh and protects against race conditions

  3. Calls deleteFromPbArchs

Here is the explanation of the second item:

  1. At startup the PageBox retries the installation of the archives in "setting pb" and dynamically reinstall the Web applications. At this time the PageBox cannot process add requests. Therefore as far as a PageBoxState context variable is not set delete method returns a NOTCONTACTED Status object. Then the Repository will put the archive in pending remove state and periodically retry the deployment.

  2. The user can click on a Retry button of the Update form to retry the installation of the archives in PBPB state. In this case add must read again the configuration of the PageBox, which should have been fixed. Therefore if PageBoxState is "reparse" delete reads and parses again rules.xml.

getArchPath

getArchPath computes the archive URL according to the rules.xml rootPath as explained above: "An archive called myarchive.jar will be installed on rootPath/myarchive if rootPath is a full path and on ApplicationServerURL/rootPath/myarchive otherwise".

getAudit

getAudit first retrieves the Archive object that describes the archive given in parameter to check if the archive is installed and if the request is valid. Then getAudit calls the getAudit method of the Log instance to get the log records of this archive.

addToPbArchs

There are three steps in addToPbArchs:

  1. Relay management

  2. Security check

  3. Installation

Relay management

addToPbArchs creates the Relayer instance if it doesn't exist yet.

addToPbArchs calls the deploy method of the Relayer with the relayed list of PageBoxes to deploy.

Security check

addToPbArchs performs a part of the PageBox security checking:

addToPbArchs first checks if the Repository that originated the deployment request is defined in the repositoryRules map of the rule object. If it is not the case and if unauthenticated is false in the default authorization addToPbArchs returns a NOTRELAYED error to the Repository: this PageBox cannot trust the unidentified Repository even to relay this deployment request.

If the Repository that originated the deployment request is defined in the repositoryRules map of the rule object, addToPbArchs looks at the repositoryRules entry, which is of RepositoryAuth type and checks if the deployment request was issued on behalf of the user identified by subName. If it is not the case addToPbArchs returns a NOTRELAYED error to the Repository: the deployer is not allowed to deploy on this PageBox.

Then addToPbArchs checks if the user who published the archive is defined in the publisherRules map of the repositoryRules entry. The issue is no more to check if the deployment request should be rejected but to refine the resource and extension set and the installation rule. If the publisher is not defined in the publisherRules map, the archive is installed with the Repository rule, the resources defined at the Repository and default level and the extensions defined at the Repository and default level. If the publisher is defined in the publisherRules map addToPbArchs looks at the publisherRules entry, which is of PublisherAuth type. The archive is installed with the publisher rule, the resources defined at the publisher, Repository and default level, the extensions defined at the publisher, Repository and default level.

If the Repository was not identified but unauthenticated was true the archive is installed with the default rule, the resources defined at default level and the extensions defined at default level.

Then addToPbArchs checks that the installation rule is COPY or INSTALL.

If the archive is already installed addToPbArchs checks that the new version is coming from the same Repository and publisher as the old version.

Installation

If the archive is already installed addToPbArchs

  1. Calls uninstall to cleanup the previous archive installation.

  2. Calls install to install the new version

  3. Updates the archive description in the archives map

If the archive is not yet installed addToPbArchs

  1. Calls install to install the archive

  2. Add the archive description to the archives map

deleteFromPbArchs

There are three steps in deleteFromPbArchs:

  1. Security check

  2. Uninstallation

Security check

deleteFromPbArchs performs the same PageBox security checking as addToPbArchs.

Then deleteFromPbArchs checks that the undeployment authorization is COPY or INSTALL.

deleteFromPbArchs checks that the delete comes from the same Repository and publisher as the installed archive.

Uninstallation

deleteFromPbArchs:

  1. Call uninstall to uninstall the archive

  2. Removes the archive description from the archives map

renameRepository

renameRepository first checks if the Repository that originated the rename request is defined in the repositoryRules map of the rule object. If it is not the case and if unauthenticated is false in the default authorization renameRepository returns an error to the Repository: this PageBox cannot trust the unidentified Repository.

If the Repository that originated the rename request is defined in the repositoryRules map of the rule object, renameRepository looks at the repositoryRules entry, which is of RepositoryAuth type and checks if the rename request was issued on behalf of the user identified by subName. If it is not the case renameRepository returns an error to the Repository: the deployer is not allowed to rename on this PageBox.

For each archive described in the archives map renameRepository checks if the RepoQuery URI of the archive is the same as the old Repository RepoQuery URI. If it is the same renameRepository replaces the RepoQuery URI of the archive by the new Repository RepoQuery URI.

install

If the installation is a delta (jardiff) update install

  1. Checks that the previous version archive was inflated

  2. Reads the members of the archive stream

  3. Writes these members into the archive directory except META-INF/INDEX.JD

  4. Applies moves and removes specified in META-INF/INDEX.JD

If the installation is not a delta update install reads the members of the archive stream and rewrites them into the archive directory.

Then if the deployment authorization is INSTALL install calls the install method of an InstallHelper member object.

If the archive is a Web archive (runInstall == true in Deploy.add) and if an Application server deployer was defined (see the Porting guide for more information) install calls the reload (if it is an update) or the install (first installation) method of the Application server deployer.

uninstall

If

  1. uninstall is called for deletion and not for update

  2. The archive is a Web archive (runRemove == true in Deploy.delete)

  3. An Application server deployer was defined (see the Porting guide for more information)

uninstall calls the remove method of the Application server deployer.

Then if the deployment authorization is INSTALL uninstall tries to find an installation class called Install.class in

  1. Archive_installation_path/WEB-INF/classes (Web archive)

  2. Archive_installation-path

If it exists uninstall loads and instantiates the installation class using the InstallClassLoader class loader. Then uninstall calls the uninstall method of the installation class.

Then if it is called for deletion and not for update, uninstall runs the dbdrop script if it is defined and removes the inflated archive.

InstallHelper

InstallHelper is instantiated in the initialize method of DeployImpl and of Reinstall.

Its install method is called in the install method of DeployImpl and in the retry method of Reinstall.

InstallHelper implements the installation logic.

install

install runs the dbcreate script if it is defined and tries to find an installation class called Install.class in

  1. Archive_installation_path/WEB-INF/classes (Web archive)

  2. Archive_installation-path

If it exists install loads and instantiates the installation class using the InstallClassLoader class loader. Then install calls the install method of the installation class.

InstallClassLoader

InstallClassLoader is a simple class loader that extends the ClassLoader.

The InstallClassLoader constructor has five parameter set by the install and uninstall methods of DeployImpl:

  1. The path of Install.class

  2. The class loader used to load InstallClassLoader

  3. The URL of the RepoQuery Web service of the deploying Repository

  4. A logging object

  5. The archive directory

The constructor creates a protection domain with:

  • The RepoQuery Web service URL as code source URL

  • The permission to read all properties

  • The permission to read, write and delete files in the archive directory and subdirectories

The most important method of InstallClassLoader is loadClass.

loadClass is called to load Install.class and classes used by the Install class.

  • If it finds a class file matching the requested class in the Install.class path loadClass loads this class, defines this class in the protection domain and adds it to a classes cache.

  • Else loadClass forwards the load request to the System class loader (the one that reads the CLASSPATH) and to the parent class loader (the class loader of the PageBox Web archive.)

Relayer

Relayer class extends Thread. Its constructor starts a worker thread responsible for deployment relaying.

run

The run method (called in the worker thread) waits for requests queued on a queue linked list.

For each deployment request found on queue run:

  1. Calls the relay method to deploy the archives on the target PageBoxes

  2. Calls the repNotify method to report the deployment status to the Repository

deploy

The deploy method is called by the addToPbArchs method of DeployImpl in the Web service thread.

deploy queues a deployment request on the queue linked list and calls the notify method to wake up the run method.

relay

relay processes the deployment of an archive to a array of subscribed PageBoxes, relayed. relay first moves half the array in another array, uua: relay will forward the deployment on the uua PageBoxes to another PageBox.

Then relay calls the add method of the Deploy Web service of the first PageBox in the remaining relayed array. If the Deploy invocation fails relay calls the Deploy Web service of the next PageBox in relayed and so on.

  • If all Deploy calls in relayed have failed, relay calls the Deploy Web services of the uua PageBoxes without deployment forwarding.

  • Else relay calls itself with the remaining relayed array.

repNotify

relay records deployment status and repNotify calls the Notify method of the Repository's RepoQuery Web service to send the deployment status to the Repository.

Reinstall

Dynamic Application Server deployment (using an Application Server deployer) is temporary: when the Application server is restarted installed archived are no longer deployed. To address this issue at startup PageBox calls the Application server deployer for each archive installed and defined with runInstall = true.

If the archive is in "setting pb" state Reinstall tries again installing the archive because the PageBox configuration may have changed.

This function is implemented in:

  1. The init method of the Update class

  2. The constructor and the run method of the Reinstall class

When the user clicks on the Retry button of the Update form Update calls a retry method of Reinstall with the URL of the DeployIF Web service of this PageBox as parameter to try reinstalling archives in "setting pb" state.

Reinstall implements another retry method with an isStarted boolean parameter, which is called by the retry method above with isStarted = false and by the run method with isStarted = true. This is the method that we detail below.

init

The init method of the Update servlet should be called at the PageBox load because it is configured with the load-on-startup element in web.xml.

init

  1. Restores the target directory and the rootPath of installed archives from rules.xml using the initialize method and a SAX handler, RuleHandler

  2. Creates a Application Server deployer

  3. Restores the archives map from PbArchives.xml using the restorePbArchs method and a SAX handler, DeployHandler

  4. Instantiates a Reinstall object

Constructor

The Reinstall class extends the Thread class. The constructor of the Reinstall class starts the thread.

The actual redeployment of the Web archives takes place in the Thread method, run. It is because the deployment command is actually a call to a servlet on the same Application Server: calling the servlet from the init method can cause a deadlock.

run

run calls the retry method with isStartup = true.

retry

For each archive in the archives map the retry method:

  • Checks if the archive is installed and defined with runInstall = true. If it is the case and if isStartup = true (retry called by the run method) retry calls the install method of the Application Server deployer to redeploy the Web application.

  • Checks if the archive is in "setting pb" state. If it is the case retry calls the install method of the InstallHelper object to again try installing the archive. Then retry updates the archive state according to the status returned by the install method. If the state has changed retry calls the notifyFix method of the DynDns singleton to notify the change to the Repository. If the state is "installed" retry calls the install method of the Application Server deployer to redeploy the Web application.

DynDns

Background

Permanent IP addresses are scarce resources especially in Europe and Asia. The cheapest way to get a permanent IP address is to use the service of an Application Service Provider (ASP). An Internet Service Provider (ISP) buys ranges of IP addresses and allocates these addresses to its customers. Depending on the customer subscription the ISP may allocate a temporary address to a customer. It is usually the case with cable and DSL subscribers and sometimes the case of dial-up subscribers.

Users usually don't know the IP address of a site. Users know the DNS name of the site. A site owner may choose to buy a domain name (about $10 to $20 per year). Buying a domain makes sense for commercial reasons: the site URL is easier to remember and shows a minimal commitment of the provider. For technical sites (syndicated content provider, Web service) a sub-domain name that we usually can get for free is usually enough.

DNS servers handle matching tables:

DNS name

IP address

When the IP address of a host changes (which is likely to happen in case of temporary allocated IP address) it is possible to dynamically update its IP address on the DNS server.

PageBoxes, PageBox Repositories and Web applications deployed with PageBox need permanent host names. They don't require domains and permanent IP addresses. Using temporary IP addresses and sub-domain names actually augments the value of PageBox by enabling server-less constellations:

In this scenario the users host the Application server and the PageBox on their workstation and the publisher hosts an Application server and a Repository on her workstation. Workstations get temporary IP addresses from the users and publisher ISPs. The PageBoxes and the Repository monitor the temporary IP address and update the Dynamic DNS server in case of change.

  1. The users subscribe to the publisher's Repository using the publisher's sub-domain name

  2. The publisher publishes her Web application on her local Repository. The Repository automatically installs the Web application on the subscribers' Application servers using the subscribers' sub-domain names

  3. Users query their local Web application instance

Using a local Web application instance can be useful for compute-intensive applications (stock trading) or in cases where the user must be able to use the application in disconnected mode (off-line learning). It is obviously also possible to run one Application server and one PageBox per office connected with DSL or WiFi.

DNS servers typically support dynamic update with the RFC 2136 protocol. We chose in PageBox version 0.0.8 and above to support the DynDNS HTTP protocol. This protocol is supported by firewalls. Furthermore the DynDNS site offers free sub-domain registration and update. In the future we may implement a DynDNS / RFC 2136 gateway.

Starting with version 0.0.9 DynDns is also responsible for notifying the Repositories about the changes of archive state occurred than to the retry mechanism. Because a Repository may be down DynDns has to serialize the changes (the PageBox may be stopped before having notified the changes) and to periodically retry the notifications.

Implementation

The DynDNS support is implemented in the DynDns class.

This class extends Thread and implements nine methods, a constructor, a run, an end, a register, a getHost method, a notifyFix, a repNotifyFix, a checkArchives and a saveFixInfo methods. A DynDns object is created in the init method of the Update class and its end method is called in the destroy method of Update class. Because the Update servlet is configured with load-on-startup equals 0, the DynDns object is created at the PageBox startup.

Constructor

The DynDns constructor first read configuration parameters such as the URL of the registration and update page, authentication parameters, the DNS name to register and either the name of the interface or a local name whose address must be used. If the last configuration parameter is the interface name the constructor retrieves the IP address with NetworkInterface.getByName(inter). If the last configuration parameter is a local name the constructor retrieves the IP address with InetAddress.getByName(name).

DynDNS.org policy is to forbid unneeded queries. Therefore DynDns persists the registered domain and address in a dyndns.txt file. This file contains three lines:

  • The URL of the registration and update page

  • The registered DNS name

  • The registered IP address

The DynDns constructor reads and caches the dyndns.txt file. Then it uses a FixHandler SAX handler to parse a fixinfo.xml file and populate a repositories map. This file has a format like this:

<fixinfos>

<url>https://localhost:8443/PageBox/jaxrpc/DeployIF</url>

<fixinfo>

<rep-url>https://localhost:8443/Repository/jaxrpc/RepoQueryIF</rep-url>

<rep-user>subscriber</rep-user>

<rep-password>subscriber</rep-password>

<archives>

<archive>

<name>epimetheus.war</name>

<status>installed</status>

</archive>

<archive>

<name>euroLCC.war</name>

<status>archive pb</status>

</archive>

</archives>

</fixinfo>

</fixinfos>

The repositories map has items whose keys are the URL of Repository RepoQueryIF Web services and values are FixInfo objects. The FixInfo is defined like this:

class FixInfo {

String repUser;

String repPassword;

HashMap archives;

FixInfo(String repUser, String repPassword, HashMap archives) {

this.repUser = repUser;

this.repPassword = repPassword;

this.archives = archives;

}

FixInfo() {}

}

Where

  • repUser is the name of the user to use to connect to the Repository

  • repPassword is the password of the user to use to connect to the Repository

  • archives is a map whose items whose key is an archive name and value is a code OK or ARCHPB

Eventually the constructor starts the thread.

notifyFix

notifyFix calls the repNotifyFix method to notify the Repository about the status change of the archives defined in the archives map of the Repository's FixInfo object.

In case of failure:

  • notifyFix adds a new entry to the repositories if the Repository to notify was not yet defined in the map

  • notifyFix adds the archive passed in parameter to the archives map of the Repository's FixInfo object

In case of success notifyFix removes the corresponding Repository entry from the repositories map.

run

The run method runs in the created thread and as far as a toStop variable equals false periodically

  • Calls the register method if the Dynamic registration parameters were set (toRegister = true)

  • Calls a checkArchives method if there are notifications to perform (repositories not empty)

The end method sets the toStop variable to true and calls notify to trigger the thread termination.

register

The register method:

  1. Retrieves the current IP address

  2. Checks if the (DNS name, IP address) is not already registered on the DynDNS server using the dyndns.txt cache

  3. Queries the DynDNS server, checks the server response and updates dyndns.txt if the (DNS name, IP address) is not yet registered

We wrote a small DynDns emulator to test the implementation.

Servlet source

Update.java

Web application

dyndns.war

The getHost method returns:

  • The DNS name of the host if the DNS registration was performed

  • The local name otherwise

The getHost method is called by the Update servlet to build the displayed URL to subscribe.

checkArchives

checkArchives enumerates the repositories defined in the repositories map.

For each entry checkArchives

  • Extracts the URL of the RepoQueryIF Web service of the Repository and the FixInfo object

  • Checks that the status of the archives defined in the FixInfo archives is still valid

  • Calls the repNotifyFix method if the FixInfo archives is still not empty

repNotifyFix

repNotifyFix calls the notifyFix method of the Repository's RepoQueryIF Web service.

saveFixInfo

saveFixInfo enumerates the repositories defined in the repositories map and for each FixInfo object found the entries of the archives map to write the fixinfo.xml file.

Deployer

To call the Deploy Web service APIImpl and Relayer methods use a Deployer class.

Deployer implements a DeployerIF, which is defined like this:

interface DeployerIF extends DeployIF {

void setUrl(String url, String user, String passwd);

}

The DeployerIF interface implements the DeployIF interface.

Deployer is a facade class that forwards its requests to an actual deployer. A Deployer factory method has a parameter, which is the full class name of the actual deployer. The factory creates the actual deployer and its facade with the following snippet:

Class iclass = Class.forName(deployerclass);

DeployerIF di = (DeployerIF)iclass.newInstance();

return new Deployer(di);

The class name of the actual deployer is defined in the deployer-class element of rules.xml, which is represented in memory by the deployerClass member of RepositoryAuth and DefaultAuth. This deployer must implement a constructor without parameter and the DeployerIF interface. Three deployers are available:

  • JWSDPDeployer

  • AxisDeployer

  • HTTPDeployer

Starting with PageBox 0.0.11 Deployer, JWSDPDeployer, AxisDeployer and HTTPDeployer support token methods, DeployIF extending the TokenIF interface defined like this:

interface TokenIF {

DeployIF.Status frameSend(TokenFrame frame) throws RemoteException;

}

We describe these methods in the Token API implementation guide.

JWSDPDeployer

JWSDPDeployer uses the JAX-RPC implementation included in the Java WSDP.

The JWSDPDeployer constructor instantiates a Deploy stub generated with wscompile -gen:client.

The setUrl method of JWSDPDeployer sets the URL of the target PageBox Deploy service.

For each method of the DeployIF interface JWSDPDeployer calls the corresponding method of the stub.

We had a problem with JWSDP 1.2 for the Token API method, frameSend. It seems that this version doesn’t handle properly linked lists (translated in SOAP sequences) of objects. We did not retry with JWSDP 1.3 and JWSDP 1.5. The TokenFrame class used by the frameSend method is defined like this:

class TokenFrame implements Serializable {

String repUrl;

int nb;

LinkedList adjacencyList;

LinkedList msgList;

}

Therefore we serialize and unserialize the adjacencyList and msgList entries with ser and unser methods.

AxisDeployer

AxisDeployer uses the Axis JAX-RPC implementation.

AxisDeployer has a service member variable initialized like this:

DeployServiceLocator service = new DeployServiceLocator();

DeployServiceLocator is a factory class allowing instantiating the stub class, DeploySoapBindingStub with the Web service URL. DeployServiceLocator, DeploySoapBindingStub and a set of helper classes are generated with org.apache.axis.wsdl.WSDL2Java.

The setUrl method of JWSDPDeployer calls the getDeploy method of DeployServiceLocator to instantiate a DeploySoapBindingStub.

For each method of the DeployIF interface AxisDeployer calls the corresponding method of the stub.

HTTPDeployer

HTTPDeployer uses HTTP and calls a HTTPDeploy servlet implemented on target PageBoxes.

To the opposite of JWSDPDeployer and AxisDeployer HTTPDeployer doesn't use JAX-RPC and XML.

The setURL method of HTTPDeployer stores:

  • The URL of the target HTTPDeploy servlet

  • The base 64 encoded string that contains the user name and password to use when the target HTTPDeploy servlet is protected by basic authentication

For each method of the DeployIF interface HTTPDeployer opens a HTTP connection to the target HTTPDeploy servlet, serialize the method parameters and parses the HTTPDeploy servlet response.

The first field in the POST request contains a verb that depends on the DeployIF method:

DeployIF method

Verb

add

ADD

delete

DELETE

rename

RENAME

getArchPath

GETARCHPATH

getAudit

GETAUDIT

frameSend (from TokenIF)

FRAMESEND

Querier

To call the RepoQuery Web service APIImpl, Relayer and DynDns methods use a Querier class.

Querier implements a QuerierIF, which is defined like this:

interface QuerierIF extends RepoQueryIF {

void setUrl(String url, String user, String passwd);

}

The QuerierIF interface implements the RepoQueryIF interface.

Querier is a facade class that forwards its requests to an actual querier. A Querier factory method has a parameter, which is the full class name of the actual querier. The factory creates the actual querier and its facade with the following snippet:

Class iclass = Class.forName(querierClass);

QuerierIF qi = (QuerierIF)iclass.newInstance();

return new Querier(qi, log);

The class name of the actual querier is defined in the querier-class element of rules.xml, which is represented in memory by the querierClass member of RepositoryAuth and DefaultAuth. This querier must implement a constructor without parameter and the RepoQueryIF interface. Three queriers are available:

  • JWSDPQuerier

  • AxisQuerier

  • HTTPQuerier

Starting with PageBox 0.0.11 Querier, JWSDPQuerier, AxisQuerier and HTTPQuerier support token methods, RepoQueryIF extending the RepoTokenIF interface defined like this:

interface RepoTokenIF extends PageBoxLib.TokenIF {

void tokenRegister(String subscriber) throws RemoteException;

void tokenUnregister(String subscriber) throws RemoteException;

}

The RepoTokenIF interface extends the TokenIF class described above, which defines the. We describe this method in the Token API implementation guide.

JWSDPQuerier

JWSDPQuerier uses the JAX-RPC implementation included in the Java WSDP.

The JWSDPQuerier constructor instantiates a Query stub generated with wscompile -gen:client.

The setUrl method of JWSDPQuerier sets the URL of the target PageBox RepoQuery service.

For each method of the RepoQueryIF interface JWSDPQuerier calls the corresponding method of the stub.

We had a problem with JWSDP 1.2 for the Token API method, frameSend. It seems that this version doesn’t handle properly linked lists (translated in SOAP sequences) of objects. We did not retry with JWSDP 1.3 and JWSDP 1.5. The TokenFrame class used by the frameSend method is defined like this:

class TokenFrame implements Serializable {

String repUrl;

int nb;

LinkedList adjacencyList;

LinkedList msgList;

}

Therefore we serialize and unserialize the adjacencyList and msgList entries with ser and unser methods.

AxisQuerier

AxisQuerier uses the Axis JAX-RPC implementation.

AxisQuerier has a service member variable initialized like this:

RepoQueryServiceLocator service = new RepoQueryServiceLocator();

RepoQueryServiceLocator is a factory class allowing instantiating the stub class, RepoQuerySoapBindingStub with the Web service URL. RepoQueryServiceLocator, RepoQuerySoapBindingStub and a set of helper classes are generated with org.apache.axis.wsdl.WSDL2Java.

The setUrl method of JWSDPQuerier calls the getRepoQuery method of RepoQueryServiceLocator to instantiate a RepoQuerySoapBindingStub.

For each method of the RepoQueryIF interface AxisQuerier calls the corresponding method of the stub.

HTTPQuerier

HTTPQuerier uses HTTP and calls a HTTPQuery servlet implemented on Repositories.

To the opposite of JWSDPQuerier and AxisQuerier HTTPQuerier doesn't use JAX-RPC and XML.

The setURL method of HTTPQuerier stores:

  • The URL of the target HTTPQuery servlet

  • The base 64 encoded string that contains the user name and password to use when the target HTTPQuery servlet is protected by basic authentication

For each method of the RepoQueryIF interface HTTPQuerier opens a HTTP connection to the target HTTPQuery servlet, serialize the method parameters and parses the HTTPQuery servlet response.

The first field in the POST request contains a verb that depends on the RepoQueryIF method:

RepoQueryIF method

Verb

GetSubscribers

GETSUBSCRIBERS

Notify

NOTIFY

NotifyFix

NOTIFYFIX

frameSend (from TokenIF)

FRAMESEND

tokenRegister (from RepoTokenIF)

TKREGISTER

tokenUnregister (from RepoTokenIF)

TKUNREGISTER

HTTPDeploy

When JAX-RPC is used messages are parsed by a servlet part of the JAX-RPC implementation, JAXRPCServlet in case of JWSDP and AxisServlet in case of Axis. In case of HTTP implementation we must provide this servlet.

HTTPDeploy has a DeployImpl member instantiated on startup.

The init method of HTTPDeploy calls the init2 method of the DeployImpl instance.

The destroy method of HTTPDeploy calls the homonymous method of the DeployImpl instance.

HTTPDeploy expects POST requests and therefore implements the doPost method.

doPost reads the first field of the request, which is the verb and calls a parsing method according to its content:

Verb

HTTPDeploy method

ADD

add

DELETE

delete

RENAME

rename

GETARCHPATH

getArchPath

GETAUDIT

getAudit

FRAMESEND

frameSend (from TokenIF)

Each of these methods unserializes the remaining parameter, calls the homonymous method of the DeployImpl instance, serializes and returns its response.

RepoTest

The RepoTest class allows generating errors at different steps of the archive deployment and undeployment and Repository notification to facilitate testing.

RepoTest reads a repotest.txt file defined in the PageBox directory. repotest.txt should contain entries in one of these formats:

[archive_name | *] [NOTDEPLOYED | NOTRELAYED | ARCHPB | PBPB | NOTUNDEPLOYED | NOTCONTACTED]

@ [repository_url | *]

Where

  • The separator is any character supported by default by StringTokenizer - typically blank or tab

  • repository_url is the URL of a Repository's RepoQuery Web service

  • * stands for all archives in the first format and for all repositories in the second format

We describe the use of these entries in the exercise and exercise2 methods.

exercise

The exercise method is called by the DeployImpl's add and delete methods.

If the exercise method is called at the beginning of the add method the state parameter equals BEGIN_ADD.

If the exercise method is called at the beginning of the add method the state parameter equals END_ADD.

If the exercise method is called at the beginning of the delete method the state parameter equals BEGIN_DEL.

If the exercise method is called at the beginning of the delete method the state parameter equals END_DEL.

The exercise method returns a Status that depends on the first format lines of the repotest.txt file and on the state. In case of match (if the archive passed in parameter is defined in repotest.txt or if the * char is used) the code set in the returned Status object is defined in this table:

exercise state

repotest.txt action

Return code

BEGIN_ADD

NOTCONTACTED

NOTCONTACTED

BEGIN_ADD

NOTDEPLOYED

NOTDEPLOYED

BEGIN_ADD

NOTRELAYED

NOTRELAYED

END_ADD

ARCHPB

ARCHPB

END_ADD

PBPB

PBPB

BEGIN_DEL

NOTCONTACTED

NOTCONTACTED

BEGIN_DEL

NOTUNDEPLOYED

NOTUNDEPLOYED

END_DEL

ARCHPB

ARCHPB

END_DEL

PBPB

PBPB

In all other cases exercise returns a Status object with an OK code.

exercise2

exercise2 is called by the repNotifyFix method of the DynDns singleton.

exercise2 return false if the repotest.txt file contains a second format line with the name of the Repository or *. Otherwise exercise2 returns true.

Contact:support@pagebox.net
2002-2004 Alexis Grandemange. Last modified .