Extended Definition Service
As of Rhapsody 6.1, the Rhapsody RDK wizard uses an extended definition service. Because definitions must now be placed in lockers, their names must be unique within that locker (though not necessarily across the entire engine). As a result, the getDefinition(String definitionName)
method is no longer sufficient to identify a definition uniquely. Two new methods are used to pass in either the route or the communication point information in order to identify the associated locker and locate the definition with the given name in that locker. Though the deprecated method has been updated to pass in the locker automatically, it is recommended you use the replacement methods.
An example of how to call the extended definition service is included in the template methods. When setting up the Rhapsody RDK development environment, you must point the environment to the latest version of Rhapsody (Rhapsody 6.1 or later) that has the extended definition service.
Deprecated Definition Service Method
The following Definition Service method has been deprecated:
public Definition getDefinition(String definitionName) throws DefinitionNotFoundException;
Replacement Definition Service Methods
The following Definition Service method replaces the deprecated method, either:
public Definition getDefinition(RouteInfo routeInfo, String definitionName) throws DefinitionNotFoundException;
where RouteInfo
can be retrieved with getFilterInfo().getRoute()
, or:
public Definition getDefinition(CommunicationPointInfo commPointInfo, String definitionName) throws DefinitionNotFoundException;
where CommunicationPointInfo
can be retrieved with getCommunicationPointInfo()
.
Message Parsing in Custom Filters
Modify the
Service.xml
to load the two services declaratively (theDefinition
andMessageParsing
services):<reference name="DEFINITION" interface="com.orchestral.rhapsody.model.definition.DefinitionService" bind="setDefinitionService" unbind="unsetDefinitionService"/> <reference name="MESSAGE_PARSING" interface="com.orchestral.rhapsody.messageparsing.MessageParsingService" bind="setMessageParsingService" unbind="unsetMessageParsingService"/>
Ensure you are importing the message parsing and definition packages. This is achieved by editing the
MANIFEST.MF
file in the bundle'sMETA-INF
folder. Ensure you have the following:... Import-Package: com.orchestral.rhapsody.configuration, com.orchestral.rhapsody.message, com.orchestral.rhapsody.messageparsing, com.orchestral.rhapsody.messageparsing.definition, com.orchestral.rhapsody.messageparsing.format, com.orchestral.rhapsody.messageparsing.xpath, com.orchestral.rhapsody.model.definition, com.orchestral.rhapsody.module, com.orchestral.rhapsody.module.filter, com.orchestral.rhapsody.module.helper.messageparsing.format, org.apache.log4j;version="1.2.14", org.osgi.framework;version="1.3.0", org.osgi.service.component;version="1.0.0" ...
The Activator class (as referenced in
Service.xml
) will need a method that matches that specified in thebind
parameter above. In the example given, the Activator class will need asetMessageParsingService()
method that takes acom.orchestral.rhapsody.messageparsing.MessageParsingService
object. The implementation of this method can store theMessageParsingService
object statically for use from the communication point or filter.Modify the Activator to have member variables to reference the services:
private static DefinitionService definitionService; private static MessageParsingService messageParsingService;
Modify your Activator class to add the following methods:
protected void setDefinitionService(final DefinitionService definitionService) { Activator.definitionService = definitionService; } protected void unsetDefinitionService(final DefinitionService definitionService) { Activator.definitionService = null; } protected void setMessageParsingService(final MessageParsingService messageParsingService) { Activator.messageParsingService = messageParsingService; } protected void unsetMessageParsingService(final MessageParsingService messageParsingService) { Activator.messageParsingService = null; } ... public static DefinitionService getDefinitionService() { return Activator.definitionService; } public static MessageParsingService getMessageParsingService() { return Activator.messageParsingService; }
Modify the filter to take a definition as an argument:
private static final String USERNAME = "USERNAME|*s||username property||Set this to any value"; private static final String PASSWORD= "PASSWORD|*w||password property||Set this to any value"; private static final String DEFFILE= "DEFFILE|*m||definition file||Set this to any def file"; private String userName; private String password; private String defFile; private final Logger logger = getLogger(); private static final String[] props = { USERNAME, PASSWORD, DEFFILE };
Here
DEFFILE
has been added to the properties list. This property will take a definition file on the filter properties dialog. From Rhapsody IDE, you will be able to select thes3d
defined for the route.Modify
doConfigure()
to receive the s3d filename. Add the following todoConfigure()
:property = config.get("DEFFILE"); if (null==property || 0==property.length()) { throw new FilterConfigurationException("Required field 'definition file' missing."); } else { defFile = property; logger.info(property); }
Use the
defFile
member from thedoProcessMessage()
method to parse your messages. This example gets the patient surname and changes it to uppercase: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()); try { Definition def = Activator.getDefinitionService().getDefinition(getFilterInfo().getRoute(), defFile); MessageFormat format = Activator.getMessageParsingService().getMessageFormatFactory().getMessageFormat(def); MessageXPathContext context = format.parseMessage(messages[i]).getParsedMessage(); String patientSurname = context.getField("PID/PatientName[0].FamilyName.Surname"); context.insertXPathValue("PID/PatientName[0].FamilyName.Surname", patientSurname.toUpperCase()); Activator.getMessageParsingService().saveParsedMessage(messages[i], context); } catch (DefinitionNotFoundException e) { logger.info(e); } catch(XPathException e ) { logger.info(e); } catch (MessageFormatException e) { logger.info(e); } catch (InterruptedException e) { logger.info(e); } } 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 a
MessageXPathContext
has been obtained, you can access or change fields on the message as required using the methods defined on the class.- To access fields you can use methods on the message context object, such as:
String getField(String path);
List<String> extractValue(String path);
int getCount(String path);
- To set fields, use:
void insertValue(String path, String value);
- To access fields you can use methods on the message context object, such as:
- Finally,
saveParsedMessage
should be called before the filter returns, as this returns the parsed body for use by other filters, saving processing time on the route:MessagingModuleService.getMessageParsingService().saveParsedMessage(message, msgContext);
saveParsedMessage
will cause the following error to be logged: "Modified MessageXPathContext was never saved". - If the parsed message has changed but you want the changes to be discarded call the above method with
null
instead of the message:MessagingModuleService.getMessageParsingService().saveParsedMessage(null, msgContext);
Using the MessageFormatFactoryConfigurationHelper
The MessageFormatFactoryConfigurationHelper
should also be used to make the job much easier.
- Modify the
MANIFEST.MF
. Go to the Dependencies tab and select the Add... button to add the following Imported Packages:com.orchestral.rhapsody.module.helper.messageparsing.lazysoftdefinitions
com.orchestral.rhapsody.module.helper.messageparsing.format
com.orchestral.rhapsody.module.helper.messageparsing.util
Import the class and store it in a static field:
public class Activator { private static MessageFormatFactoryConfigurationHelper configurationHelper; ...
Add a setter method for it:
//Message parsing service getters and setters go here public static MessageFormatFactoryConfigurationHelper getMessageFormatFactoryConfigurationHelper() { return MyFilterActivator.configurationHelper; }
Modify the activate method as follows:
protected void activate(final ComponentContext componentContext) { //Initialise the message format helper Activator.configurationHelper = new MessageFormatFactoryConfigurationHelper(Activator.messageParsingService.getMessageFormatFactory(), Activator.definitionService); ...
Remove the definition property we created earlier and use the helper to add a definition property:
private static final String[] props = { USERNAME, PASSWORD, DEFFILE }; @Override public String[] getPropertyList() { //return props; return Activator.getMessageFormatFactoryConfigurationHelper().appendConfigurationParameters(props, false, false); }
- The last two arguments to the
appendConfigurationParameters()
method determine whether the optionsAUTO
orNONE
will be available, respectively, instead of a definition. If the definition is only sometimes required, passtrue
as the final parameter. Doing this allows the
doConfigurationMessageFormat()
method to be called in the filter'sdoConfigure()
method (ensure you remove the code excerpt you originally used to get the definition file prior to using the helper).public void doConfigure(Configuration config) throws FilterConfigurationException, InterruptedException { ... try { this.messageFormat = Activator.getMessageFormatFactoryConfigurationHelper().getConfigurationMessageFormat(config, getFilterInfo().getRoute()); } catch (final MessageFormatException e) { throw new FilterConfigurationException("Caught exception while loading definition",e); } ... }
Add a reference for the message format:
private MessageFormat messageFormat;
The message format can then be used in the
doProcessMessage()
method to parse the message: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()); try { MessageXPathContext context = this.messageFormat.parseMessage(messages[i]).getParsedMessage(); String patientSurname = context.getField("PID/PatientName[0].FamilyName.Surname"); context.insertXPathValue("PID/PatientName[0].FamilyName.Surname", patientSurname.toUpperCase()); Activator.getMessageParsingService().saveParsedMessage(messages[i], context); ...
Parsing XML
To parse an XML message, you can use the following message format:
MessagingModuleService.getMessageParsingService().getMessageFormatFactory().getXMLMessageFormatType().newMessageFormat(null);