Geronimo_MoinMoin_wiki > Geronimo_Management_API
Added by Confluence Administrator, last edited by Confluence Administrator on Aug 02, 2006

Geronimo Management API

Current Plans

Recall that to access an arbitrary GBean, you can call Geronimo methods like Kernel.invoke(!ObjectName, "!MethodName"), you can call JSR-77 methods like MEJB.invoke("!ObjectName", "!MethodName"), or you can create a proxy for the GBean where the proxy implements some interface and under the covers all calls to the proxy methods are converted into calls to the GBean. Currently the web console uses the Kernel.invoke(!ObjectName, "!MethodName") style to communicate with the running GBeans.

Dain, David J, and Aaron talked by IRC, and concluded that to improve the options for writing management clients, we should:

  1. Create interfaces for all important GBeans
  2. Make GBeans implement their interfaces, and register the interfaces with their G!BeanInfo
  3. Create an !ObjectName-to-proxy factory, where you pass it an !ObjectName and the interfaces you're interested in seeing, and it returns a proxy that implements any of those that the G!BeanInfo says that the GBean under that !ObjectName supports.
  4. If we want to ease navigation between proxies (say, server to applications deployed in the server, or application to individual modules within the application) then any logic for that should be implemented in a helper class.
  5. GBean framework plumbing supporting certain JSR-77 attributes should be removed, and the individual GBeans should implement those attributes if they represent JSR-77 objects. This will be added to JIRA.

In More Detail

  1. GBean Interfaces – right now, a GBean can just provide a bunch of attributes and operations and add them individually to its G!BeanInfo (which effectively controls what management calls are available). We plan to add an interface for each GBean defining its management API, and have the GBean add the whole interface to its GBeanInfo in one shot. That would let you create a proxy to the GBean based on that interface. There may actually be an interface hierarchy, such that common Tomcat/Jetty fucntions could be encapsulated in WebContainer and then there could still be JettyWebContainer and TomcatWebContainer extending that. We intend to create interfaces representing most JSR-77 concepts, though there will be many interfaces and GBeans beyond what JSR-77 alone offers. Any GBeans that represent JSR-77 object must inherit from J2EEManagedObject (the root JSR-77 interface), but other GBean interfaces do not need to.
  2. Each GBean will implement its management interface, if for no other reason than to prove that it actually provides all the necessary methods. As mentioned above, it should call G!BeanInfoFactory.addInterface(interface) when constructing its G!BeanInfo.
  3. Given an !ObjectName, the developer needs some way to create a working proxy from that. You can give the !ProxyManager an interface and have it create you a proxy, but you may not know whether an !ObjectName for a web container represents a JettyWebContainer or a TomcatWebContainer, for example. We will create a factory where you can ask for a proxy implementing certain mandatory and optional interfaces (in that example, GenericWebContainer might be mandatory and JettyWebContainer and TomcatWebContainer optional). It'll give you back a proxy implementing the mandatory interface(s) and any of the optional interfaces that the underlying GBean actually supports. It'll figure this out by getting the G!BeanInfo for the GBean and inspecting its list of interfaces and comparing that to the list of interfaces you provided. The developer will still need to cast, but they can do something like this:
    • WebContainer wc = (WebContainer)Factory.getProxy(objectName,
                        WebContainer.class, new Class[]{
                          JettyWebContainer.class, TomcatWebContainer.class});
      if(wc instanceof TomcatWebContainer) {
          // do Tomcat stuff
      } else if(wc instanceof JettyWebContainer) {
          // do Jetty stuff
      } else {
          // do totally generic stuff
      }
          
  1. I might provide a helper class with a method like:
    • public GeronimoApplication getApplications(J2EEServer server);
          
    • Under the covers, this would use the factory from setp 3, but it would allow someone unfamiliar with the classes and interfaces to navigate around without knowing exactly what interfaces to pass to the factory and cast the result to. In any case, this would be a helper class that is (at least for now) outside the core management infrastructure.
  1. Currently the "GBean framework" will respond to the methods defined in J2EEManagedObject regardless of whether the underlying GBean implements those methods. This is not actually desirable. We'd like to remove that logic from the "GBean framework" and require that GBeans implement those methods if they should be available. I'm not sure where the actual code for that is yet.

All together these should make it more viable to write management code based on calling into interfaces, instead of management code based on passing !ObjectNames and Strings and casting every result.

Original Proposal

So this comes from looking at JSR-77 and the code that the web console uses to interact with Geronimo.

