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

  1. Modify the Service.xml to load the two services declaratively (the Definition and MessageParsingservices):

    <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"/>            
  2. Ensure you are importing the message parsing and definition packages. This is achieved by editing the MANIFEST.MF file in the bundle's META-INFfolder. 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 the bind parameter above. In the example given, the Activator class will need a setMessageParsingService() method that takes a com.orchestral.rhapsody.messageparsing.MessageParsingService object. The implementation of this method can store the MessageParsingService object statically for use from the communication point or filter.

  3. Modify the Activator to have member variables to reference the services:

    private static DefinitionService definitionService;
    private static MessageParsingService messageParsingService;
  4. 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;
    }
  5. 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 the s3d defined for the route.

  6. Modify doConfigure() to receive the s3d filename.  Add the following to doConfigure():

    property = config.get("DEFFILE");
    if (null==property || 0==property.length()) {
    	throw new FilterConfigurationException("Required field 'definition file' missing.");
    } else {
        defFile = property;
        logger.info(property);
    }
    


  7. Use the defFile member from the doProcessMessage() 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;
    }
    
  8. 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);
  9. 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);
    This method will work out if the parsed message has changed, and if it has will save the changes to the message body. Failing to call saveParsedMessage will cause the following error to be logged: "Modified MessageXPathContext was never saved".
  10. 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.

  1. 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
  2. Import the class and store it in a static field:

    public class Activator {
    	private static MessageFormatFactoryConfigurationHelper configurationHelper;
    ...


  3. Add a setter method for it:

    //Message parsing service getters and setters go here
    public static MessageFormatFactoryConfigurationHelper getMessageFormatFactoryConfigurationHelper() {
    	return MyFilterActivator.configurationHelper;
    }
  4. 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);
    	...
    


  5. 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);
        }
  6. The last two arguments to the appendConfigurationParameters() method determine whether the options AUTO or NONE will be available, respectively, instead of a definition. If the definition is only sometimes required, pass true as the final parameter.
  7. Doing this allows the doConfigurationMessageFormat() method to be called in the filter's doConfigure()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);
        }
    
    	...
    }
    
  8. Add a reference for the message format:

    private MessageFormat messageFormat;
    

     

  9. 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);