Skip to main content

QBean Primer

git checkout qbean-primer

The tutorials are organized within their specific branches, as indicated at the start. While some are based on the main branch, others build upon previous tutorials. You have the option to manually type out the tutorial or simply use the checkout command to switch to the relevant branch. In this case, you want to git checkout qbean-primer.

TL;DR

This section offers insight into some basic but detailed aspects of jPOS QBeans, which are not commonly used. While it's not essential, a quick read is recommended to gain a general understanding of the underlying mechanics.

The QBean interface

The QBean interface is quite simple:

public interface QBean {}
void init () throws Exception;
void start () throws Exception;
void stop () throws Exception;
void destroy () throws Exception;
int getState ();
String getStateAsString ();
}

Please be patient as we delve into some complex aspects of Q2's inner workings, particularly its relationship with JMX. However, there's a bright side: as we'll demonstrate in the upcoming tutorial, this process becomes significantly more straightforward thanks to the QBeanSupport class, a helpful tool your QBeans can extend to streamline their functionality.

In ths example, we'll write a very simple QBean, called MyQBean.

Sources

MyQBean.java

Located in src/main/java/org/jpos/tutorial/MyQBean.java the class looks like this:

package org.jpos.tutorial;

import org.jpos.q2.QBean;

public class MyQBean implements MyQBeanMBean {
int state;

@Override
public void init() {
state = QBean.STARTING;
printState();
}

@Override
public void start() {
state = QBean.STARTED;
printState();
}

@Override
public void stop() {
state = QBean.STOPPED;
printState();
}

@Override
public void destroy() {
state = QBean.DESTROYED;
}

@Override
public int getState() {
return state;
}

@Override
public String getStateAsString() {
return state >= 0 ? QBean.stateString[state] : "Unknown";
}

private void printState() {
System.out.println (getClass().getCanonicalName() + ":" + getStateAsString());
}
}

As you can see, this is very simple, we just need to keep the state in order to properly return the state of the QBean to the Q2 server.

You may notice that this class implements MyQBeanMBean. This is required by the JMX MBeanServer. Q2 registers its QBean with the JVM's MBeanServer.

So we need to implement MyQBeanMBean, which is very simple as well:

MyQBeanMBean.java

package org.jpos.tutorial;

import org.jpos.q2.QBean;

public interface MyQBeanMBean extends QBean { }

MyQBean just extends QBean (with its init, start, stop, destroy methods) and make them available through JMX to programs such as jconsole or Java Mission Control as well as custom monitoring systems.

60_myqbean.xml

Finally, we need to create the QBean descriptor. We place it in src/dist/deploy/60_myqbean.xml so that it's picked up by the gradle installApp task. It looks like this:

<myqbean class="org.jpos.tutorial.MyQBean" />

Running

If you call gradle installApp && bin/q2 you'll see a new service running:

<log realm="stdout" at="2023-11-13T14:52:54.858117" lifespan="504ms">
org.jpos.tutorial.MyQBean:Starting
org.jpos.tutorial.MyQBean:Started
</log>

If you add -Dcom.sun.management.jmxremote=true to the q2 startup script (located in src/dist/bin/q2) and execute jconsole on another terminal, you can drill down to the Q2->myqbean screen where you can execute some of the "published operations".

"Published operations" in this context mean all the interfaces exposed in the MyQbeanMBean class that provides JMX metadata to your MyQBean class.

You can see the name 'myqbean' in that screenshot. That's taken from the top level XML descriptor:

<myqbean class="org.jpos.tutorial.MyQBean" />
^^^^^^^

This is equilvalent to:

<qbean name='myqbean' class='org.jpos.tutorial.MyQBean' />

The top level element qbean is customary, but you could use anything else:

<anyname name='myqbean' class='org.jpos.tutorial.MyQBean' />

anyname would work just as well.

So far, we know a QBean descriptor:

  • needs a name, used to publish it with the MBeanServer.
  • the name can be taken from a name attribute.
  • If there's no name attribute, Q2 uses the top level element name as the QBean's name.
  • It needs a class attribute defining the class that implements this QBean.