Here's an example:

    ObjectName gbeanName = ...;
    DataSourceInfo info = new DataSourceInfo();
    info.setObjectName(gbeanName);
    info.setName(gbeanName.getKeyProperty("name"));
    try {
        info.setJndiName((String) kernel.getAttribute(gbeanName,
                "globalJNDIName"));
        info.setState((Integer) kernel.getAttribute(gbeanName,
                "state"));
        //check if user asked this connection to be tested
        if ((gbeanName.toString().equals(name)) && (check)) {
            info.setWorking(true);
            try {
                Object cf = kernel.invoke(gbeanName, "$getResource");
                testConnection(cf);

At heart, this is non-portable code, because it interacts with the kernel, which is produced by a call like

    kernel = KernelRegistry.getSingleKernel();

However, this is quite close to JSR-77 code. Like kernel, the JSR-77 ManagementEJB is built on calls like this:

public Object getAttribute(ObjectName name, String attribute) throws ...
public Object invoke(ObjectName name, String operationName,
                     Object[] params, String[] signature) throws ...

So the code above could be changed to use the ManagementEJB instead of kernel, and it would be close to portable – the missing link being that JSR-77 doesn't define $getResource as a legitimate function of a JCAManagedConnectionFactory or JDBCDataSource (the closest JSR-77 objects to the example above).

So what's the problem?

Well, at heart, I don't like code that looks like this:

String value = (String) service.getAttribute("object", "attribute");
Something something = (Something) service.invoke("object", "method",
                       new Class[]{arg_types}, new String[]{args});

It's pretty hard to code to an API like that. If you're determined to write a portable JSR-77 management tool, then you need to learn JSR-77 by heart, and too bad. But otherwise, how can you know what object names to use, what method names to use, what methods take what argument types, what attributes are available, what to cast the results to, and so on? Well, you can look it up, but can you imagine writing more fragile code? Code completion doesn't help, the compiler and IDE can't catch your problems, etc. If you're not familiar with the API, there's nothing to do but read documentation or browse code. But Geronimo has a ton of code, and if you're given an arbitrary !ObjectName, there's no way to figure out where in all that code is the class that you should cast this sucker to.

To try to clarify my concerns:

  • Avoid requiring passing cryptic parameters to the API (object name, method name, argument Class types, etc.)
  • Avoid casting what you get back from the API (return types of Object, Set, etc.)
    • Avoid needing to know what to cast it to
  • Make it easy to navigate from X to Child-of-X (especially not "get names-of-children, make cryptic call to get instances-of-children as Objects, then figure out what child types should be and cast to real child types")
    • Related: Make it easy to learn the API by making it immediately obvious "if I have a Foo, what child objects does Foo have, and what types do they have?"

So what's the solution?

I'm imagining an API like this:

public interface Factory {
    public J2EEDomain[] getDomains();
}

public interface J2EEDomain {
    public J2EEServer[] getServers();
}

public interface J2EEServer {
    public J2EEDeployedObject[] getDeployedObjects();
    public JVM getJavaVMs();

// everything down to here mirrors JSR-77, everything below is non-JSR-77

    public CORBAContainer getCORBAContainer();
    public WebContainer getWebContainer();
    public EJBContainer getEJBContainer();
    public ThreadPool[] getThreadPools();
}

public interface WebContainer {
    public ThreadPool getAcceptThreadPool();
    public WebConnector[] getConnectors();
    public (String?) getJSPCompiler();
}

public interface WebConnector {
    public InetSocketAddress getListenAddress();
}

...

So this would be a mix of JSR-77 and not JSR-77.

On the JSR-77 side, it would include interfaces representing the JSR-77 components, and trying to keep to similar names and similarly named properties. The types would change – for example, a JSR-77 J2EEDomain.getServers() method would return a list of !ObjectNames for servers, and in this API it would instead return a list of J2EEServer objects. But the idea is that someone familiar with JSR-77 programming would find it easy to get up to speed.

However, the API would also include non-JSR-77 components. For example, there's no "!WebContainer" object in JSR-77, that would let you inspect and alter the listen port of Tomcat/Jetty, or configure the number of threads used, or which compiler is used for JSPs, and so on. The reason I want to extend beyond JSR-77 is that I want to give direct API access to all the things that something like the web console would need to do its job.

If we don't actually want the API to include references to other objects in the API, then we can have some kind of master to bootstrap the process and manage relationships. Something like:

public interface Manager {
    // root properties
    J2EEDomain[] getDomains();

    // domain properties
    J2EEServer[] getServers(J2EEDomain domain);
    SecurityRealm[] getSecurityRealms(J2EEDomain domain);

    // server properties
    J2EEDeployedObject[] getDeployedObjects(J2EEServer server);
    J2EEApplication[] getApplications(J2EEServer server);
    J2EEAppClientModule[] getAppClients(J2EEServer server);
    WebModule[] getWebModules(J2EEServer server);
    EJBModule[] getEJBModules(J2EEServer server);
    ResourceAdapterModule[] getRAModules(J2EEServer server);
    J2EEResource[] getResources(J2EEServer server);
    JCAResource[] getJCAResources(J2EEServer server);
    JDBCResource[] getJDBCResources(J2EEServer server);
    JMSResource[] getJMSResources(J2EEServer server);
    JVM[] getJavaVMs(J2EEServer server);

    //application properties, resource properties, etc.
    ...

or perhaps

public interface Manager {
    // root properties
    J2EEDomain[] getDomains();

    // domain properties
    J2EEServer[] getServers(String domainName);
    SecurityRealm[] getSecurityRealms(String domainName);

    // server properties
    J2EEDeployedObject[] getDeployedObjects(String serverName);
    J2EEApplication[] getApplications(String serverName);
    J2EEAppClientModule[] getAppClients(String serverName);
    WebModule[] getWebModules(String serverName);
    EJBModule[] getEJBModules(String serverName);
    ResourceAdapterModule[] getRAModules(String serverName);
    J2EEResource[] getResources(String serverName);
    JCAResource[] getJCAResources(String serverName);
    JDBCResource[] getJDBCResources(String serverName);
    JMSResource[] getJMSResources(String serverName);
    JVM[] getJavaVMs(String serverName);

    //application properties, resource properties, etc.
    ...

This would give you an entry into the domain and related objects, and also let you navigate between objects without needing to manually do things based on the !ObjectNames that all the JSR-77 classes return when you ask about their children.

How could this be implemented?

We have GBeans that expose all the necessary properties and functions already. The only issue is providing an implementation of this API that accesses them. In many cases, we might supply interfaces that the GBeans could implement. Still, here might be some glue code between the interfaces defined above and the actual GBeans.

The interfaces themselves probably wouldn't be as extensive as the GBeans. For example, the !WebContainer interface would likely have the least-common-denominator configuration options across Jetty and Tomcat (and maybe even less than that). Still, a programmer always has access to the raw GBeans to go deeper than the common API allows. But this way you can use normal OO code for the bulk of your management tasks, and only go to the paradigm that started this page if you really need to.

Note that it is not my intention to start a "build the world" project to provide a totally comprehensive management API for Geronimo. Instead, I'd plan to start small, and grow it as tools such as the web console expand into new areas. "On demand" development of the management API, I guess.

How would this be integrated into Geronimo?

Well, at the source level, it would be a Geronimo module. I don't think it would need much presence in the server runtime – mostly it's just an outside wrapper around the GBeans in the server already. So a tool would create a new management factory, get an instance connected to the desired Geronimo server, and then start making calls against the API which would be translated under the covers into calls against the Geronimo MEJB or kernel and GBeans.

It only makes sense for J2EE configurations of Geronimo. That is, if you start the Geronimo kernel and load it with nothing but a mail server GBean, you would not be able to use this API to manage it. That's OK though, you still have JMX/GBean access to manage totally arbitrary Geronimo configurations – this is just to help tools for the common J2EE configurations of Geronimo.

Still, some methods might return null or 0-length arrays if certain features are disabled. For example, if you remove the EJB container from your configuration, then you just wouldn't be able to navigate to that part of the management API tree.

What's the advantage? Is it worth the trouble?

Well, back to the example at the top of the page. I'd much rather see it like this (where JDBC!DataSource is an interface in the management API, based on the JDBC!DataSource definition in JSR-77):

    JDBCDataSource ds = ...;
    DataSourceInfo info = new DataSourceInfo();
    info.setName(ds.getName());
    try {
        info.setJndiName(ds.globalJNDIName);
        info.setState(ds.getState());
        //check if user asked this connection to be tested
        if ((ds.getName().equals(name)) && (check)) {
            info.setWorking(true);
            try {
                Connection con = ds.createConnection();
                testConnection(cf);

In fact, maybe the tool could just dispense with the DataSourceInfo altogether and just use JDBCDataSource as its object model, depending on whether the DataSourceInfo class has any UI-specific stuff in it.

In any case, note that there are no Strings in the code above. It's all verified by the compiler, you can inspect an unfamiliar interface via code completion and popup !JavaDoc, etc.

I think something like this would make it much easier to develop management tools such as the web console. By using this API the tools would end up being specific to Geronimo, but that's OK for our purposes – we're trying to make Geronimo more usable not write a console that can manage any J2EE server. And if we're going to be reusing code across tools, I would much rather it be an API layer like this, not copying and pasting kernel invocations with string arguments and so on.

Discussion

Please confirm that the API:

  • is a set of EXTERNAL wrapper APIs for management ONLY when Geronimo assumes the "J2EE personality"
    • Ans: yes, though WRT "external" it may end up with some helper GBeans deployed in Geronimo. The web console requires this, and I haven't yet looked to see whether it can be avoided.
  • provides programmer friendly semantics to access the guts of Geronimo, for management/monitoring/tuning purposes ONLY (enforced by available operations)
    • Ans: yes
  • a subset of this API provides portable JSR-77 access to Geronimo, for the benefits of other JSR-77 compliant tools
    • Ans: no – strict JSR-77 does not use any API other than the Management EJB. We will have JSR-77 "lookalike" functions. That is, if JSR-77 requires that you have a JVM object with a "vendor" property, we will have a JVM interface with a getVendor method. However, to remain portable, JSR-77 code would still need to access it using MEJB.getAttribute("jvm_object_name", "vendor") and not by calling our methods. There's nothing we can do to make life easier on portable JSR-77 developers – I'm just hoping that our easy-to-use alternative won't look alien if you happen to be familiar with JSR-77.

Suggestion... create this layer in a reusable(extensible?) manner, to enable the creation of other G-management APIs applicable when Geronimo assumes other "personalities" (J2EE subset, J2EE superset, no J2EE - purely embedded, etc)

  • Ans: perhaps, though I'm not clear on how to make this that flexible yet still achieve all the other goals. For now, I'd like to solve the first problem first and consider refactoring to solve additional problems later.