Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Thursday, June 6, 2013

Date Comparison using XSLT and XPath in DataPower

It was a brain-draining day at work. I had to implement a customized SLM policy enforcement for a customer where we pull a meta-data of policies from an xml and enforce it inside an XSL transform action. The trickiest part of the day was implementing the Allowed Time of Day policy. From the parameters defined in the xml snippet we had to reject transaction if the current time is beyond the start and stop times. This is the element that we are retrieving the start and stop time from:
<Daily StartTime="08:00:00+08:00" StopTime="17:00:00+08:00"/>

After hours of attempts, below is the solution I've came up with.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
 version="1.0" 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
 xmlns:dp="http://www.datapower.com/extensions" 
 xmlns:func="http://exslt.org/functions" 
 xmlns:df="http://lmls.ph/date_func" 
 xmlns:date="http://exslt.org/dates-and-times"
 extension-element-prefixes="dp slm func" 
 exclude-result-prefixes="dp slm func">
 
 <!-- removes the padded zero in front of HH or mm or ss -->
 <func:function name="df:removeFrontPaddedZeroes">
  <xsl:param name="pad_num" />
  <xsl:if test="starts-with($pad_num, '0')">
   <func:result select="substring($pad_num,2)" />
  </xsl:if>
  <func:result select="substring($pad_num,1)" />
 </func:function>
 
 <!-- converts the time format 00:00:00+00:00 into numbers for comparison -->
 <func:function name="df:convertTimeToNumber">
  <xsl:param name="zTime" />
  <xsl:if test="substring($zTime,9,1) = '+'">   
   <func:result select="((number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2))) * 100) + number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))) + ((number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2))) * 100))" />   
  </xsl:if>
  <func:result select="((number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2))) * 100) + number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))) - ((number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2))) * 100))" />  
 </func:function>

 <!-- function that evaluates if the current time is within the start and stop time bounds -->
 <func:function name="df:testTimeOfDay">
  <xsl:param name="start_time" />
  <xsl:param name="stop_time" />  
  <xsl:variable name="current_time" select="date:time()" />

  <xsl:variable name="start_time_c" select="df:convertTimeToNumber($start_time)" />
  <xsl:variable name="stop_time_c" select="df:convertTimeToNumber($stop_time)" />
  <xsl:variable name="current_time_c" select="df:convertTimeToNumber($current_time)" />

  <xsl:if test="(number($current_time_c) &gt;= number($start_time_c)) and (number($current_time_c) &lt;= number($stop_time_c))">
   <func:result select="true()" />
  </xsl:if>
  <func:result select="false()" />
 </func:function>
<xsl:stylesheet>

There are three functions in this XSL namely: df:testTimeOfDay, df:converTimeToNumber, and df:removeFrontPaddedZeroes the comments explain their purposes. The converTimeToNumber is where bulk of the logic goes and before I arrived to this code I had the split it up into smaller and more easy to understand pieces:
# The input parameter is called zTime, this comes in the format of 00:00:00+00:00
zTime = "00:00:00+08:00"

# Now in the format HH:mm:ss[\-\+]Hz:mz
HH = number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) 
mm = number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2)))
ss = number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))
op = substring(string($zTime),9,1)
Hz = number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2)))
mz = number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2)))

# Then logically we get HHmmss by doing (HH * 10000) + (mm *100) + (ss)
# And, Hzmz by doing (Hz * 10000) + (mz * 100)
HHmmss = (number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2))) * 100) + number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))
Hzmz = (number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2))) * 100)

# Then our if condition should be which provides the final output in HHmmss + or - Hzmz
if op = '+'
 result = ((number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2))) * 100) + number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))) + ((number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2))) * 100))
else 
 result = ((number(df:removeFrontPaddedZeroes(substring(string($zTime),1,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),4,2))) * 100) + number(df:removeFrontPaddedZeroes(substring(string($zTime),7,2)))) - ((number(df:removeFrontPaddedZeroes(substring(string($zTime),10,2))) * 10000) + (number(df:removeFrontPaddedZeroes(substring(string($zTime),13,2))) * 100))

Finally we can call the the df:testTimeOfDay to return true() when current time when current time is greater than or equal to start time AND current time less than or equal to stop time. See snippet below:
<xsl:variable name="start_time" select="$sla_snippet/Daily/@StartTime" />
<xsl:variable name="stop_time" select="$sla_snippet/Daily/@StopTime" />

<xsl:if test="df:testTimeOfDay($start_time,$stop_time) = false()">
 <dp:xreject reason="'Time of Day policy violated.'"/>
</xsl:if>

I haven't really done much testing and error handlings with this but I was able to test it making happy paths. This at least gives an idea of how you can perform comparison between dates.

Thursday, July 12, 2012

Modifying Endpoint Bindings Using XML Property File in BPM 7.5


In my  previous post, Deployment Script for BPM 7.5 on a Linux machine, I mentioned that I would post another blog about modifying endpoint bindings of your SCA imports. During that time we used a properties file to specify deployment specific information such as installation directory, applications to install, list of federated nodes,  etc. When I was working with WPS 6.2 in my previous project, I used the same properties file to specify the SCA import bindings to modify. To tell you the truth, the property was not very readable and it got large to read at some point. I specified the import bindings this way:

# modify_ws_binding="<module_name>::<import_name> <endpoint_binding>,<import_name> <endpoint_binding>,<import_name> <endpoint_binding>;;

