This section describes how to create a custom filter class:

Adding the Filter Class

To add the filter class:

  1. Right-click on the project and select New>Class.
  2. Specify package  com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.
  3. Specify class name UpperCaseFilter:

  4. For the Superclass, select the Browse... button and then type AbstractFilter to display a list of matches including the Rhapsody AbstractFilter:

  5. Select com.orchestral.rhapsody.module.filter.AbstractFilter.
  6. If the Rhapsody AbstractFilter is not listed, then either:
    • Your plug-in Target Platform is not configured correctly, or
    • Your MANIFEST.MF file does not have the Dependencies/ImportedPackages set up to point to Rhapsody plug-ins.
  7. The end result will be:

  8. Select the Finish button to get your blank class:

    package com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter;
    
    import com.orchestral.rhapsody.message.Message;
    import com.orchestral.rhapsody.message.MessageException;
    import com.orchestral.rhapsody.module.Configuration;
    import com.orchestral.rhapsody.module.filter.AbstractFilter;
    import com.orchestral.rhapsody.module.filter.FilterConfigurationException;
    import com.orchestral.rhapsody.module.filter.FilterProcessingException;
    
    public class UpperCaseFilter extends AbstractFilter {
    
                    @Override
                    public String[] getPropertyList() {
                                    // TODO Auto-generated method stub
                                    return null;
                    }
    
    
                    @Override
                    public void doConfigure(Configuration arg0)
                                                    throws FilterConfigurationException, InterruptedException {
                                    // TODO Auto-generated method stub
    
    
                    }
    
    
                    @Override
                    public Message[] doProcessMessage(Message[] arg0) throws MessageException,
                                                    FilterProcessingException, InterruptedException {
                                    // TODO Auto-generated method stub
                                    return null;
                    }
    
    
    }
  9. Note how Eclipse automatically adds the methods we must implement (that were defined in the AbstractFilter class) and marks them with @Override annotation:

    @Override
    public String[] getPropertyList() {
    	// TODO Auto-generated method stub
        return null;
    }
    
  10. The @Override annotation is beneficial to safeguard you from making mistakes:

    1. It allows the compiler to check you are correctly overriding a method rather than creating a new unique one. This way, if you make the common mistake of misspelling a method name or not correctly matching the parameters, you will be warned that your method does not actually override as you think it does.

    2. Secondly, it makes your code easier to understand because it clarifies your intentions.

  11. Note how Eclipse has highlighted an error:

  12. If you click on the globe with the error marker, you will get suggestions for fixing:

  13. Select the Add constructor suggestion and the following constructor will be added to your code:

    public UpperCaseFilter(FilterInfo arg0) {
    	super(arg0);
        // TODO Auto-generated constructor stub
    }
    
  14. Rename arg0 to filterInfo in the two places it occurs:

    public UpperCaseFilter(FilterInfo filterInfo) {
    	super(filterInfo);
        // TODO Auto-generated constructor stub
    }
    
  15. Delete the TODO Auto-generate constructor stub comment.
  16. Finally, to prevent a NullPointerException, ensure that you return an empty properties set from getPropertyList():

    @Override
    public String[] getPropertyList() {
    	return new String[0];
    }
    
  17. Press Ctrl-S to save and compile.
  18. There should be no errors highlighted now for your UpperCaseFilter.java class.

Filter Class Basics

This section introduces the methods your filter must implement.

Every filter extends AbstractFilter and must therefore implement the following three methods:

  • getPropertyList() - returns a list of configuration properties for Rhapsody IDE.
  • doConfigure() - sets configuration properties on the filter that were set in Rhapsody IDE.
  • doProcessMessage() - process the incoming messages and return the resulting outgoing messages.

And optionally override the getKind() method if the implementation is not thread-safe or uses a lot of memory. Refer to Concurrency for details on concurrency.

When you double-click on a filter to bring up its properties, Rhapsody invokes getPropertyList() against your filter which returns the properties that are listed in Rhapsody IDE as shown in this example:

When you select the OK button in the filter's properties dialog then Rhapsody invokes doConfigure() against your filter and Rhapsody IDE passes the settings into the filter. The filter then configures itself with those settings.

If the filter finds some issue with the configuration settings, then it throws a FilterConfigurationException which results in a message back to Rhapsody IDE:

After being configured, a filter can be used.

Every time a message on the route hits the filter, Rhapsody calls the filter's doProcessMessage() method and the Rhapsody Engine passes the incoming message to the filter.  The filter returns from doProcessMessage() passing the output message back to the Engine and thus back to the route.

Completing the Filter with a doProcessMessage Method

