PageBox |
|
|
|
|
|
Presentation | Install | User guide | Developer guide | Programming | Port | Repository | PageBox | Release notes |
PageBox for JavaForewordObjectiveThis document presents the implementation of PageBox. To present the design we use a representation that loosely follows UML. AudienceProgrammers who want:
This documentation assumes some knowledge of UML, Java and J2EE. Design choices
Use casesThe Repository interfaces with:
We distinguish:
UpdateThe 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. DeploymentPageBox 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:
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. DeployImplThe Deploy Web service is implemented in the DeployImpl class. The DeployImpl class implements the DeployIF class, which is defined like this:
Where:
The UrlUser class is defined like this:
Where
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:
DeployImpl uses two XML files:
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:
The table below describes the members of the Rule class:
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:
Where:
Two classes inherit from PublisherAuth:
DefaultAuth is defined like this:
Where:
RepositoryAuth is defined like this:
The table below describes the members of the RepositoryAuth class:
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:
Where
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,
StatusStarting with version 0.0.9 the add and delete methods return an object of class DeployIF.Status defined like this:
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. addadd can return a Status object with a code OK, NOTCONTACTED, NOTDEPLOYED, NOTRELAYED, ARCHPB or PBPB. The archive map is set accordingly:
add
Here is the explanation of the second item:
deletedelete can return a Status object with a code OK, NOTCONTACTED, NOTUNDEPLOYED, ARCHPB or PBPB. The archive map is set accordingly:
delete
Here is the explanation of the second item:
getArchPathgetArchPath 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". getAuditgetAudit 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. addToPbArchsThere are three steps in addToPbArchs:
Relay managementaddToPbArchs 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 checkaddToPbArchs 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. InstallationIf the archive is already installed addToPbArchs
If the archive is not yet installed addToPbArchs
deleteFromPbArchsThere are three steps in deleteFromPbArchs:
Security checkdeleteFromPbArchs 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. UninstallationdeleteFromPbArchs:
renameRepositoryrenameRepository 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. installIf the installation is a delta (jardiff) update install
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. uninstallIf
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
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. InstallHelperInstallHelper 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. installinstall runs the dbcreate script if it is defined and tries to find an installation class called Install.class in
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. InstallClassLoaderInstallClassLoader is a simple class loader that extends the ClassLoader. The InstallClassLoader constructor has five parameter set by the install and uninstall methods of DeployImpl:
The constructor creates a protection domain with:
The most important method of InstallClassLoader is loadClass. loadClass is called to load Install.class and classes used by the Install class.
RelayerRelayer class extends Thread. Its constructor starts a worker thread responsible for deployment relaying. runThe run method (called in the worker thread) waits for requests queued on a queue linked list. For each deployment request found on queue run:
deployThe 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. relayrelay 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.
repNotifyrelay records deployment status and repNotify calls the Notify method of the Repository's RepoQuery Web service to send the deployment status to the Repository. ReinstallDynamic 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:
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. initThe 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
ConstructorThe 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. runrun calls the retry method with isStartup = true. retryFor each archive in the archives map the retry method:
DynDnsBackgroundPermanent 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:
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.
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. ImplementationThe 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. ConstructorThe 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 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:
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:
Where
Eventually the constructor starts the thread. notifyFixnotifyFix 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:
In case of success notifyFix removes the corresponding Repository entry from the repositories map. runThe run method runs in the created thread and as far as a toStop variable equals false periodically
The end method sets the toStop variable to true and calls notify to trigger the thread termination. registerThe register method:
We wrote a small DynDns emulator to test the implementation.
The getHost method returns:
The getHost method is called by the Update servlet to build the displayed URL to subscribe. checkArchivescheckArchives enumerates the repositories defined in the repositories map. For each entry checkArchives
repNotifyFixrepNotifyFix calls the notifyFix method of the Repository's RepoQueryIF Web service. saveFixInfosaveFixInfo 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. DeployerTo call the Deploy Web service APIImpl and Relayer methods use a Deployer class. Deployer implements a DeployerIF, which is defined like this:
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:
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:
Starting with PageBox 0.0.11 Deployer, JWSDPDeployer, AxisDeployer and HTTPDeployer support token methods, DeployIF extending the TokenIF interface defined like this:
We describe these methods in the Token API implementation guide. JWSDPDeployerJWSDPDeployer 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:
Therefore we serialize and unserialize the adjacencyList and msgList entries with ser and unser methods. AxisDeployerAxisDeployer uses the Axis JAX-RPC implementation. AxisDeployer has a service member variable initialized like this:
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. HTTPDeployerHTTPDeployer 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:
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:
QuerierTo call the RepoQuery Web service APIImpl, Relayer and DynDns methods use a Querier class. Querier implements a QuerierIF, which is defined like this:
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:
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:
Starting with PageBox 0.0.11 Querier, JWSDPQuerier, AxisQuerier and HTTPQuerier support token methods, RepoQueryIF extending the RepoTokenIF interface defined like this:
The RepoTokenIF interface extends the TokenIF class described above, which defines the. We describe this method in the Token API implementation guide. JWSDPQuerierJWSDPQuerier 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:
Therefore we serialize and unserialize the adjacencyList and msgList entries with ser and unser methods. AxisQuerierAxisQuerier uses the Axis JAX-RPC implementation. AxisQuerier has a service member variable initialized like this:
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. HTTPQuerierHTTPQuerier 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:
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:
HTTPDeployWhen 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:
Each of these methods unserializes the remaining parameter, calls the homonymous method of the DeployImpl instance, serializes and returns its response. RepoTestThe 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:
Where
We describe the use of these entries in the exercise and exercise2 methods. exerciseThe 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:
In all other cases exercise returns a Status object with an OK code. exercise2exercise2 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 |
|