If we take a look at the 00_logger.xml QBean, we'll notice that there's no class attribute. In this case, the descriptor looks like this:

<logger name="Q2">
...
...
</logger>

For commonly used QBeans, such as logger, sysmon, transaction-manager, Q2 uses a resource called QFactory.properties that do the mapping. The file is located here and looks like this:

logger=org.jpos.q2.qbean.LoggerAdaptor
shutdown=org.jpos.q2.qbean.Shutdown
sysmon=org.jpos.q2.qbean.SystemMonitor
txnmgr=org.jpos.transaction.TransactionManager
qmux=org.jpos.q2.iso.QMUX
qserver=org.jpos.q2.iso.QServer
channel-adaptor=org.jpos.q2.iso.ChannelAdaptor
qexec=org.jpos.q2.qbean.QExec
config=org.jpos.q2.qbean.QConfig
xml-config=org.jpos.q2.qbean.QXmlConfig
sshd=org.jpos.q2.ssh.SshService
ssm=org.jpos.q2.security.SMAdaptor
ks=org.jpos.q2.security.KeyStoreAdaptor
templates=org.jpos.q2.qbean.TemplateDeployer
prometheus=org.jpos.metrics.PrometheusService
...
...

That means that when configuring one of those standard QBeans, you can omit the class attribute, e.g.:

<qbean class="org.jpos.q2.qbean.SystemMonitor" name="sysmon" logger="Q2" />

can be written as just:

<sysmon logger="Q2" />

Shutdown

Just to show you around in this initial walk through, you can see there's a QBean called org.jpos.q2.qbean.Shutdown.

The implementation is very simple:

package org.jpos.q2.qbean;

import org.jpos.q2.QBeanSupport;

public class Shutdown extends QBeanSupport {
public void startService() {
getServer().shutdown ();
}
}

If you look at the stop script located in src/dist/bin/stop you'll see a line like this:

 echo '<shutdown/>' > deploy/shutdown.xml

That basically creates a descriptor called shutdown.xml the the following content:

<shutdown />

Because QFactory.properties has a mapping shutdown=org.jpos.q2.qbean.Shutdown that happens here is that Q2 will start the Shutdown service that would in turn initiate the shutdown sequence.

About JMX

Explaining JMX (Java Management Extensions) goes beyond what we cover in this tutorial. If you're already using JMX within your organization, you're likely familiar with its functionalities. If not, it's important to know that jPOS utilizes JMX internally, but you don’t need to be concerned with its intricacies. However, for completeness, let's touch upon some basics.

JMX serves as a proxy to the actual object instances registered in the JVM's MBeanServer. This system was established before Java supported annotations. It identifies available attributes and operations in an MBean through a simple naming convention. To expose a class as an MBean, create an interface with the base class name followed by the MBean suffix. For example, for a class named MyQBean, you would create an interface called MyQBeanMBean to expose certain methods.

If you wish to use JMX tools like jconsole or JMC to monitor a component's load, and you have a double getLoad() method, simply include this method in your MyQBeanMBean interface to make it accessible.

The creation of MyQBeanMBean was necessary for the MBeanServer to recognize the object as a proper MBean.

In the next section, we’ll explore how the QBeanSupport helper class simplifies this process. Unless you're planning to add extra attributes and operations to your QBean, you won’t need to create these *MBean interfaces. QBeanSupport implements a handy QBeanSupportMBean interface that exposes the basic QBean operations to JMX.

It’s worth noting the distinction between QBean and MBean. A QBean is an interface defining the lifecycle of jPOS Q2 services (referred to as QBeans and defined in QBean descriptors). In contrast, an MBean is an object that can be registered with the JMX MBeanServer. In jPOS, a QBean implementation also serves as an MBean, although the terms are similar but represent different concepts.

Finally, JMX exposes "attributes" (basically Bean-like properties with their getters and setters) and "operations" which are just public methods that return void.