JBossTools - Delegating Servers Via Subsystems

Version 3

    This is an attempt to outline a proposed new API for handling server types that need to delegate their behavior based on settings on a server.

     

    Current Situation

     

    JBossTools' component "ASTools" currently has an API called delegating server behavior. This framework allows implementations of each of the core sever behavior logic (starting, stopping, publishing, restarting modules, etc).  The problem here is that the server may only be in 1 mode at a time. Currently there exist 2 primary modes: Local and RSE.  RSE Mode involves using the eclipse RSE framework for handling communication with remote systems over ssh / scp / sftp / etc.

     

    The current implementation is insufficient for several reasons. First, it is a piece-meal API designed by repeated bandaids, and so is not very 'clean'. The second, and more important, is that it does not allow for mixing implementations. For example, you cannot have a server that starts via a local shell script (which perhaps communicates with a remote server on its own), shuts down via a management command, and publishes via RSE / SCP.

     

    Currently, the server is in either local mode, or rse mode.  Local mode is a mix of behavior depending on management, or eclipse's debug launching mechanism. RSE mode is a mix of management commands, filesystem transfers, and remote shell commands. Some actions are arbitrarily performed using management, while others depend on commands to be executed on command line.   These details sometimes change arbitrarily based on the version of the server, with no clear explanation in the API.

     

    The Proposal

     

    This proposal suggests splitting each piece of logic up into self-contained units, registered via an extension point and controlled by a single entry-point. It further proposes a method such that a system can depend on another system, and have it be properly resolved.

     

    To begin, we'll look at the extension point used to declare a typical self-contained subsystem:

     

      <extension   point="org.jboss.ide.eclipse.as.wtp.core.serverSubsystem">

      <subsystem

             serverTypes="as7,wf8"

             class="org.test.ManagementPublishSystem"

             system="publish"

             id="publish.management"

             name="Management Publishing">

                 <properties>

                        <property key="someKey" value="someVal"/>

                 </properties>

                 <dependencies>

                       <requires system="management"/>

                       <requires system="publish" subsystem="publish.localZip"/>

                 </dependencies>

      </subsystem>

      </extension>

     

    What we can see above is an implementation of the publish system. The subsystem's id is "publish.management" and so it represents a publish system. It has a property key/value pair which can be used for filtering. For its first dependency, it depends on any available implementation of the "management" system, with no specific requirements as to which. Most server types will have only one implementation of the management system, and if there are more, at least one will be marked as a default.  It also depends on another implementation of the publish system, but requires the specific system publish.localZip  This is because the management publisher may first zip the module in a temporary location before sending it over the wire. We also note this publish  subsystem is valid for servers as7 and wf8.

     

    One thing to note here is that there is no single ID for a subsystem implementation.  The framework will allow two subsystems with the same ID so long as they do not have overlapping servers for which they are valid.  So for example,  they may declare one module state controller "modules.management" for as7 and wf8,  and have another "modules.management" using JMX to start and stop the modules for as less-than 7.  Despite the id's of the systems being the same, this will not register as an error. Each server type  has only 1 unique implementation for each subsystem id, and so this is seen as valid.

     

    What would be an error is if two different implementations of modules subsystem were declared for the same server type, and they both had the id "modules.management". One example of this would be for as7. It'd be possible to have two ways to control the module state:  one over management, and one over the legacy JMX. If both of these had same id and were both registered for as7 server types, this would be an error. If an extender wishes for two competing implementations of the subsystem to be available for the same server version, they should give them different id's.  For example, modules.management and modules.jmx.

     

    The Code

     

    The class in the extension point must extend org.jboss.ide.eclipse.as.wtp.core.server.behavior.AbstractSubsystemController.

     

    The entry point for accessing subsystems is org.jboss.ide.eclipse.as.wtp.core.server.behavior.SubsystemModel.   This class is a singleton which loads the data directly from the extension points. It will resolve and create a SubsystemType object for each subsystem declared, and will store them in a map<String, HashMap<String, SubsystemType>>  where the map's key is a SERVER TYPE id, and the inner map has key=subsystemId and value = the subsystem type.

     

    Dependencies will be checked and validated.  Any subsystem missing required systems or subsystems will be marked as currently invalid, and errors will be logged.

     

    The remainder of this class is mostly dealing with entry points to create a new instance of a given subsystem controller ID for a given server type. A new instance is created upon each request. The subsystems in general should try to not keep state, but may if they are confident they will be re-used and able to keep their state accurate and that their caller will not discard them and create a new one later.  Keeping state is not dangerous, however, the next request may be made on a new controller instance, so the state may not be present in the next call (if the client is misbehaving).

     

    Requests against the SubsystemModel to create a given subsystem implementation has the effect of initializing the new subsystem (with superclass AbstractSubsystemController)  with a variety of variables, such as an environment which may be passed in, the server for which the subsystem was created,  the subsystem type (an internal construct from the SubsystemModel) and others.

     

    The AbstractSubsystemController superclass of your subsystem also provides ways to resolve your dependencies. The environment passed in when creating your subsystem may also indicate required properties to use when resolving your dependencies.

     

    Take this example:

        The server knows it needs to publish, and it knows it needs to do it to a remote system. Let's say the server is configured to zip the archive and send it over scp into the deployments folder of an as7/wf8 installation.   Let's also assume there is only 1 publish system (publish.standard), and the publish system depends on an implementation of the fileio system. Let's assume there are 2 implementations of fileio,   1) fileio.local  and 2) fileio.rse.

     

       In the above example, the publish.standard system cannot specifically depend on either fileio.local or fileio.rse.  We could get around this by declaring 2 different publish systems that have identical code. We could have a publish.local and a publish.rse, where publish.local subsystem has a hard dependency on fileio.local,   and publish.rse has a hard dependency on fileio.rse.  But this seems like a lot of duplication, and it would be better if you could simply use one publish subsystem (publish.standard) and have it resolve the correct fileio implementation based on some property.   In this way, you can use the framework to create a new publish.standard, and, during that creation, pass in an environment that indicates when resolving the fileio system, to make sure it has property rse=true, or iotype=rse, or whatever other flags are specified on the subsystem's declaration.

     

     

    Server Behavior

     

    A server which wishes to fully use a delegating style for its primary behavior will extend ControllableServerBehavior.    The ControllableServerBehavior splits up the primary tasks of the wtp server behavior into separate pre-defined subsystems:  publish,  modules,   launch,   and shutdown.   This class will override most of the wtp's ServerBehavior methods to allow all such requests to be sent to a proper subsystem registered for the task.

     

    The ControllableServerBehavior also keeps 1 hashmap to keep current state.  Any subsystem that would like to guarantee state they request remains around even in if the calling client discards the subsystem instance is free to put any data in the ControllableServerBehavior's "shared data" map, or pull data from there at any time. Shared data can be notoriously volatile, though, so each subsystem should evaluate whether it makes sense to put data there or simply insist their clients hold onto the subsystem reference.

     

    Each of the required subsystems (publish,  modules,   launch,   and shutdown) have interfaces associated with them:

              IPublishController

              IModuleStateController

              IServerShutdownController

              ILaunchServerController

     

    Each of these interfaces are meant as delegates for the portion of behavior that fits with that interface. So all tasks having to do with shutdown will be contained in the IServerShutdownController. This logically groups all the methods and allows for separation of the tasks.

     

    The branch can be found here: https://github.com/robstryker/jbosstools-server/tree/JBIDE-15915/