To convert the filter into a working filter, implement the doProcessMessage method (you can add configuration properties to the filter, but for now create a filter without any configuration properties.):

  1. Rename arg0 in the doProcessMessage arguments list to messages.
  2. Then modify the doProcessMessage method as follows (paste or type the following code excerpt into the filter):

    @Override
    public Message[] doProcessMessage(Message[] messages) throws MessageException {
    	for (int i = 0; i < messages.length; i++){
        	try {
            	final String body = Messages.asString(messages[i]);
                Messages.setBody(messages[i], body.toUpperCase());
            } catch (UnsupportedEncodingException e) {
                messages[i].addError("Message encoding type '" + messages[i].getBodyEncoding() + "' not supported.");
            } catch (IOException e) {
               throw new MessageException("Error while reading message", e);
            }
        }
        return messages;
    }
    
  3. The doProcessMessage method now loops through all incoming messages.

  4. Next it extracts the message body into a String body.
  5. Finally it converts body to uppercase and replaces the message body with the uppercase version of the message.

    We only expect a single message, messages[0], to come into the filter unless Message Collection is enabled on the filter. If you are not designing your filter to handle collections then you could optionally choose to throw an exception if the incoming messages array contains more than one message:

    if (messages.length > 1) {
    	throw new FilterProcessingException("This filter doesn't process batches of messages. Remove collector from filter.");
    }
  6. There are some errors in the code.

  7. First Eclipse has marked Messages because it does not have an import statement.
  8. Click on the error marker and choose to add the import.

    A much quicker way to do this is to press Shift+Ctrl+O on the keyboard. This will immediately add all the import statements.

  9. The class should now be error free. Save it (Ctrl+S) and then create a module Activator after which you can deploy and test the filter.

  10. You should have something like this, which will be explained in the next section:

    package com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    
    import com.orchestral.rhapsody.message.Message;
    import com.orchestral.rhapsody.message.MessageException;
    import com.orchestral.rhapsody.message.Messages;
    import com.orchestral.rhapsody.module.Configuration;
    import com.orchestral.rhapsody.module.filter.AbstractFilter;
    import com.orchestral.rhapsody.module.filter.FilterConfigurationException;
    import com.orchestral.rhapsody.module.filter.FilterInfo;
    
    public class UpperCaseFilter extends AbstractFilter {
    
                    public UpperCaseFilter(FilterInfo filterInfo) {
                    	super(filterInfo);
                    }
    
    
                    @Override
                    public String[] getPropertyList() {
                    	return new String[0];
                    }
    
    
                    @Override
                    public void doConfigure(Configuration arg0) throws FilterConfigurationException, InterruptedException {
                    }
    
    
                    @Override
                    public Message[] doProcessMessage(Message[] messages) throws MessageException{
                    	for (int i = 0; i < messages.length; i++){
    
                        	try {
                        		final String body = Messages.asString(messages[i]);
                            	Messages.setBody(messages[i], body.toUpperCase());
                        	} catch (UnsupportedEncodingException e) {
                                                                   
     							messages[i].addError("Message encoding type '" + messages[i].getBodyEncoding() + "' not supported.");
                        	} catch (IOException e) {
                        		throw new MessageException("Error while reading message", e);
                        	}
                        }
                        return messages;
                    }
    }


Exceptions

The doProcessMessage method can throw the following exceptions:

  • com.orchestral.rhapsody.message.MessageException - possibly thrown in reaction to a Java IOException (for example, some problem reading the message or performing a transformation of some kind).
  • java.io.UnsupportedEncodingException - possibly thrown your filter supports UTF-8 and does not support CP-1252 Windows files and the incoming message is a Windows format message and for some reason specific to your filter you are being very strict about formats and so you reject the CP-1252 message. This is specific to your implementation.

  • java.io.IOException - normally wrapped in a MessageException. Ensure you send an informative message back since IOException is highly generic and by itself probably meaningless to the person debugging a route.

    try {
    	final String body = Messages.asString(messages[i]);
    	Messages.setBody(messages[i], body.toUpperCase());
    } catch (IOException e) {
    	throw new MessageException("Error while reading message", e);
    }
    

Throwing an exception will drop the message into the Error Queue.

Add an error to the message

You can also add an error to the message. The result is the same as throwing an exception – the message will end up in the Error Queue:

try {
	final String body = Messages.asString(messages[i]);
	Messages.setBody(messages[i], body.toUpperCase());
} catch (UnsupportedEncodingException e) {
	messages[i].addError("Message encoding type '" + messages[i].getBodyEncoding() + "' not supported.");
}

Forward the original message back into the route

You might, however, consider allowing the original message to continue unchanged instead of throwing an exception – that depends entirely on the requirements for the custom filter, as determined by the project you are working on.

if (null == processingResult) {
	Messages.setBody(message, inputBuffer); // return the input message instead of an exception.
}

Configuring a Module Activator

The Activator is a class that we have to create that will be used to activate our custom module and load up our filter into Rhapsody.

Loading a Custom Module into Rhapsody

How does your custom module get bootstrapped or loaded?

There is a META-INF/MANIFEST.MF file in your project that will also reside in your final module. Your module will be a JAR file (which is a java archive or java library), and Rhapsody will look first at the MANIFEST.MF file to determine if there is something that needs to be loaded into the Engine.

