© IntegrationWise, 2025
The webMethods IntegrationServer provides several lesser known features and extension points, some more useful than others, for example:
In this post we'll explore these features in detail.
For administrators of the IntegrationServer it's important to be able to tightly control the configuration of the IntegrationServer.
One great help is CommandCentral (CCE), which offers the possibility to compare the configuration of two IS's. There are more than
600 documented parameters in the $IS_INSTANCE_HOME/config/server.cnf
file. Knowing them all and managing them is a challenge,
to say the least. Wouldn't it be nice to know when a parameter is changed, and what the previous and current value is? Instead of
regularly comparing IS's in CCE?
During runtime, the Extended Settings of the IntegrationServer are kept in the same place as the standard System properties.
If you create Java Service named listProperties
and you put the follow code in it:
public static final void listProperties(IData pipeline) throws ServiceException { IDataMap idm = new IDataMap(pipeline); java.util.Properties properties = System.getProperties(); java.util.Set<String> propNames = properties.stringPropertyNames(); Iterator<String> it = propNames.iterator(); while(it.hasNext()) { String key = it.next(); idm.put(key, props.getProperty(key)); } }
then you'll see something like this:
os.name Linux java.class.version 52.0 watt.server.defaultPackage default watt.server.primaryPort 5555 ...
What's not obvious, is that the Properties
object that is returned by System.getProperties()
, is not a (direct)
instance of java.util.Properties
. If you print the class name:
System.out.println("Class name: " + System.getProperties().getClass().getName());
you'll see (in the wrapper.log
file):
Class name: com.wm.util.Config
This is most definetely a non-standard, webMethods specific class. Inspection of this class reveals the following methods:
public static com.wm.util.ServerPropertyCallbackHandle registerPropertyChangeCallback(String pattern, com.wm.util.ServerPropertyChangeEvent callback) public static com.wm.util.ServerPropertyCallbackHandle unregisterPropertyChangeCallback(com.wm.util.ServerPropertyChangeEvent callback)
So one has to provide a callback that satisfies the interface ServerPropertyChangeEvent
and you get back a ServerPropertyCallbackHandle
.
Also, one has to provide a pattern which is used to filter out the properties for which we want to be notified. For the logger that we're going to
create, let's include all properties and use the pattern .*
.
Let's inspect the interface ServerPropertyChangeEvent
:
public interface ServerPropertyChangeEvent { void onChange(String setting, String oldValue, String newValue); void onDelete(String setting, String value); void onAdd(String setting, String value); }
So if you provide an implementation for this interface, then the IS will call the appropriate method when an Extended Setting is added, changed or deleted.
Next, let's look at the object that we get back from the method registerPropertyChangeCallback
: ServerPropertyCallbackHandle
and
inspect its public methods:
public String getId(); public String getPattern(); public ServerPropertyChangeEvent getCallback();
The object that we get back after registering our interest, is identified by some id. We should use this id to register the callback somewhere, so we can remove it when we need to.
The following code provides a complete implementation of an Extended Setting Change Logger. Put this in the <<IS-SHARED-SOURCE-AREA>>
:
private static ServerPropertyCallbackHandle handle = null; private static void registerPropertyChangeListener(String pattern) { if(handle == null) { handle = Config.registerPropertyChangeCallback(pattern, new ServerPropertyChangeLogger()); JournalLogger.log(4, 90, 4, "spc", "Registered ServerPropertyChangeLogger; pattern: " + handle.getPattern() + "; id: " + handle.getId() ); } else { JournalLogger.log(4, 90, 4, "spc", "ServerPropertyChangeLogger was already registered"); } } private static void unregisterPropertyChangeListener() { if(handle != null) { Config.unregisterPropertyChangeCallback(handle); JournalLogger.log(4, 90, 4, "spc", "Unregistered ServerPropertyChangeLogger; pattern: " + handle.getPattern() + "; id: " + handle.getId()); handle = null; } else { JournalLogger.log(4, 90, 4, "spc", "ServerPropertyChangeLogger was already unregistered"); } } private static class ServerPropertyChangeLogger implements ServerPropertyChangeEvent { @Override public void onAdd(String setting, String value) { JournalLogger.log(4, 90, 4, "spc", "Added setting: " + setting + "; value: " + value); } @Override public void onChange(String setting, String oldValue, String newValue) { JournalLogger.log(4, 90, 4, "spc", "Changed setting: " + setting + "; old value: " + oldValue + "; new value: " + newValue); } @Override public void onDelete(String setting, String value) { JournalLogger.log(4, 90, 4, "spc", "Deleted setting: " + setting + "; value: " + value); } }
The only things that we now have to add, are two Java Service, one for registering the listener, and one for unregistering it.
The signature of the registerPropertyChangeListener
provides for an optional parameter pattern
, which is expected to be a regular expression.
And finally the implementation of the two Java Services:
public static final void registerPropertyChangeListener(IData pipeline) throws ServiceException { IDataMap idm = new IDataMap(pipeline); String pattern = idm.getAsNonEmptyString("pattern", ".*"); registerPropertyChangeListener(pattern); }
public static final void unregisterPropertChangeListener(IData pipeline) throws ServiceException { unregisterPropertyChangeListener(); }
The IS provides a hook into the package loading mechanism. You can register to package load- and unload events.
The class com.wm.app.b2b.server.PackageManager
exposes two methods for this purpose:
public static void addPackageListener(com.wm.app.b2b.server.PackageListener listener); public static void removePackageListener(PackageListener listener);
The PackageListener
is actually an interface. It defines these methods:
public interface PackageListener { void preload(String packageName) throws Exception; void postload(String packageName) throws Exception; void preunload(String packageName) throws Exception; void postunload(String packageName) throws Exception; }
Follow the same pattern as with the Server Property Change Listener, the following piece of code provides a complete
implementation. Save this code in the <<IS-SHARED-SOURCE-AREA>>
:
private static PackageEventListener packageEventListener = null; private static void registerPackageEventListener() { if(packageEventListener == null) { packageEventListener = new PackageEventListener(); PackageManager.addPackageListener(packageEventListener); JournalLogger.log(4, 90, 4, "pkg", "Registered Package Event Listener"); } else { JournalLogger.log(4, 90, 4, "pkg", "Package Event Listener was already registered"); } } private static void unregisterPackageEventListener() { if(packageEventListener != null) { PackageManager.removePackageListener(packageEventListener); JournalLogger.log(4, 90, 4, "pkg", "Unregistered Package Event Listener"); } else { JournalLogger.log(4, 90, 4, "pkg", "Package Event Listener was already unregistered"); } } private static class PackageEventListener implements PackageListener { @Override public void postload(String packageName) throws Exception { JournalLogger.log(4, 90, 4, "pkg", "Post load: " + packageName); } @Override public void postunload(String packageName) throws Exception { JournalLogger.log(4, 90, 4, "pkg", "Post unload: " + packageName); } @Override public void preload(String packageName) throws Exception { JournalLogger.log(4, 90, 4, "pkg", "Pre load: " + packageName); } @Override public void preunload(String packageName) throws Exception { JournalLogger.log(4, 90, 4, "pkg", "Pre unload: " + packageName); } }
For registering and unregistering the listener, create two Java Services named
registerPackageEventListener
unregisterPackageEventListener
with these implementations:
public static final void registerPackageEventListener(IData pipeline) throws ServiceException { registerPackageEventListener(); } public static final void unregisterPackageEventListener(IData pipeline) throws ServiceException { unregisterPropertyChangeListener(); }
The behaviour of the IntegrationServer with regards to firing the unload and load signals, is not quite logical. Observe this in the server log when you unload a package:
2025-05-06 17:01:06 CEST [ISS.0028.0034I] (tid=198) Package IwUtil is stopping. 2025-05-06 17:01:06 CEST [ISS.0028.0034I] (tid=198) IwUtil: Shutdown service (iw.util.admin:shutdown) 2025-05-06 17:01:08 CEST [ISP.0090.0004I] (tid=198) pkg -- Pre unload: IwUtil 2025-05-06 17:01:08 CEST [ISP.0090.0004I] (tid=198) pkg -- Post unload: IwUtil
The loading of the package causes these entries:
2025-05-06 17:27:56 CEST [ISP.0090.0004I] (tid=197) pkg -- Pre load: IwUtil 2025-05-06 17:27:56 CEST [ISS.0028.0005I] (tid=197) Loading IwUtil package 2025-05-06 17:27:56 CEST [ISS.0028.0012I] (tid=197) IwUtil: Startup service (iw.util.admin:startup) 2025-05-06 17:27:56 CEST [ISP.0090.0004I] (tid=197) pkg -- Post load: IwUtil
Notice the asymmetry here. When package unloads, first the shutdown service are executed, before the signal pre-unload
fires.
When a package is loaded on the other hand, the pre-load
signal comes before anything else, and after the startup services have
run, the post-load
signal is fired. This seems the proper order.
Things get perhaps more confusing when a package is reloaded, instead of first unloaded, and loaded. Observe these entries in the server log:
2025-05-06 17:40:25 CEST [ISP.0090.0004I] (tid=195) pkg -- Pre load: IwUtil 2025-05-06 17:40:25 CEST [ISS.0028.0015I] (tid=195) IwUtil: Shutdown service (iw.util.admin:shutdown) 2025-05-06 17:40:25 CEST [ISP.0090.0004I] (tid=195) pkg -- Pre unload: IwUtil 2025-05-06 17:40:25 CEST [ISP.0090.0004I] (tid=195) pkg -- Post unload: IwUtil 2025-05-06 17:40:25 CEST [ISS.0028.0005I] (tid=195) Loading IwUtil package 2025-05-06 17:40:25 CEST [ISS.0028.0012I] (tid=195) IwUtil: Startup service (iw.util.admin:startup) 2025-05-06 17:40:26 CEST [ISP.0090.0004I] (tid=195) pkg -- Post load: IwUtil
These observations can be made:
pre-load
event fires before anything else!From these three scenario's, unloading, loading and reloading, the following conclusions can be drawn:
pre-load
event does not mean that the services in the package are unavailable. If a package
is reloaded, the service are available and executablepre-unload
event, the shutdown services have run, but the services are still loaded and active/post-unload
event, the package and its services are not available anymore.post-load
event, the package and its services is are available and the startup services have run.Every service (Flow, Java, Adapter etc.) provides the option to log its pipeline to file via the Pipeline Debug flag. You can inspect this flag in the Properties pane of a service:
Not only can you save the pipeline, but you can also restore the pipeline. Both are very useful for debugging purposes,
but the latter is extremely dangerous. The setting is namely saved in the definition of the service and will be
deployed to the next environment. There is an Extended Setting that disables this functionality, but the default value is true
.
watt.server.pipeline.processor=true
It is very important that the Pipeline Processor is disabled in the production environment, in order to prevent strange errors. Many companies that use webMethods actually do not the use of this feature at all. In order to achieve the same functionality with some more flexibility, they usually create a utility service that by default saves the pipeline to file, and only if an input variable is set to a certain value, it will restore the pipeline.
By the way, after changing watt.server.pipeline.processor
you have to restart the IntegrationServer. The
reason is that the the value of this setting is not checked with every service invocation, but rather the whole Pipeline
Processor is added or not added to the Invoke Chain.
With that caveat out of the way, you can set the Pipeline Debug flag to the one of these values:
In the node.ndf
file, this corresponds to the field pipeline_option
with the possible values:
<?xml version="1.0" encoding="UTF-8"?> <Values version="2.0"> ... <value name="pipeline_option">1</value> ... </Values>
As we saw earlier, the value of watt.core.pipeline.processor
is not a guarantee that the Pipeline Processor is added
to the Invoke Chain or not. The proper way to test this in a running IntegrationServer is to call the static method:
public static boolean com.wm.app.b2b.server.invoke.PipelineProcessor.isPipelineProcessorEnabled();
If that returns true
, then you can manipulate the Pipeline Debug setting of a service. The class
com.wm.app.b2b.server.BaseService
provides these two methods:
public int getPipelineOption(); public void setPipelineOption(int option);
To get hold of the BaseService
, which is the parent class of all actual services, like com.wm.app.b2b.server.JavaService
and
com.wm.app.b2b.FlowSvcImpl
, you have to retrieve the service from the com.wm.app.b2b.server.ns.Namespace
:
BaseService svc = Namespace.getService(NSName.create("iw.util.admin:startup"))
Note that setting the Pipeline Option in this way only affects the running IS. It is not persisted to file and hence will not survive a restart of the IntegrationServer.
With this information, it's quite easy to create two Java Services that retrieve or set the Pipeline Debug setting of a service, along with a service that checks whether the Pipeline Processor is active in the first place.
Like the Pipeline Debug setting, the Audit Settings of a service are defined on the service and persisted in the
node.ndf
file. They are visible in the Properties pane of a service:
The possible values for Enable Auditing are:
In the node.ndf
file, this corresponds to the field auditoption
with these possible values,
respectively:
The possible values for when to log, corresponding to the property Log on, are:
In the node.ndf
file, this setting corresponds to three fields, all with a boolean value:
auditsettings/startExecution
auditsettings/stopExecution
auditsettings/onError
Technically, this offers eight possible combinations, but apparently only three are deemed interesting enough to be exposed in Designer.
The possible values for whether or not to include the whole pipeline, corresponding to the property Include pipeline, are:
In the node.ndf
file, this setting corresponds to the field auditsettings/document_data
. The
possible values are, respectively:
Here is an excerpt from a node.ndf
file featuring the fields that are relevant for auditing:
<?xml version="1.0" encoding="UTF-8"?> <Values version="2.0"> ... <value name="auditoption">2</value> <record name="auditsettings" javaclass="com.wm.util.Values"> <value name="document_data">1</value> <value name="startExecution">false</value> <value name="stopExecution">true</value> <value name="onError">true</value> </record> ... </Values>
The way to manipulate the audit settings of a service, is by using the methods of the class com.wm.lang.ns.NSService
(contained in
$WM_HOME/common/lib/wm-isclient.jar
), which is the parent class of com.wm.app.b2b.server.BaseService
:
public int getAuditOption(); public void setAuditOption(int option); public AuditSettings getAuditSettings(); public void setAuditSettings(AuditSettings settings);
The class com.wm.lang.ns.AuditSettings
captures the settings for when to log and whether or not include the pipeline.
The main methods this class exposes are:
public final void enableStartAudit(boolean start); public final void enableErrorAudit(boolean error); public final void enableCompleteAudit(boolean complete); public final void enableDocumentAudit(int state);
You would have to create a new AuditSettings();
object and set the properties as desired. Then
use this object to set it on the service you would like to audit. Just like in the Pipeline Debug case,
first get a hold of the BaseService
:
BaseService svc = Namespace.getService(NSName.create("iw.util.admin:startup"))
This should be enough information to create Java Service that retrieve and set the audit parameters of a service.
As pointed out here everything in the IntegrationServer revolves around the
IData
object. This is not a class, but an interface. In a saved pipeline you can see what the default implementing class is,
but it is also possible to find that out programmatically:
public static final void getPipelineType(IData pipeline) throws ServiceException { IDataMap idm = new IDataMap(pipeline); idm.put("class", pipeline.getClass().getName()); }
This reveals that the actual class is com.wm.data.ISMemDataImpl
. The way to create a new IData
instance,
is by going through the com.wm.data.IDataFactory
class:
IData data = IDataFactory.create();
Inspection of the the class IDataFactory
reveals this method:
public static void register(String name, String className); public static IData create(String name);
Use the method register(String name, String className)
to register your own custom implementation of the IData
interface (and its related IDataCursor
interface.
Normally, ISMemDataImpl
satisfies all needs, but perhaps some projects run into constraints, or think they need
a more performant implementation.
Note that it is not possible to register an IData
implementation that is used IS wide.
You can only use it in custom code.