Realizing that problem, I had the idea to use XML as "response" file for modifying the endpoint bindings via wsadmin. The structure goes something like:

<?xml version="1.0" encoding="UTF-8"?>
<deployment>
 <module name="ModuleName">
  <import name="ImportName1" type="WS">
   <property name="endpoint" value="http://localhost:9800/setWhatever" />
  </import>
  <import name="ImportName2" type="WS">
   <property name="endpoint" value="http://localhost:9800/setWhatever" />
  </import>  
  <import name="ImportName3" type="HTTP" level="methodName">
   <property name="endpointURL" value="http://localhost:9800/setWhatever" />
   <property name="endpointHttpMethod" value="POST" />
   <property name="httpProxyPort" value="80" />
  </import>
  <import name="ImportName3" type="HTTP" level="methodName1">
   <property name="endpointURL" value="http://localhost:9800/getWhatever" />
   <property name="endpointHttpMethod" value="POST" />
   <property name="httpProxyPort" value="80" />
  </import>
 </module>
 <module name="ModuleName1">
  <import name="ImportName1" type="WS">
   <property name="endpoint" value="http://localhost:9800/setWhatever" />
  </import>
  <import name="ImportName2" type="WS">
   <property name="endpoint" value="http://localhost:9800/setWhatever" />
  </import>  
  <import name="ImportName3" type="HTTP" level="ImportName3">
   <property name="endpointURL" value="http://localhost:9800/setWhatever" />
   <property name="endpointHttpMethod" value="POST" />
   <property name="httpProxyPort" value="80" />
  </import>
 </module>
</deployment>

The root element is the deployment element. I used this term because I have hopes of creating a deployment script using XML. Now, the deployment element has a an array of children named module. Each module tag has an attribute name where you specify the SCA module name. module has an array of children named import. Each import element has an attribute name  which specifies the name of the Import binding and an attribute type which specifies the type of SCA import binding (for now, the script only supports web services and http bindings).  The attribute level is an attribute for HTTP bindings. If you want to specify values on the import level, you specify the name of the import. Otherwise, specify the method name. Lastly, the import element has multiple property elements. Each property element has an attribute name which specifies the name of the parameter and an attribute value which specifies the value of the parameter. Visit the infocenter to get a list of parameters and values you can set for methods modifySCAImportHttpBinding and modifySCAImportWSBinding.

After writing the xml file, I wrote the code for reading it. I don't want to explain in detail the code, but, in general, what it does is, it parses the xml file. From the root element, it traverses down to the properties element and executes modifySCAImportHttpBinding  or modifySCAImportWSBinding using the values specified in the xml file. Lastly, it saves the configuration. In a network deployment, you might want to add the synchronize nodes code block that I used in my previous post.

Here's the code:

import javax.xml.parsers.DocumentBuilderFactory as DocumentBuilderFactory
import javax.xml.parsers.DocumentBuilder as DocumentBuilder
import sys

dbf = DocumentBuilderFactory.newInstance()
db = dbf.newDocumentBuilder()
dom = db.parse(sys.argv[0])

deployment = dom.getDocumentElement()
modules = deployment.getElementsByTagName('module') 

m=0
while m<modules.getLength():
 module = modules.item(m)
 moduleName = module.getAttribute('name')
 scaImports = module.getElementsByTagName('import')
 i=0
 while i<scaImports.getLength():
  scaImport = scaImports.item(i)
  scaImportType = scaImport.getAttribute('type')
  scaImportName = scaImport.getAttribute('name')
  properties = scaImport.getElementsByTagName('property')
  p=0
  props = ''
  levelBegin = '<' + scaImport.getAttribute('level') + '>'
  levelEnd = '</' + scaImport.getAttribute('level') + '>'
  if (scaImportType == 'WS'):
   while p<properties.getLength():
    property = properties.item(p)
    props += ' -' + property.getAttribute('name') + ' ' + property.getAttribute('value')
    p+=1
   #endWhile
   try:
    print 'AdminTask.modifySCAImportWSBinding(\'[-moduleName ' + moduleName + ' -import ' + scaImportName + props + ']\')'
    AdminTask.modifySCAImportWSBinding('[-moduleName ' + moduleName + ' -import ' + scaImportName + props + ']')
   except :
    print "Unexpected error:", sys.exc_info()[0]
  elif (scaImportType == 'HTTP'):
   while p<properties.getLength():
    property = properties.item(p)
    props += ' -' + property.getAttribute('name') + ' ' + levelBegin + property.getAttribute('value') + levelEnd
    p+=1
   #endWhile
   try:
    print 'AdminTask.modifySCAImportHttpBinding(\'[-moduleName ' + moduleName + ' -import ' + scaImportName + props + ']\')'
    AdminTask.modifySCAImportHttpBinding('[-moduleName ' + moduleName + ' -import ' + scaImportName + props + ']')
   except :
    print "Unexpected error:", sys.exc_info()[0]
  i+=1
 m+=1
else:
 print 'Processing Complete...'

print 'Saving...'
AdminConfig.save()

We then execute this using wsadmin:

wsadmin -lang jython -f script_path\modify_ws_bindings.py xml_path\properties.xml

Note that there are not much error checking done with this code. If you encounter any error don't forget to write a comment. 

References:
Changing an import binding using commands