It will find a reference to a Service.xml file that you will create, which in turn will point to your Activator class. Rhapsody will then run our Activator class (by calling its activate() method.
The Activator will be coded to contain the details of our filter and will load our custom filter into Rhapsody.

Configure the Activator

Perform the following steps only if you used the wizard to create your project.

To configure the Activator:

  1. Open the Activator class and delete any default registration code:

  2. Modify the activate method to now read:

    protected void activate(final ComponentContext componentContext) {
    	final BundleContext context = componentContext.getBundleContext();
        final String cpr = CommunicationPointRegistration.class.getName();
        final String fr = FilterRegistration.class.getName();
    
    
        // Register the filter types in this module        
        this.registrations.add(context.registerService(fr, new FilterRegistration("UPPERCASEFILTER",
                                                       "Upper Case Filter", "Utility", UpperCaseFilter.class, "/Smiley-32.bmp",
                                                       "/Smiley-16.bmp"), null));
                                   
       /* <START> - CODE CONTROLLED BY WIZARDS DO NOT REMOVE COMMENT */
       /* <END> - CODE CONTROLLED BY WIZARDS DO NOT REMOVE COMMENT */
    }
  3. Press Ctrl-Shift-O to import the FilterRegistration class.

Create the Service.xml File

Perform the following steps only if you did not use the wizard to create your project.

To create a Service.xml file that points to the Activator class so that Rhapsody can find the class:

  1. Create a new folder in your project, called OSGI-INF (right click on the project and choose New > Folder).
  2. Add a file to OSGI-INF called Service.xml (right click on OSGI-INF and choose New > File).
  3. Paste the following into the Service.xml file and then save it:

    <component name="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter" immediate="true">
                    <implementation class="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.Activator"/>
    </component>
    
  4. Now looking at what was created, you can see that the filter module has been defined as the component named com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter

     <component name="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter" immediate="true">
    
  5. Next it points to the Activator class that Rhapsody will use to load the filter.

     <implementation class="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.Activator"/>
    
  6. You will create this class shortly.

An Activator can load multiple filters or communication points and an eclipse plugin project could contain multiple filters or communication points – just make sure you have meaningful and tidy package structures (in other words, folder hierarchies in your source src folder). Rhapsody provides the filter with a bunch of named services. For example, the service SECURITY is specified – we could potentially use that service to manage security artifacts such as certificates.

Modify MANIFEST.MF to point to Service.xml

Perform the following steps only if you did not use the wizard to create your project.

  1. Open the META-INF/MANIFEST.MF file and select the MANIFEST.MF tab.
  2. Add the pointer to the Service.xml file as the final line: Service-Component: OSGI-INF/Service.xml.
  3. Ensure you add a newline after the newly added line in step 2:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: UpperCaseFilter
    Bundle-SymbolicName: UpperCaseFilter
    Bundle-Version: 1.0.0.qualifier
    Bundle-RequiredExecutionEnvironment: JavaSE-1.8
    Import-Package: com.orchestral.rhapsody.configuration;version="6.4.0",
     com.orchestral.rhapsody.configuration.auxiliaryfiles;version="6.4.0",
     com.orchestral.rhapsody.configuration.definition;version="6.4.0",
     com.orchestral.rhapsody.configuration.repository;version="6.4.0",
     com.orchestral.rhapsody.configuration.security;version="6.4.0",
     com.orchestral.rhapsody.configuration.variables;version="6.4.0",
     com.orchestral.rhapsody.idgenerator;version="6.4.0",
     com.orchestral.rhapsody.initialisation;version="6.4.0",
     com.orchestral.rhapsody.message;version="6.4.0",
     com.orchestral.rhapsody.messageparsing;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.definition;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.format;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.xpath;version="6.4.0",
     com.orchestral.rhapsody.model.definition;version="6.4.0",
     com.orchestral.rhapsody.module;version="6.4.0",
     com.orchestral.rhapsody.module.communicationpoint;version="6.4.0",
     com.orchestral.rhapsody.module.filter;version="6.4.0",
     com.orchestral.rhapsody.module.filter.internal;version="6.4.0",
     com.orchestral.rhapsody.persistentmap;version="6.4.0",
     com.orchestral.rhapsody.security;version="6.4.0",
     org.apache.log4j;version="1.2.17"
    Service-Component: OSGI-INF/Service.xml
     
  4. Navigate to the Build tab.

  5. Check OSGI-INF on the Binary Build list.

  6. Save the manifest file.

Modify the MANIFEST.MF to Point to Class Files

Perform the following steps only if you did not use the wizard to create your project.

The source files with .java extension are located in the src/ folder. The compiled .class files are located in the bin/ folder. However, when the module JAR file is created, an Ant script will copy the class files to the root directory of the project. The script needs to know where to find these .class files.

The MANIFEST.MF can be configured so it knows where to find the class files:

  1. Double-click on the META-INF/MANIFEST.MF to open it.
  2. Select the MANIFEST.MF tab.
  3. Add the following line to the bottom (and ensure you add a newline afterward): 'Bundle-ClassPath: .', for example:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: UpperCaseFilter
    Bundle-SymbolicName: UpperCaseFilter
    Bundle-Version: 1.0.0.qualifier
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: com.orchestral.rhapsody.configuration;version="6.4.0",
     com.orchestral.rhapsody.configuration.auxiliaryfiles;version="6.4.0",
     com.orchestral.rhapsody.configuration.definition;version="6.4.0",
     com.orchestral.rhapsody.configuration.repository;version="6.4.0",
     com.orchestral.rhapsody.configuration.security;version="6.4.0",
     com.orchestral.rhapsody.configuration.variables;version="6.4.0",
     com.orchestral.rhapsody.idgenerator;version="6.4.0",
     com.orchestral.rhapsody.initialisation;version="6.4.0",
     com.orchestral.rhapsody.message;version="6.4.0",
     com.orchestral.rhapsody.messageparsing;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.definition;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.format;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.xpath;version="6.4.0",
     com.orchestral.rhapsody.model.definition;version="6.4.0",
     com.orchestral.rhapsody.module;version="6.4.0",
     com.orchestral.rhapsody.module.communicationpoint;version="6.4.0",
     com.orchestral.rhapsody.module.filter;version="6.4.0",
     com.orchestral.rhapsody.module.filter.internal;version="6.4.0",
     com.orchestral.rhapsody.persistentmap;version="6.4.0",
     com.orchestral.rhapsody.security;version="6.4.0",
     org.apache.log4j;version="1.2.17",
     org.openrdf.util;version="6.1.0",
     org.osgi.framework;version="1.3.0",
     org.osgi.service.component;version="1.0.0"
    Service-Component: OSGI-INF/Service.xml
    Bundle-ClassPath: .
     

Create the Activator Class

Perform the following steps only if you did not use the wizard to create your project.

To create the com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.Activator class that was indicated in the Service.xml:

  1. Create a new class in package com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter and call it Activator.

    Your Activator does not have to be named Activator. You can use any class name as long as the Service.xml file points to the correct file. If you have multiple projects and want them all to have an Activator named Activator, you should make sure each of your Activator.java files have different package names so that there is no confusion.

  2. It will look like this:

    package  com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.;
    public class Activator {
    }
  3. Paste in the complete code required:

    package  com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import com.orchestral.rhapsody.module.FilterRegistration;
    import com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.UpperCaseFilter;
    import org.osgi.framework.BundleContext;
    import org.osgi.framework.ServiceRegistration;
    import org.osgi.service.component.ComponentContext;
    
    public class Activator {
    
        private Set<ServiceRegistration> registrations = new HashSet<ServiceRegistration>();
                   
        protected void activate(final ComponentContext componentContext) {
                                                                
            final BundleContext context = componentContext.getBundleContext();
            final String fr = FilterRegistration.class.getName();
    
            this.registrations.add(context.registerService(fr,
            new FilterRegistration("UPPERCASEFILTER",
                                   "Upper Case Filter", "Utility", UpperCaseFilter.class, "/Smiley-32.bmp",
                                   "/Smiley-16.bmp"), null));
        }
    
        protected void deactivate(final ComponentContext context) {
            for (final ServiceRegistration registration : this.registrations) {
                registration.unregister();
            }
            this.registrations.clear();
        }
    }

Understanding the Activator

When the Rhapsody Engine calls activate() on our Activator, we register the UpperCaseFilter module:

// Register the filter types in this module        
this.registrations.add(context.registerService(fr, new FilterRegistration("UPPERCASEFILTER", 
                                                                          "Upper Case Filter", "Utility", UpperCaseFilter.class, 
                                                                          "/Smiley-32.bmp", "/Smiley-16.bmp"), null));

There are a number of necessary parameters in the FilterRegistration constructor:

Parameters Description

UPPERCASEFILTER

This is an identifier for the filter, which must be unique (in other words, no other filter type on the engine can have the same identifier).

Upper Case Filter

This is a text description of the filter (in other words, the filter name that will be displayed to the user).

Utility

This is the class of filter and determines where in the Filter Toolbox it appears:

UpperCaseFilter.class

This is the name of the actual filter class.

/Smiley-32.bmp

This is the filter icon as it appears on the route.

/Smiley-16.bmp

This is a smaller size icon used in Rhapsody IDE in the Filter Toolbox:

For now you do not need to create the icons; instead allow Rhapsody to assign the default icons.

Preparing the Filter for Deployment

To deploy the filter, generate a JAR file archive containing all the files:

  1. Create an Ant script to generate the JAR file.  Call the script build.xml and place in the root  folder of the project with the following contents:

    <project name="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter" default="jar" basedir=".">
        <property name="build.classes" value="bin"/>
        <target name="jar">
            <jar jarfile="${ant.project.name}.jar"
                 basedir="${build.classes}"
                 manifest="META-INF/MANIFEST.MF">
                    <fileset dir=".">
                        <include name="OSGI-INF/Service.xml"/>
                    </fileset>
            </jar>
        </target>
    </project>
  2. Right-click on build.xml and select Run As>Ant Build to generate a JAR file that can be deployed to the Rhapsody engine:

  3. Select the project, then press F5, or right-click and select  Refresh.

  4. The generated JAR file now appears in the project's root folder: com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.jar.

Because the JAR file is created outside of Eclipse by using Ant, Eclipse requires its cache of files updated to find it. Eclipse is always out-of-sync with the file system with regards to files it does not create – hence the need to refresh to force Eclipse to discover the file and display it in the Navigator.

Deploying and Testing your Filter

  1. In your Rhapsody installation, in the folder of your datastore, you will find a modules folder.
    For example: C:\Rhapsody\Rhapsody Engine 6\rhapsody\data\modules
  2. To deploy the filter, simply copy the com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter.jar into the modules folder.
  3. Restart Rhapsody.
  4. If the filter has problems and cannot be loaded, these should be examined in the standard log file (C:\Rhapsody\Rhapsody Engine 6\log\log.txt) using Notepad++ or any other technique you fancy for viewing text log files.
  5. Otherwise, the filter will appear, with default icons in the Filter Toolbox:

  6. You can then create a test route to see if it works:

  7. Attach a text file to the Timer and test the route.

Adding Configuration Properties to Your Filter

To add properties to your filter, add a static String for each property describing the property, for example:

private static String USERNAME = "USERNAME|*s||username property||Set this to any value";
private static String PASSWORD = "PASSWORD|*w||password property||Set this to any value";	

Each string has six fields, separated by a vertical bar '|' or pipe:

public static final String[] props = {"Name|Type|DefaultValue|Display Name|Dependency|Description"};

The fields in this string are:

Field Description

Name

The filter property name.

Type

The type of the property. This is a coded string.

Default Value

The default value for the property (optional, although recommended for enumerated types)

Display Name

The name to display to the user.

Dependency

Other property/value that must be set before this property is used (optional), for example:

private static final String MODE= "MODE|*e{sign;verify}|0|mode||mode";
 private static final String ALIAS= "ALIAS|*s||private key alias|MODE=sign|private key alias";

You can create multiple property dependencies by using the logical operators AND and OR, and the inequality operator <>.

Description

A description of the property.

Property Types

The Type field is a coded string that uses a data-type character, preceded by zero or more modifier characters to give meaning to the property. A property with no modifiers is optional. The basic property types recognised by Rhapsody are:

Data Type

Description

s

A string value property, for example:

private static final String ALIAS= "ALIAS|*s||private key alias|MODE=sign|private key alias";

z

A boolean property.

i

A 32-bit integer property.

iP A 32-bit integer property used for displaying port numbers in the Management Console or via the REST API.

e{name;name...}

An integer-based enumerated property (starting at 0), with a drop-down box displaying the specified strings.  Keep your text clear of special characters otherwise it will not work.

For example:

private static final String ONERROR= "ONERROR|*e{Generate Error Response; Send to Error Queue}|0|on error||on error";

w

A password property (a string property but will be displayed as ****** by Rhapsody IDE), for example:

private static final String PASSWD= "PASSWD|*w||keystore password||keystore password";

f

File.

d

Directory.

m

Symphonia message definition (can be mapper, S3D or XSD).

p

Message property name, for example:

private static final String RESULT= "RESULT|*p|p_SigningValidateResult|name of property to populate result SUCCESS or FAILED||name of property to populate result";

r

Regular expression.

M

Symphonia mapper definition.

L

An EDI message field name.

n

A Rhapsody destination name.

V

A watchlist.

j

A JavaScript script.

k

A cryptographic public/private key.

K

A cryptographic secret key (symmetric key).

c

A cryptographic certificate.

t

An EDI message type (for example, MSH).

T

A list of Response EDI messages.

Modifier

Description (modifier goes before the data type, for example *s)

*

The property is required. If no default value is given then the user must supply a value.

$

Indicates to Rhapsody IDE to enable a drop-down list for the property in the configuration dialog. The drop-down box contains any message properties that are extracted on the route.

When using the $ modifier, you still need to parse the data that has been entered by a user, either manually or from the drop-down box that the modifier enables.

Therefore, when using the doConfigure method, you still need to get the message property name from the configuration property and strip off the $ prefix, and save that message property name to a member variable. Then, when using the doProcessMessage method, you need to check that each message has that message property name, for example using messages[i].getProperty("myPropertyName").

[

An array (there can be more than one of these, one for each dimension). Array modifiers may also follow (see table below). A two-dimensional array defines the rows, then the columns. Do not close the square bracket.

Usage of modifiers

If using the * modifier, it must be the first modifier (in other words, you must have *$, not $*). The $ and [ modifiers cannot be used together.

Array Modifier

Description

n

A number specifying the size of the array.

{name;...}

A list of header strings for the array (separated by ';' characters).


The property defines the dimension and the names of the headers.

Hence in our case:

private static String USERNAME = "USERNAME|*s||username property||Set this to any value";
private static String PASSWORD = "PASSWORD|*w||password property||Set this to any value";
  • We add a String property called USERNAME, with display name username property and description Set this to any value.
  • We also add a Password property called PASSWORD, with display name password property and description Set this to any value.  The value of a password property is hidden and not viewable from Rhapsody IDE.

Now create a properties array to return in getPropertyList():

private static final String[] props = { USERNAME, PASSWORD };

Then add two variables to hold the properties:

private String userName;
private String password;

Modify the getPropertyList() method to return the properties array:

@Override
public String[] getPropertyList() {
	return props;
}

Modify doConfigure to take the properties from the IDE and set them on the class. Notice that the argument name is also changed from arg0 to config:

@Override
public void doConfigure(Configuration config) throws FilterConfigurationException, InterruptedException {
                               
	userName = config.get("USERNAME");
    password = config.get("PASSWORD");
}

Finally get the filter to write the configuration properties onto the message as message properties:

public Message[] doProcessMessage(Message[] messages) throws MessageException {
	for (int i = 0; i < messages.length; i++) {
    	try {
        	final String body = Messages.asString(messages[i]);
            Messages.setBody(messages[i], body.toUpperCase());
            messages[i].getProperty("p_userName").setValue(userName);
            messages[i].getProperty("p_password").setValue(password);
            ...

And therefore end up with a class file as follows:

package com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import com.orchestral.rhapsody.message.Message;
import com.orchestral.rhapsody.message.MessageException;
import com.orchestral.rhapsody.message.Messages;
import com.orchestral.rhapsody.module.Configuration;
import com.orchestral.rhapsody.module.filter.AbstractFilter;
import com.orchestral.rhapsody.module.filter.FilterConfigurationException;
import com.orchestral.rhapsody.module.filter.FilterInfo;

public class UpperCaseFilter extends AbstractFilter {
               
    private static String USERNAME = "USERNAME|*s||username property||Set this to any value";
    private static String PASSWORD= "PASSWORD|*w||password property||Set this to any value";
               
    private String userName;
    private String password;
               
    private static final String[] props = { USERNAME, PASSWORD };

    public UpperCaseFilter(FilterInfo filterInfo) {
    	super(filterInfo);
    }

    @Override
    public String[] getPropertyList() {
    	return props;
    }

    @Override
    public void doConfigure(Configuration config)
    throws FilterConfigurationException, InterruptedException {
       
        userName = config.get("USERNAME");
        password = config.get("PASSWORD");
    }

    @Override
    public Message[] doProcessMessage(Message[] messages) throws MessageException{
        for (int i = 0; i < messages.length; i++) {
            try {
                final String body = Messages.asString(messages[i]);
                Messages.setBody(messages[i], body.toUpperCase());
                messages[i].getProperty("p_userName").setValue(userName);
                messages[i].getProperty("p_password").setValue(password);
            } catch (UnsupportedEncodingException e) {
                messages[i].addError("Message encoding type '" + messages[i].getBodyEncoding() + "' not supported.");
            } catch (IOException e) {
            	throw new MessageException("Error while reading message", e);
            }
        }
        return messages;
    }
}

Now test it by deploying, then adding properties to the filter on the route:

And if you send a test message through you can see the properties (even the hidden password property) are written onto the message as properties:

Validating Filter Configuration Properties

Currently, the configure() method simply fetches the values:

@Override
public void doConfigure(Configuration config) throws FilterConfigurationException, InterruptedException {
               
	userName = config.get("USERNAME");
	password = config.get("PASSWORD");
}

This method can be enhanced to throw an exception back to the IDE in the case that the properties do not validate or are missing.  Modify the configure() to store the property in a temporary variable after which we validate it before assigning it. Using a temporary variable is very useful in cases where you have to follow with a type conversion such as String to Integer.

@Override
public void doConfigure(Configuration config) throws FilterConfigurationException, InterruptedException {
                               
	String property = config.get("USERNAME");
	if (null==property || 0==property.length()) {
		throw new FilterConfigurationException("Required field 'username' missing.");
    } else {
    	userName = property;
    }
                               
    property = config.get("PASSWORD");
    if (null==property || 0==property.length()) {
    	throw new FilterConfigurationException("Required field 'password' missing.");
    } else {
    	password = property;
    }
}


Now if you leave a property blank, the properties dialog will prompt you with an error as you set above in the code:

Also, when you attempt to commit you will again be shown the error message and the filter will go into an error state indicated by a big black cross:

Getting or Setting Message Properties on Messages

Setting Properties

As shown in the previous section, you can easily set properties by calling getProperty("new prop name").setValue("new property value"):

public Message[] doProcessMessage(Message[] messages) throws MessageException{
	for (int i = 0; i < messages.length; i++) {

	try {
    	final String body = Messages.asString(messages[i]);
        Messages.setBody(messages[i], body.toUpperCase());
        messages[i].getProperty("p_userName").setValue(userName);
        messages[i].getProperty("p_password").setValue(password);

Getting Properties

You can also get existing properties by simply calling:

getProperty("new prop name").getValue()

For example:

String inputTime = messages[i].getProperty("InputTime").getValue());

Adding Custom Icons to the Filter

So far the filter has defaulted to the standard filter icon. Rhapsody uses two icons for every a filter or communication point. The first is the 32x32 bit size used on the route itself, and the second is the 16x16 size used in the filter palette.

Icons must be in legacy1 Layer RGB 24-bit BMP format only. Additionally, they must have no more than 256 colors. Refer to Change the Mode for details on how to change images to indexed colors using Gimp.

To set up a legacy 24-bit BMP image:

  1. Navigate to http://en.wikipedia.org/wiki/Smiley from your web browser.
  2. Right-click on the smiley face and select Copy Image.
  3. Start up the Gimp graphics program and select Edit>Paste As>New Image.

  4. Scale the image to be 32x32 by selecting Image>Scale.

  5. Ensure the X ↔ Y locks are in place when you scale:

  6. Select Layer>Transparency>Add Alpha Channel.
  7. Choose the color select tool:

  8. Select the black background:

  9. Select Edit>Cut:

  10. Select File>Save As.
  11. Select Windows BMP Type.
  12. Name the file Smiley-32.bmp:

  13. Select the filter's src folder as the location to save to.
  14. Expand Advanced Settings and select the 24 bits format:

  15. Select the Save button.
  16. Select Image>Scale.
  17. Change the image to 16x16.
  18. Save it as Smiley-16.bmp following the same procedure as for the 32x32 icon.  Again, save it to your src folder, taking care you do not save the 16x16 as Smiley-32.bmp.
  19. Go back to Eclipse, and right-click on your project and select Refresh.
  20. You should now have two icons in position:

  21. Note how Eclipse "compiles" the icons across to the bin folder.
  22. Add the icons to your registration.  Check the Activator has the correct filenames for the icons:

     this.registrations.add(context.registerService(fr,
                    new FilterRegistration("UPPERCASEFILTER",
                    "Upper Case Filter", "Utility", UpperCaseFilter.class, "/Smiley-32.bmp",
                                    "/Smiley-16.bmp"), null));
  23. Ensure the Ant script includes the images.

  24. Since Ant is configured to build the jar from basedir="${build.classes}", the images are automatically included as Eclipse 'compiles' them into that location.
  25. Generate a new module using the Ant build, and deploy it to verify the icons have changed:

Adding Logging to the Filter

 Install the log4 JAR

Perform the following steps only if you used the wizard to create your project, otherwise skip this section and follow the steps in Invoke the Logger.

To install the log4j JAR file:

  1. Create a lib folder in your project.
  2. Download the log4j JAR file and place it in the lib folder:
  3. Add the jar to the build path.
  4. Right-click on the project and select Build Path>Configure Build Path and select the Add JARS... button on the Libraries tab.
  5. Browse to the JAR file and select it.
  6. Open the META-INF/MANIFEST.MF file – the module needs to know where the JAR file is for when it is running within Rhapsody.  Note that when running the module is 'extracted' and has its own root folder. It will look for the lib folder relative to that for the log4j JAR file.  Hence we add the lib/log4j jar in the Runtime tab to the classpath:

  7. Check the MANIFEST.MF contents have been changed accordingly (it should be added to the Bundle-Classpath):

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: UpperCaseFilter
    Bundle-SymbolicName: UpperCaseFilter
    Bundle-Version: 1.0.0.qualifier
    Bundle-RequiredExecutionEnvironment: JavaSE-1.8
    Import-Package: com.orchestral.rhapsody.configuration;version="6.4.0",
     com.orchestral.rhapsody.configuration.auxiliaryfiles;version="6.4.0",
     com.orchestral.rhapsody.configuration.definition;version="6.4.0",
     com.orchestral.rhapsody.configuration.repository;version="6.4.0",
     com.orchestral.rhapsody.configuration.security;version="6.4.0",
     com.orchestral.rhapsody.configuration.variables;version="6.4.0",
     com.orchestral.rhapsody.idgenerator;version="6.4.0",
     com.orchestral.rhapsody.initialisation;version="6.4.0",
     com.orchestral.rhapsody.message;version="6.4.0",
     com.orchestral.rhapsody.messageparsing;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.definition;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.format;version="6.4.0",
     com.orchestral.rhapsody.messageparsing.xpath;version="6.4.0",
     com.orchestral.rhapsody.model.definition;version="6.4.0",
     com.orchestral.rhapsody.module;version="6.4.0",
     com.orchestral.rhapsody.module.communicationpoint;version="6.4.0",
     com.orchestral.rhapsody.module.filter;version="6.4.0",
     com.orchestral.rhapsody.module.filter.internal;version="6.4.0",
     com.orchestral.rhapsody.persistentmap;version="6.4.0",
     com.orchestral.rhapsody.security;version="6.4.0",
     org.apache.log4j;version="1.2.17",
     org.openrdf.util;version="6.1.0",
     org.osgi.framework;version="1.3.0",
     org.osgi.service.component;version="1.0.0"
    Service-Component: OSGI-INF/Service.xml
    Bundle-ClassPath: .,
     lib/log4j-1.2.17.jar

Invoke the Logger

  1. Change the Ant script so that it adds the log4j JAR file into the module jar:

    <project name="com.orionhealth.EMEA.rhapsody.module.UpperCaseFilter" default="jar" basedir=".">
        <property name="build.classes" value="bin"/>
        <target name="jar">
            <jar jarfile="${ant.project.name}.jar" basedir="${build.classes}" manifest="META-INF/MANIFEST.MF">                                          
                <fileset dir=".">
                     <include name="OSGI-INF/Service.xml"/>
                     <include name="lib/*"/>
                </fileset>                                              
            </jar>
        </target>
    </project>
  2. Set up the filter class to use log4j JAR file.

  3. Add the import:

    import org.apache.log4j.Logger;
    
  4. Create a final instance of logger:

     public class UpperCaseFilter extends AbstractFilter {
                    ….          
                    private final Logger logger = getLogger();
    
  5. Invoke the logger in our methods:

    public Message[] doProcessMessage(Message[] messages) throws MessageException {
    	for (int i = 0; i < messages.length; i++) {
    		try {
    			final String body = Messages.asString(messages[i]);
    			Messages.setBody(messages[i], body.toUpperCase());
    			logger.info("prop="+messages[i].getProperty("InputTime").getValue());
    			...

log4j.properties and Debug Logging

To improve the logging configuration:

  1. Change all the logger.info calls to logger.debug.
  2. Obtain the ID of your filter. You can get this from the log, for example:

    2011-07-28 09:46:51,393  INFO [Route Executor 3] [Route.2969.Filter.4013] prop=1311842810803
  3. In the above line from the log we can see our filter has ID=4013, but for log4j purposes what we want is the route qualified ID Route.2969.Filter.4013.

  4. If you cannot find a statement with the ID then you can force a log statement into the log with logger.error("bogus error"); and then catch the ID information from log.txt.
  5. If it was a communication point, not a filter, then you can also get the ID using the web services API.
  6. Launch SOAP-UI and create a new project by importing your Rhapsody WSDL which is located at https://localhost:8449/services/RhapsodyComponentsService?wsdl.
  7. Configure security details for the interface by right-clicking on the green railway sleeper as per:

  8. Then invoke the getAllComponents method and find the ID of your component in the response message.
  9. Now you can create a log4j configuration for our filter to capture debug messages.  You can then easily turn off logging for our filter through the log4j configuration.
  10. Add the following section to your log4j.properties file (in your rhapsody folder under your Rhapsody installation):

    # custom filter logging
    log4j.logger.Route.2969.Filter.4013=DEBUG,4013
    log4j.appender.4013=org.apache.log4j.RollingFileAppender
    log4j.appender.4013.File=logs/4013.txt
    log4j.appender.4013.MaxBackupIndex=9
    log4j.appender.4013.MaxFileSize=5MB
    log4j.appender.4013.layout=org.apache.log4j.PatternLayout
    log4j.appender.4013.layout.ConversionPattern=%d %5.5p [%32.32t] [%40.40c] %m%n
    
    
  11. Ensure you change the Route ID and Filter ID to match your own example.

  12. Restart Rhapsody, send a message and check your log file. In this example the logfile is called log/4013.txt.

Adding Javadoc Comments

Javadoc is a way of commenting your code so that HTML documentation can be generated for it.

Simply type /** above your class or method and press Enter. Eclipse will add the javadoc template and you can then simply flesh it out:

/**
* Converts the message to uppercase and adds some properties
*/
@Override
public Message[] doProcessMessage(Message[] messages) throws MessageException {

Concurrency

If you set the filter to unlimited concurrency, then you cannot alter the concurrency from the IDE and there will not be a concurrency limit on the filter. This is the default setting:

public FilterKind getKind() {
	return FilterKind.UNLIMITED_CONCURRENCY;
}

The implications are that Rhapsody will try to create a new route instance for every message coming in – in other words, a new thread per route instance. However, it may be that this is inappropriate for your filter (a database JDBC filter, for example, will create too many connections and end up swamped in context switching if you allow unlimited concurrency).

If, however, you want to let the user control the concurrency, then you need to set it as follows:

@Override
public FilterKind getKind() {
	return FilterKind.LIMITED_CONCURRENCY;
}

This provides the concurrency option on the filter as follows:

Fetching the Filter's Route

You can get the route using the following method:

getFilterInfo().getRoute()