Friday, 1 February 2013

Sending and Saving Emails in SoaSuite

My current project is about the revisiting of some workflows built in SoaSuite10g (Bpel, Esb).
In these workflows emails are sent at several point. Some of these mails are about errors to the administrator. But others are for customers/applicatants or end-users.

In one of these cases  I have to add a new email that is to be saved in to the Document Management System (DMS) after being send.

Now in a typical use of the email-notification service, the text of the message is put in the Notification activity in the bpel process. That means that the text is fixed/hard-coded in the Bpel code. Changing the text means changing the bpel process, and have it through the whole test/acceptance/production cycle.

In this particular case the message is to be determined up-front, since I need to save the same message to file. One of my "rescues" is the lookupXML function I talked about in my previous post. The other one is the lovely XSL-language.

First I created an XML-file file as follows:

<?xml version="1.0" encoding="windows-1252" ?>
<!-- $Id: email_texts.xml 99 2013-02-01 08:27:07Z ca_mavandenakker $ -->
<!-- This file can be located in a HTTP accessible directory in the midtier installation and it's referred 
     through an lookup-xml XPath expression in the service invocation.  -->
<!-- The current location is http://${deploy.soasuite.URL}/ObjectLibrary/EmailService/xml/email_texts.xml -->
<email_texts>
   <email_text>
      <key>EmailSubject1</key>
      <value>Subject Email 1</value>
   </email_text>
   <email_text>
      <key>Body Email 1</key>
      <value>Dear Mailer, &lt;br/>
Thank you for sending these documents:&lt;br/>
&lt;table>

 &lt;tr>
  &lt;td colspan =&quot;2&quot;>
   ${ReceivedDocuments}
  &lt;td>
 &lt;/tr>
  &lt;tr>
  &lt;td>Indiener:&lt;/td>&lt;td>${ApplicantName}&lt;td>
 &lt;/tr>
 &lt;tr>
  &lt;td>Telefoonnummer:&lt;/td>&lt;td>${ApplicantPhone}&lt;/td>
 &lt;/tr>
 &lt;tr>
  &lt;td>E-mailadres:&lt;/td>&lt;td>${ApplicantEmail}&lt;/td>
 &lt;/tr>
  
 &lt;tr>
  &lt;td colspan=&quot;2&quot;>&lt;br>&lt;b>Your appointed reprecentative:&lt;/b>&lt;/td>
 &lt;/tr>
 &lt;tr>
  &lt;td>Naam:&lt;/td>&lt;td>${ReprecentativeFullname}&lt;/td>
 &lt;/tr>
 &lt;tr>
  &lt;td>Telefoon:&lt;/td>&lt;td>${ReprecentativePhone}&lt;/td>
 &lt;/tr>
 &lt;tr>
  &lt;td>E-mail:&lt;/td>&lt;td>${ReprecentativeUsername}@darwin-it.nl&lt;/td>
 &lt;/tr>
&lt;/table> 
&lt;br/>
Your application is taken in account.

Remark: this is an  automatically generated message. Replies on this message will not be answered.&lt;br/>
&lt;br/>
Best regards,&lt;br/>
&lt;br/>
Darwin-IT Professionals&lt;br/>
</value>
   </email_text>
</email_texts>

This file can be either on the file system of the SOAServer or on a particular folder in the docRoot of the HTTP server of the SoaServer. In 11g you would put it in the MDS.

As you can see in the example, I put several properties into the document. Properties in the form of ${property}, like properties in ANT.

These have to be replaced by the process giving a list of properties. I created a bpel process that does this. And use the result to send the email message. In the response the resulting subject and body are returned, so that they can be used to save to a html-document in a temporary folder on the filesystem.

The xsd of the bpel process is as follows:
<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
            xmlns:svnId="$Id" xmlns:version="1.0"
            targetNamespace="http://www.darwin-it.nl/xsd/v1/EmailService"
            xmlns="http://www.darwin-it.nl/xsd/v1/EmailService"
            xmlns:ems="http://www.darwin-it.nl/xsd/v1/EmailService"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="EmailServiceProcessRequest"
              type="ems:EmailServiceProcessRequestType"/>
 <xsd:element name="EmailServiceProcessResponse"
              type="ems:EmailServiceProcessResponseType"/>
 <xsd:complexType name="EmailServiceProcessRequestType">
  <xsd:sequence>
   <xsd:element name="FromAccountName" type="xsd:string" minOccurs="0"/>
   <xsd:element name="To" type="xsd:string"/>
   <xsd:element name="Cc" type="xsd:string" minOccurs="0"/>
   <xsd:element name="Bcc" type="xsd:string" minOccurs="0"/>
   <xsd:element name="ReplyToAddress" type="xsd:string" minOccurs="0"/>
   <xsd:element name="subjectKey" type="xsd:string"/>
   <xsd:element name="bodyKey" type="xsd:string"/>
   <xsd:element name="PropertyList" type="ems:PropertyList" minOccurs="0"/>
  </xsd:sequence>
 </xsd:complexType>
 <xsd:complexType name="EmailServiceProcessResponseType">
  <xsd:sequence>
   <xsd:element name="To" type="xsd:string"/>
   <xsd:element name="Cc" type="xsd:string" minOccurs="0"/>
   <xsd:element name="EmailBody" type="xsd:string"/>
   <xsd:element name="EmailSubject" type="xsd:string"/>
  </xsd:sequence>
 </xsd:complexType>
 <xsd:complexType name="PropertyType">
  <xsd:sequence>
   <xsd:element name="name" type="xsd:string"/>
   <xsd:element name="value" type="xsd:string"/>
  </xsd:sequence>
 </xsd:complexType>
 <xsd:element name="Property" type="ems:PropertyType"/>
 <xsd:complexType name="PropertyList">
  <xsd:sequence>
   <xsd:element name="Property" type="ems:PropertyType" maxOccurs="unbounded"/>
  </xsd:sequence>
 </xsd:complexType>
</xsd:schema>

The subjectKey and bodyKey elements correspond to the key values in the email.xml. With those values the XSL will lookup the particular template text. In the process the following xsl is used to do the transformation:
<?xml version="1.0" encoding="UTF-8" ?>
<?oracle-xsl-mapper
  <!-- SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY. -->
  <mapSources>
    <source type="XSD">
      <schema location="EmailService.xsd"/>
      <rootElement name="EmailServiceProcessRequest" namespace="http://www.darwin-it.nl/xsd/v1/EmailService"/>
    </source>
  </mapSources>
  <mapTargets>
    <target type="XSD">
      <schema location="EmailService.xsd"/>
      <rootElement name="EmailServiceProcessResponse" namespace="http://www.darwin-it.nl/xsd/v1/EmailService"/>
    </target>
  </mapTargets>
  <!-- GENERATED BY ORACLE XSL MAPPER 10.1.3.5.0(build 090730.0200.1754) AT [WED JAN 30 10:49:55 CET 2013]. -->
?>
<xsl:stylesheet version="1.0" xmlns:svnId="$Id"
                xmlns:bpws="http://schemas.xmlsoap.org/ws/2003/03/business-process/"
                xmlns:ehdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.esb.server.headers.ESBHeaderFunctions"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:version="1.0"
                xmlns:hwf="http://xmlns.oracle.com/bpel/workflow/xpath"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
                xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ora="http://schemas.oracle.com/xpath/extension"
                xmlns:ids="http://xmlns.oracle.com/bpel/services/IdentityService/xpath"
                xmlns:orcl="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
                xmlns:ns0="http://www.darwin-it.nl/xsd/v1/EmailService"
                exclude-result-prefixes="xsl svnId xsd version ns0 bpws ehdr hwf xp20 xref ora ids orcl">
  <xsl:variable name="configURI"
                select="'http://melia03.hhdelfland.nl/ObjectLibrary/EmailServices/xml/email_texts.xml'"/>
  <xsl:variable name="test"/>
  <xsl:template match="/">
    <ns0:EmailServiceProcessResponse>
      <ns0:To>
        <xsl:value-of select="/ns0:EmailServiceProcessRequest/ns0:To"/>
      </ns0:To>
      <ns0:Cc>
        <xsl:value-of select="/ns0:EmailServiceProcessRequest/ns0:Cc"/>
      </ns0:Cc>
      <ns0:EmailBody>
        <xsl:call-template name="getEmailText">
          <xsl:with-param name="key"
                          select="/ns0:EmailServiceProcessRequest/ns0:bodyKey"/>
          <xsl:with-param name="propList"
                          select="/ns0:EmailServiceProcessRequest/ns0:PropertyList"/>
        </xsl:call-template>
      </ns0:EmailBody>
      <ns0:EmailSubject>
        <xsl:call-template name="getEmailText">
          <xsl:with-param name="key"
                          select="/ns0:EmailServiceProcessRequest/ns0:subjectKey"/>
          <xsl:with-param name="propList"
                          select="/ns0:EmailServiceProcessRequest/ns0:PropertyList"/>
        </xsl:call-template>
      </ns0:EmailSubject>
    </ns0:EmailServiceProcessResponse>
  </xsl:template>
   
  <!--  User Defined Templates  -->
   
  <xsl:template name="getEmailText">
    <xsl:param name="key"/>
    <xsl:param name="propList"/>
    <xsl:variable name="emailTextTpl"
                  select='orcl:lookup-xml($configURI,"/email_texts/email_text","key","value",$key)'/>
    <xsl:variable name="emailText">
      <!-- <xsl:value-of select="$emailTextTpl"/> -->
      <xsl:call-template name="replaceProps">
        <xsl:with-param name="string" select="$emailTextTpl"/>
        <xsl:with-param name="propList" select="$propList"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$emailText"/>
  </xsl:template>
   
  <xsl:template name="replaceProps">
    <xsl:param name="string"/>
    <xsl:param name="propList"/>
    <xsl:variable name="parm">
      <xsl:call-template name="getFirstProp">
        <xsl:with-param name="string" select="$string"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="parmVal"
                  select="$propList/ns0:Property[ns0:name=$parm]/ns0:value"/>
    <xsl:variable name="newString">
      <xsl:choose>
        <xsl:when test="string-length($parm)>0">
          <!-- There is a parameter so recusively call replaceProps with a replaced string -->
          <xsl:call-template name="replaceProps">
            <xsl:with-param name="string">
              <!-- Replace the parameter with the value and use that as a parameter for the recursively called template -->
              <xsl:call-template name="replace-ci">
                <xsl:with-param name="input" select="$string"/>
                <xsl:with-param name="fromStr" select="concat('${',$parm,'}')"/>
                <xsl:with-param name="toStr" select="$parmVal"/>
              </xsl:call-template>
            </xsl:with-param>
            <xsl:with-param name="propList" select="$propList"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <!-- No parameters left so just return the string -->
          <xsl:value-of select="$string"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:value-of select="$newString"/>
  </xsl:template>
   
  <xsl:template name="replace-ci">
    <xsl:param name="input"/>
    <xsl:param name="fromStr"/>
    <xsl:param name="toStr"/>
    <xsl:if test="string-length( $input ) > 0">
      <xsl:variable name="startPos">
        <xsl:call-template name="index-of-ci">
          <xsl:with-param name="string" select="$input"/>
          <xsl:with-param name="search" select="$fromStr"/>
        </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="inputLwr" select="xp20:lower-case($input)"/>
      <xsl:variable name="fromStrLwr" select="xp20:lower-case($fromStr)"/>
      <xsl:choose>
        <xsl:when test="contains( $inputLwr, $fromStrLwr )">
          <xsl:variable name="stringBefore"
                        select="substring($input,1,$startPos - 1)"/>
          <xsl:variable name="stringAfter"
                        select="substring($input,$startPos + string-length($fromStr))"/>
          <xsl:value-of select="concat($stringBefore,$toStr)"/>
          <xsl:call-template name="replace-ci">
            <xsl:with-param name="input" select="$stringAfter"/>
            <xsl:with-param name="fromStr" select="$fromStr"/>
            <xsl:with-param name="toStr" select="$toStr"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$input"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
  </xsl:template>
   
  <xsl:template name="index-of-ci">
    <xsl:param name="string"/>
    <xsl:param name="search"/>
    <xsl:param name="startPos"/>
    <xsl:variable name="searchLwr" select="xp20:lower-case($search)"/>
    <xsl:variable name="work">
      <xsl:choose>
        <xsl:when test="string-length($startPos)>0">
          <xsl:value-of select="substring($string,$startPos)"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$string"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:variable name="stringLwr" select="xp20:lower-case($work)"/>
    <xsl:variable name="result">
      <xsl:choose>
        <xsl:when test="contains($stringLwr,$searchLwr)">
          <xsl:variable name="stringBefore">
            <xsl:value-of select="substring-before($stringLwr,$searchLwr)"/>
          </xsl:variable>
          <xsl:choose>
            <xsl:when test="string-length($startPos)>0">
              <xsl:value-of select="$startPos +string-length($stringBefore)"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="1 + string-length($stringBefore)"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
          -1
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:copy-of select="$result"/>
  </xsl:template>
   
  <xsl:template name="getFirstProp">
    <xsl:param name="string"/>
    <xsl:variable name="startPosParm">
      <xsl:call-template name="index-of-ci">
        <xsl:with-param name="string" select="$string"/>
        <xsl:with-param name="search" select="'${'"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="endPosParm">
      <xsl:call-template name="index-of-ci">
        <xsl:with-param name="string" select="$string"/>
        <xsl:with-param name="search" select="'}'"/>
        <xsl:with-param name="startPos" select="$startPosParm"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="parmNameLength"
                  select="number($endPosParm)-(number($startPosParm)+2)"/>
    <xsl:variable name="parm">
      <xsl:choose>
        <xsl:when test="number($startPosParm)>0 and number($parmNameLength)>0">
          <xsl:value-of select="substring($string, $startPosParm+2,$parmNameLength)"/>
        </xsl:when>
        <xsl:otherwise/>
      </xsl:choose>
    </xsl:variable>
    <xsl:value-of select="$parm"/>
  </xsl:template>
</xsl:stylesheet>

You might think to just loop over the properties in the property list and then replace all the corresponding properties with the corresponding values. But that won't work. You can't repeatedly change a variable or target in XSL. The trick to solve this is recursion.

The template first looks up the email text, in the "getEmailText" template, using the same lookupXML function as used in my previous post. Then it replaces all occurences of properties from the template text using the "replaceProps" template. That template does a lookup of the first occurence of the parameter within the text, using the "getFirstProp"-template. If it finds one, it will do a replace of the property with the value and feed the result recursively into the "replaceProps"-template. The value is queried using the key from the propertylist  If it does not find a property then it will return just the text, which ends the recursion.

The lookup and replacement is done with about the same functions I described in this post. Unfortunately I found a bug in the replace template in that post.  I solved it in the version above, where I also left-out a small un-needed functionality. Maybe you can find it as well?

I won't explain the rest of the process. After the transformation above it just fills in the blanks in the notification service using the elements above. The property list is a simle name-value-pair list. To fill that is not so hard either.

LookUp DVM in SoaSuite 10g, the 11g way

In SoaSuite 10g there is a xpath and xslt function do Domain Value Map lookups,lookupDVM. This function is based on tables in the SoaSuite repository. There is a screen in the ESB console to edit them.

In SoaSuite 11g there is a separate artefact for DVM's, and a corresponding lookupDVM function.
I found that one more convenient, since there is an editor in Jdeveloper for it and the artefact is in fact an xml file that can be put in the MDS and referenced from anywhere in your SOA projects. And lastly, it can be versioned as any other artefact.
Deployment is similar to other SOA-artefacts. For 10g you must ensure to synchronise the dvm's in the database between the different (OTAP) environments.

Today I needed a simple translation from 0,1 to Ja,Nee (Yes, No). I did not feel like to create a DVM in the database. But, also in 10g there is a quite similar solution as the 11g lookupDVM function

In SoaSuite 10g, there is an oracle xsl-extension function: lookupXML. It enables you to read in a xml file and perform a xpath-lookup query. I found it very convenient to lookup fixed (OTAP) environment-dependent parameters. If you would put those in bpel preferences you have to ensure that they're changed every deployment between the environments. Using the lookupXML function, you can lookup those values from an xml file from the filesystem of the SOASuite. Then you only have to put an version of the file in a fixed folder on every SOASuite environment. After adapting those for the particular environment, you don't need to touch it every deployment anymore.

This same construction can be used to do lookups. I created the following xml file:

<?xml version="1.0" encoding="windows-1252" ?>
<!-- $Id: email_texts.xml 99 2013-02-01 08:27:07Z ca_mavandenakker $ -->
<!-- This file can be located in a HTTP accessible directory in the midtier installation and it's referred 
     through an lookup-xml XPath expression in the service invocation.  -->
<!-- The current location is http://${deploy.soasuite.URL}/ObjectLibrary/Core/xml/v1/CoreLookupDVM.xml -->
<domains>
      <domain name="JaNee">
            <keyDb>0</keyDb>
            <dispValue>Nee</dispValue>
            <boolean>false</boolean>
      </domain>
      <domain name="JaNee">
            <keyDb>1</keyDb>
            <dispValue>Ja</dispValue>
            <boolean>true</boolean>
      </domain>
</domains>
   
Since the lookupXML is a xsl function, it is in 10g apparently not available as an Xpath function, I created an XSD for the convenience of doing the DVM-lookups:
<?xml version="1.0" encoding="windows-1252" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:version="1.0"
            xmlns:svnId="$Id$" xmlns="http://www.darwin-it.nl/xsd/v1/DVM"
            xmlns:dvm="http://www.darwin-it.nl/xsd/v1/DVM"
            targetNamespace="http://www.darwin-it.nl/xsd/v1/DVM"
            elementFormDefault="qualified">
  <xsd:element name="DVM" type="dvm:DVMType">
    <xsd:annotation>
      <xsd:documentation>Domain Value Mapping</xsd:documentation>
    </xsd:annotation>
  </xsd:element>
  <xsd:complexType name="DVMType">
    <xsd:sequence>
      <xsd:element name="Domain" type="xsd:string"/>
      <xsd:element name="FromKey" type="xsd:string"/>
      <xsd:element name="FromValue" type="xsd:string"/>
      <xsd:element name="ToKey" type="xsd:string"/>
      <xsd:element name="ToValue" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>
Based on this XSD you can create a variable, fill in the Domain, eg. "JaNee", the FromKey, eg. "keyDb", the FromKeyValue, eg. "0", and the ToKey, eg. "dispValue" or "boolean". Then you can transform the variable to it's self using the following XSL:
<?xml version="1.0" encoding="UTF-8" ?>
<?oracle-xsl-mapper <!-- SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY. -->
  <mapSources>
    <source type="XSD">
      <schema location="http://melia03.hhdelfland.nl/ObjectLibrary/Core/xsd/v1/DVM.xsd"/>
      <rootElement name="DVM" namespace="http://www.darwin-it.nl/xsd/v1/DVM"/>
    </source>
  </mapSources>
  <mapTargets>
    <target type="XSD">
      <schema location="http://melia03.hhdelfland.nl/ObjectLibrary/Core/xsd/v1/DVM.xsd"/>
      <rootElement name="DVM" namespace="http://www.darwin-it.nl/xsd/v1/DVM"/>
    </target>
  </mapTargets>
  <!-- GENERATED BY ORACLE XSL MAPPER 10.1.3.5.0(build 090730.0200.1754) AT [FRI FEB 01 10:04:09 CET 2013]. -->
?>
<xsl:stylesheet version="1.0"
                xmlns:bpws="http://schemas.xmlsoap.org/ws/2003/03/business-process/"
                xmlns:ehdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.esb.server.headers.ESBHeaderFunctions"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:version="1.0"
                xmlns:ns0="http://www.darwin-it.nl/xsd/v1/DVM"
                xmlns:hwf="http://xmlns.oracle.com/bpel/workflow/xpath"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
                xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ora="http://schemas.oracle.com/xpath/extension"
                xmlns:ids="http://xmlns.oracle.com/bpel/services/IdentityService/xpath"
                xmlns:orcl="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
                xmlns:svnId="$Id$"
                exclude-result-prefixes="xsl xsd version ns0 svnId bpws ehdr hwf xp20 xref ora ids orcl">
  <xsl:variable name="configURI"
                select="'http://${deploy.soasuite.URL}/ObjectLibrary/Core/xml/v1/CoreLookupDVM.xml'"/>
                
  <xsl:template match="/">
    <ns0:DVM>
      <ns0:Domain>
        <xsl:value-of select="/ns0:DVM/ns0:Domain"/>
      </ns0:Domain>
      <ns0:FromKey>
        <xsl:value-of select="/ns0:DVM/ns0:FromKey"/>
      </ns0:FromKey>
      <ns0:FromValue>
        <xsl:value-of select="/ns0:DVM/ns0:FromValue"/>
      </ns0:FromValue>
      <ns0:ToKey>
        <xsl:value-of select="/ns0:DVM/ns0:ToKey"/>
      </ns0:ToKey>
      <ns0:ToValue>
        <xsl:call-template name="LookupDVM">
          <xsl:with-param name="domainName" select="/ns0:DVM/ns0:Domain"/>
          <xsl:with-param name="fromKey" select="/ns0:DVM/ns0:FromKey"/>
          <xsl:with-param name="fromKeyValue" select="/ns0:DVM/ns0:FromValue"/>
          <xsl:with-param name="toKey" select="/ns0:DVM/ns0:ToKey"/>
        </xsl:call-template>
      </ns0:ToValue>
    </ns0:DVM>
  </xsl:template>
  <!--  User Defined Templates  -->
  <xsl:template name="LookupDVM">
    <xsl:param name="domainName"/>
    <xsl:param name="fromKey"/>
    <xsl:param name="fromKeyValue"/>
    <xsl:param name="toKey"/>
    <xsl:variable name="parentPath"
                  select="concat('/domains/domain[@name=&quot;',$domainName,'&quot;]' )"/>
    <xsl:variable name="result"
                  select="orcl:lookup-xml($configURI,$parentPath,$fromKey,$toKey ,$fromKeyValue)"/>
    <xsl:value-of select="$result"/>
  </xsl:template>
</xsl:stylesheet> 
This for those cases that you just want to lookup some values from the domain-xml this construction can be handy. In other cases where you need to lookup multiple values in an complexer xsl't you can copy and paste the construction above.

Since the CoreLookupDVM.xml is a, quite simple, xml file, it can easily be transformed to an 11g lookupDVM file.

Thursday, 10 January 2013

Google Docs: the clipboard-sharing tool in the cloud

Today I was asked to support a customer with some urgent issues. To be able to help them quickly I was given access via a VMware View Client (a Citrix alike online desktopsharing solution from VMware).
From that desktop I connected to an internal desktop with a Remote Desktop session, since there were all the necessary tools, like Notepad++, jDeveloper, SoapUI, etc, installed. In the end I neede another RDP session to the server on which SoaSuite was running.

Anyway, during the analasis I kept a report document in Google Docs. At one point I needed to write an email in which I wanted to share some information from logs on the remote desktop. Now I could connect to gmail. But I was already writing the email in Thunderbird on my laptop. Since there were so may layers of RDP-in-RDP sessions, I could not copy and paste from the logs to my laptop anymore.

Then the team-cooperation functionality from Google Docs come in handy! I opened the same document again on my laptop and Google Docs neatly synchronized both sessions. So anything I wrote and copied-and-pasted into the document in the RDP session became visible on my laptop and vice-versa. Now, I know: it's not new, but it enabled me to copy-and-paste from the document into my email.

So, Google Docs turned out the perfect clipboard sharing tool. I found it a nice trick to share.

Thursday, 6 December 2012

Generate Tradingpartners for Oracle B2B 11g with Ant

Introduction

At my previous customer, a Dutch energy infrastructure managing company, I worked on an implementation of AS2 (http://en.wikipedia.org/wiki/AS2) message exchanging with Oracle B2B (part of SOASuite 11g) The company needed to exchange information about energy delivery with other companies that supply and transport energy.

Problem

Since there are many companies in the enegery market that exchange messages with eachother, in our case, we needed to enter about 80 tradingpartners (TP's), that were very similar in message-exchange capabilities. Instead of entering those 80 TP’s in Oracle B2B by hand, which is a lot of error-prone work, I decided to see if it was possible to automate the process, by generating an export file that would serve as input property file for B2B. Hence, as a start I looked at the B2B selfservice scripts. With those (ANT) scripts you can generate an export file based on a set of definition input xml files. Afterwards this export file can be imported into B2B. This last step can be done manually, but you could even import (deploy) the generated export file and even deploy the agreements automatically.

Generating the export file from an addressing properties input Excel sheet 

In our case we had two roles: shippers and suppliers. There are a few differences between the two tradingpartner roles, but all the shippers have the same capabilities as well as all the suppliers.
Hence, shippers and suppliers send and receive about the same set of messages.
Also, all the TP’s are identified in the same way, using a code. Furthermore, all the addressing properties were delivered in an Excel sheet. So I could generate a property file naming all the TP’s with all their properties and their roles. Based on the TP’s role I determined if the TP should be enabled or not: only suppliers and shippers had to be imported, the rest of the TP's I disabled in the property file.

Lets take a look in how this was done.

Initializing

For the scripting I have some base targets and a base property file.
The property file:

dev.mode=false
#dev.mode=true
demo.mode=false
#demo.mode=true

#Generic Environment variables
wl_home=${wn.bea.home}/wlserver_10.3
ant-contrib.jar=${wn.bea.home}/modules/net.sf.antcontrib_1.1.0.0_1-0b2/lib/ant-contrib.jar

# temp
tmp.output.dir=c:/temp
log.dir=${basedir}/logs
host.tpl=${basedir}/templates/Host_tpl.xml
doctypes=${basedir}/templates/DocTypes.xml
supplier.tpl=${basedir}/templates/Supplier_tpl.xml
shipper.tpl=${basedir}/templates/Shipper_tpl.xml
b2bselfservice.tpl.dir=${basedir}/b2bselfservice
b2bselfservice.exp.file=${basedir}/zip/B2BExport.zip


junit.output.dir=../../

jndi.props.tpl=${basedir}/jndi.properties.template
jndi.props=${oracle.home}/bin/jndi.properties

Then I have a library.xml with a LogMessage target:
<?xml version="1.0" encoding="windows-1252" ?>
<project name="library">
      <macrodef name="logMessage">
            <attribute name="message" default=""/>
            <attribute name="level" default="debug"/>
            <sequential>
                  <echo message="@{message}" level="@{level}"></echo>
                  <!-- <echo file="${log.file}" append="true"
                        message="@{message}${line.separator}" level="@{level}"></echo> -->
                  <echo file="${log.file}" append="true"
                        message="@{message}${line.separator}"></echo>
            </sequential>
      </macrodef>
</project>

Now and the start of my build.xml:

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- B2B Utilities
@author Martien van den Akker, Darwin-IT Professionals
@version 0.1, 2012-11-22
-->
<project name="B2BScripting" basedir=".">
    <property environment="env"/>
    <property name="deploy.basedir" value="${basedir}"/>
    <!-- Default environment property dir -->
    <property file="${basedir}/build.properties"/>
    <property file="${env.prop.dir}/tradingPartner.properties"/>
    <import file="${basedir}/library.xml"/>
    <taskdef resource="net/sf/antcontrib/antlib.xml">
        <classpath>
            <pathelement location="${ant-contrib.jar}"/>
        </classpath>
    </taskdef>
    <!-- Deploy Targets -->
    <!-- Clean Up logs -->
    <target name="clean">
        <echo>Delete log dir ${log.dir}</echo>
        <delete dir="${log.dir}"/>
    </target>
    <!-- initialize project -->
    <target name="init" depends="clean">
        <echo>Dev mode: ${dev.mode}</echo>
        <echo>Target environment: ${deployment.plan.environment}</echo>
        <echo message="Create log dir" level="debug"></echo>
        <mkdir dir="${log.dir}"/>
        <!-- Build time -->
        <tstamp>
            <format property="build.date" pattern="yyyy-MM-dd HH:mm:ss"/>
        </tstamp>
        <!-- Build number -->
        <condition property="build.number" value="${env.BUILD_NUMBER}">
            <isset property="env.BUILD_NUMBER"/>
        </condition>
        <buildnumber file="build.num"/>
        <property name="log.file"
                  value="${log.dir}/instance-${build.number}.log"/>
    </target>
    <!-- log env properties -->
    <target name="logEnvProps" depends="init">
        <echo>BEA Home: ${wn.bea.home}</echo>
        <echo>Oracle Home: ${oracle.home}</echo>
        <echo>Java Home: ${java.home}</echo>
        <echo>Log File: ${log.file}</echo>
    </target>

The templates

As a starting point for the scripts I used the documentation on the B2B Utilities, see: B2B Command-Line Tools. Also I found the blog of Anuj Dwivedi on this subject very usefull.

You can put all the definitions of DocumentTypes, Trading partners and agreements in one file. Or you can split them up in several files. Important is that the different files have to be ordered alphabetically, first the documenttypes, then the ones with the tradingpartners and then the agreements. I decided to have the documenttypes in one file named 1_docTypes.xml, the host trading partner named 2_tp_1234567000004.xml and then the different tradingpartners named 3_TP-<id>.xml. Where the <id> is expanded to the 18-digit code of the particular remote TP. And I put the agreements and the tradingpartner-definitions per tradingpartner in one file.

The documentypes

The documenttypes are defined as follows:

<?xml version="1.0" encoding="windows-1252" ?>
<SelfService xmlns="http://xmlns.oracle.com/integration/b2b/selfservice/profile">
  <DocumentProtocols>
    <DocumentProtocol name="Custom">
      <DocumentProtocolVersion name="MSGORG_V1.2">
        <!-- METER -->
        <DocumentType name="METERAcknowledgementDocType">
          <DocumentDefinition customFileType="true"
                              definitionFileName="../../../EnergyCieMDS/apps/MSGORG/xsd/v1.2/METERAcknowledgement_1p2.xsd"
                              name="METERAcknowledgement"
                              useDefaultDefinition="false">
            <ParameterValue name="IdentificationExpression"
                            value='/*[local-name()="METERAcknowledgementEnvelope"]'/>
          </DocumentDefinition>
        </DocumentType>
        <DocumentType name="METERNotificationDocType">
          <DocumentDefinition customFileType="true"
                              definitionFileName="../../../EnergyCieMDS/apps/MSGORG/xsd/v1.2/METERNotification_1p2.xsd"
                              name="METERNotification"
                              useDefaultDefinition="false">
            <ParameterValue name="IdentificationExpression"
                            value='/*[local-name()="METERNotificationEnvelope"]'/>
          </DocumentDefinition>
        </DocumentType>
      </DocumentProtocolVersion>
    </DocumentProtocol>
  </DocumentProtocols>
</SelfService>

You see that the main tag is "SelfService". This tag applicable for every selfservice-xml-file.  So all the definitions should be contained within the  "SelfService"  tag.
The xsd that this xml implements can be generated using the SelfService ant utilities that are delivered with SOASuite as follows:

set BEA_HOME=D:\Oracle\Middleware\SOASuite11g
set ORACLE_HOME=%BEA_HOME%\Oracle_SOA1
set ANT_HOME=%BEA_HOME%\modules\org.apache.ant_1.7.1
set PATH=%ANT_HOME%\bin;%PATH%
set JAVA_HOME=%BEA_HOME%\jdk160_24 

ant -f %ORACLE_HOME%\bin\ant-b2b-util.xml  b2bselfservicexsd 
This delivers an XSD that you can register in JDeveloper. Doing so, JDeveloper can help you edit the XML, since it knows what nodes and attributes come where.

The Host Tradingpartner

The host tradingpartner comes next in line, here's my sample template:
<?xml version="1.0" encoding="windows-1252" ?>
<SelfService xmlns="http://xmlns.oracle.com/integration/b2b/selfservice/profile">
  <TradingPartners>
    <TradingPartner hosted="true" name="${hostTp.name}">
      <!-- Host Trading Partner -->
      <Identification name="Name" value="${hostTp.name}"/>
      <Identification name="Generic" value="${hostTp.generic.name}"/>
      <Identification name="AS2 Identifier" value="${hostTp.AS2.id}"/>
      <DeliveryChannel ackMode="None" compressed="false" internal="true"
                       listening="false"
                       name="${hostTp.short}_AQ_IN_INT_Channel"
                       responseMode="None" retryCount="3" retryInterval="3">
        <ExchangeProtocolRef name="Generic-AQ"/>
        <TransportProtocolRef name="AQ">
          <ParameterValue name="queue_name" value="IP_IN_QUEUE"/>
          <ParameterValue name="datasource" value="jdbc/SOADataSource"/>
        </TransportProtocolRef>
      </DeliveryChannel>
      <SupportedDocumentDefinition docDefName="METERAcknowledgement"
                                   docProtocolName="Custom"
                                   docProtocolVersion="MSGORG_V1.2"
                                   docTypeName="METERAcknowledgementDocType"
                                   initiator="false"/>
      <SupportedDocumentDefinition docDefName="METERNotification"
                                   docProtocolName="Custom"
                                   docProtocolVersion="MSGORG_V1.2"
                                   docTypeName="METERNotificationDocType"
                                   initiator="true"/>
    </TradingPartner>
  </TradingPartners>
</SelfService>

So here you see that the name of the tradingpartner as well as the AS2 Identifier is filled with an ANT property that will come from  a property file that I will give later on.
Since in our case the Tradingpartners where identified by 18-digit-numbers, I added a "Generic" identifier for a more human-readable name. After the identifications come the Delivery Channels, and after that the SupportedDocumentDefinitions: the capabilities. The Internal Delivery Channel I used was the AQ, which I think is the preferred.

The Remote TradingPartner

<?xml version="1.0" encoding="windows-1252" ?>
<SelfService xmlns="http://xmlns.oracle.com/integration/b2b/selfservice/profile">
  <TradingPartners>
    <TradingPartner hosted="false" name="${tp.name}">
      <Identification name="AS2 Identifier" value="${tp.AS2.id}"/>
      <Identification name="Name" value="${tp.name}"/>
      <Identification name="Generic" value="${tp.generic.name}"/>
      <DeliveryChannel listening="false"
                       name="${tp.name}_AS2_OUT_EXT_SNGENC_Channel"
                       internal="false">
        <ExchangeProtocolRef name="AS2"/>
        <TransportProtocolRef name="HTTP">
          <ParameterValue name="url" value="${tp.AS2.host}"/>
          <ParameterValue value="true" name="use_proxy"/>
        </TransportProtocolRef>
        <DigitalSecurity ackSigned="true" messageEncrypted="true"
                         messageSigned="true" transportSecured="true">
          <DigitalSignature securitySpecificationName="SMIME-3_0-SHA-RSA"
                            certAlias="${hostTp.cert.alias}"/>
          <DigitalEncryption securitySpecificationName="SMIME-3_0-DES"
                             certAlias="${tp.cert.alias}"/>
          <!--     <TransportSecurity securitySpecificationName="SSL"
                             certAlias="${tp.cert.alias}"/> -->
        </DigitalSecurity>
      </DeliveryChannel>
      <SupportedDocumentDefinition docDefName="METERAcknowledgement"
                                   docProtocolName="Custom"
                                   docProtocolVersion="MSGORG_V1.2"
                                   docTypeName="METERAcknowledgementDocType"
                                   initiator="true"/>
      <SupportedDocumentDefinition docDefName="METERNotification"
                                   docProtocolName="Custom"
                                   docProtocolVersion="MSGORG_V1.2"
                                   docTypeName="METERNotificationDocType"
                                   initiator="false"/>
     
    </TradingPartner>
  </TradingPartners>
  <Agreements>
    <!--METER-->
    <Agreement agreementId="${tp.name}_Custom_MSGORG_V1.2_METERAcknowledgementDocType_METERAcknowledgement_Inbound"
               name="${tp.name}_Custom_MSGORG_V1.2_METERAcknowledgementDocType_METERAcknowledgement_Inbound"
               effectiveFromDate="${agreement.effectiveFromDate}" >
      <SupportedDocumentType docProtocolName="Custom"
                             docProtocolVersion="MSGORG_V1.2"
                             docTypeName="METERAcknowledgementDocType"
                             docDefName="METERAcknowledgement">
        <InitiatingParticipant name="${tp.name}">
          <Identifications>
            <IdentificationRef name="Name" value="${tp.name}"/>
            <IdentificationRef name="AS2 Identifier" value="${tp.AS2.id}"/>
          </Identifications>
          <DeliveryChannels>
            <DeliveryChannelRef name="${tp.name}_AS2_OUT_EXT_SNGENC_Channel"/>
          </DeliveryChannels>
        </InitiatingParticipant>
        <RespondingParticipant name="${hostTp.name}">
          <Identifications>
            <IdentificationRef name="Name" value="${hostTp.name}"/>
            <IdentificationRef name="AS2 Identifier" value="${hostTp.name}"/>
          </Identifications>
          <DeliveryChannels>
            <DeliveryChannelRef name="${hostTp.short}_AQ_IN_INT_Channel"/>
          </DeliveryChannels>
        </RespondingParticipant>
      </SupportedDocumentType>
    </Agreement>
    <Agreement agreementId="${tp.name}_Custom_MSGORG_V1.2_METERNotificationDocType_METERNotification_Outbound"
               name="${tp.name}_Custom_MSGORG_V1.2_METERNotificationDocType_METERNotification_Outbound"
               effectiveFromDate="${agreement.effectiveFromDate}" >
      <SupportedDocumentType docProtocolName="Custom"
                             docProtocolVersion="MSGORG_V1.2"
                             docTypeName="METERNotificationDocType"
                             docDefName="METERNotification">
        <InitiatingParticipant name="${hostTp.name}">
          <Identifications>
            <IdentificationRef name="Name" value="${hostTp.name}"/>
            <IdentificationRef name="AS2 Identifier" value="${hostTp.name}"/>
          </Identifications>
          <DeliveryChannels/>
        </InitiatingParticipant>
        <RespondingParticipant name="${tp.name}">
          <Identifications>
            <IdentificationRef name="Name" value="${tp.name}"/>
            <IdentificationRef name="AS2 Identifier" value="${tp.AS2.id}"/>
          </Identifications>
          <DeliveryChannels>
            <DeliveryChannelRef name="${tp.name}_AS2_OUT_EXT_SNGENC_Channel"/>
          </DeliveryChannels>
        </RespondingParticipant>
      </SupportedDocumentType>
    </Agreement>

  </Agreements>
</SelfService>
The Remote trading partner is basically the same, except that I can have more of these and in stead of the AQ Delivery Channel, I have a AS2 Delivery channel. And of course the capabilities, the SupportedDocumentDefinitions are complementary to those of the Host. That means that where the host is Initiator, the Remote TP is not and vice versa.

Within each TP I also have agreements for each SupportedDocumentDefinition stating the correct Identifications and delivery channels.

And that's about it for the templates.

 The Script

Propertyfile

First the property file of the tradingpartners. I named the file tradingpartner.properties:
tp.list=1234567000005,1234567000006,1234567000004 
hostTp.name=1234567000004
hostTp.short=NMI
hostTp.generic.name=NetManager Inc.
hostTp.AS2.id=1234567000004
hostTp.cert.alias=1234567000004netManager
agreement.effectiveFromDate=2012-11-27T00:00:00+01:00
#agreement.effectiveToDate=
# Trading Partner Props
#tp.enabled=true
#tp.name=
#tp.role=
#tp.generic.name=
#tp.AS2.id=
#tp.AS2.host=
#tp.cert.alias=
#tp.SSLcert.alias=
#
# 1234567000005 - SupplierCie
1234567000005.enabled=true
1234567000005.name=1234567000005
1234567000005.role=Supplier
1234567000005.generic.name=Supplier Cie
1234567000005.AS2.id=1234567000005
1234567000005.AS2.host=https://b2b-prd.SupplierCie.nl/as2-endpoint
1234567000005.cert.alias=1234567000005SupplierCie
#1234567000005.SSLcert.alias=
#
# 1234567000006 - ShipperCie
1234567000006.enabled=true
1234567000006.name=1234567000006
1234567000006.role=PV/shipper
1234567000006.generic.name=Shipper Cie
1234567000006.AS2.id=1234567000006
1234567000006.AS2.host=https://b2b-prd.ShipperCie.nl/as2-endpoint
1234567000006.cert.alias=1234567000006ShipperCie
#1234567000006.SSLcert.alias=
#
# 1234567000004 - NetManager Inc.
1234567000004.enabled=false
1234567000004.name=1234567000004
1234567000004.role=NNO
1234567000004.generic.name=NetManager Inc.
1234567000004.AS2.id=1234567000004
1234567000004.AS2.host=https://b2b.netmanager.com/b2b/httpreceiver
1234567000004.cert.alias=1234567000004NMI.cer
#1234567000004.SSLcert.alias=

Create All TP Definitions

The folllowing target is the main target for generating the trading partner definitions.
It starts with copying the doctype template to the folder where the selfservice files are gathered. Then the selfservice file for the host TP is generated based on the template explained above. At the end for each tradingpartner named in the tp.list in the tradingpartner property file the corresponding selfservice file is generated. The last step is calling the target that calls the selfservice api to generate the B2B export zip-file.
 <!-- Create All TP Definitions -->
    <target name="createAllTPDefinitions" depends="logEnvProps">
        <logMessage message="createAllTPDefinitions" level="info"/>
        <logMessage message="basedir ${basedir}" level="info"/>
        <logMessage message="date = ${build.date}" level="info"/>
        <logMessage message="build = ${build.number}" level="info"/>
        <logMessage message="environment = ${deployment.plan.environment}"
                    level="info"/>
        <logMessage message="List of TradingPartners = ${tp.list}"
                    level="info"/>
        <delete dir="${b2bselfservice.tpl.dir}"/>
        <delete file="${b2bselfservice.exp.file}"/>
        <mkdir dir="${b2bselfservice.tpl.dir}"/>
        <logMessage message="======> Copy ${doctypes}" level="info"/>
        <copy file="${doctypes}"
              tofile="${b2bselfservice.tpl.dir}/1_docTypes.xml"
              overwrite="true">
            <filterchain>
                <expandproperties/>
            </filterchain>
        </copy>
        <copy file="${host.tpl}"
              tofile="${b2bselfservice.tpl.dir}/2_tp_${hostTp.name}.xml"
              overwrite="true">
            <filterchain>
                <expandproperties/>
            </filterchain>
        </copy>
        <foreach list="${tp.list}" param="tradingPartner"
                 target="createTPDefinitions" inheritall="true"
                 inheritrefs="false"/>
        <!-- Create B2B Export -->
        <antcall target="b2bCreateExport"/>
    </target>

Create the Remote Trading Partner definitions

Below the target for the generation of the Remote TP definitions.
It does a copy of the properties form the selected trading partner to corresponding generic  properties. These are used in the expansion of the properties in the tradingpartner template on copying the file to the selfservice file for the selected tradingpartner. Since there are two tradingpartner-types in our case (Shippers and Suppliers) you'll find an if on the tradingpartner role. Based on the role the specific template is being copied to the remote tradingpartner's selfservice file.

<!-- Create TradingPartner Definitions -->
    <target name="createTPDefinitions">
        <logMessage message="createTPDefinitions" level="info"/>
        <propertycopy name="tp.enabled" from="${tradingPartner}.enabled"/>
        <echo message="${tradingPartner} enabled:${tp.enabled}"></echo>
        <if>
            <equals arg1="${tp.enabled}" arg2="true"/>
            <then>
                <logMessage message="${line.separator}====>Create config for Tradingpartner ${tradingPartner}"
                            level="info"/>
                <propertycopy name="tp.name" from="${tradingPartner}.name"/>
                <propertycopy name="tp.role" from="${tradingPartner}.role"/>
                <propertycopy name="tp.generic.name"
                              from="${tradingPartner}.generic.name"/>
                <propertycopy name="tp.AS2.id" from="${tradingPartner}.AS2.id"/>
                <propertycopy name="tp.AS2.host"
                              from="${tradingPartner}.AS2.host"/>
                <propertycopy name="tp.cert.alias"
                              from="${tradingPartner}.cert.alias"/>
                <logMessage message="======> Name: ${tp.name}" level="info"/>
                <logMessage message="======> Role: ${tp.role}" level="info"/>
                <logMessage message="======> Generic Name: ${tp.generic.name}"
                            level="info"/>
                <logMessage message="======> AS2.id: ${tp.AS2.id}"
                            level="info"/>
                <logMessage message="======> AS2.host: ${tp.AS2.host}"
                            level="info"/>
                <logMessage message="======> cert.alias: ${tp.cert.alias}"
                            level="info"/>
                <if>
                    <equals arg1="${tp.role}" arg2="Supplier"/>
                    <then>
                        <logMessage message="======> Copy ${supplier.tpl} for ${tp.name}"
                                    level="info"/>
                        <copy file="${supplier.tpl}"
                              tofile="${b2bselfservice.tpl.dir}/3_tp_${tp.name}.xml"
                              overwrite="true">
                            <filterchain>
                                <expandproperties/>
                            </filterchain>
                        </copy>
                    </then>
                </if>
                <if>
                    <equals arg1="${tp.role}" arg2="PV/shipper"/>
                    <then>
                        <logMessage message="======> Copy ${shipper.tpl} for ${tp.name}"
                                    level="info"/>
                        <copy file="${shipper.tpl}"
                              tofile="${b2bselfservice.tpl.dir}/3_tp_${tp.name}.xml"
                              overwrite="true">
                            <filterchain>
                                <expandproperties/>
                            </filterchain>
                        </copy>
                    </then>
                </if>
            </then>
            <else>
                <logMessage message="${line.separator}====>Skip Tradingpartner ${tradingPartner}"
                            level="info"/>
            </else>
        </if>
    </target>

Create B2B Export

The above targets generate the document types, host-tradingpartner and remote tradingpartner definitions in files in a specific folder, denoted by the ${b2bselfservice.tpl.dir} property.
Based on the generated files in that folder the b2bexport zip can be generated, using the following target:
<target name="b2bCreateExport">
        <logMessage message="${line.separator}====>Create B2B Export"
                    level="info"/>
        <if>
            <equals arg1="${demo.mode}" arg2="false"/>
            <then>
                <ant antfile="${oracle.home}/bin/ant-b2b-util.xml"
                     inheritall="false" target="b2bselfservice">
                    <property name="input" value="${b2bselfservice.tpl.dir}"/>
                    <property name="output" value="${b2bselfservice.exp.file}"/>
                </ant>
            </then>
        </if>
    </target>

Conclusion

The export-file that is generated in the last target can then be imported to the target environment.
This import can be done with the import/export tooling under the administration tab within the b2bconsole. I found that somehow it only works with internet explorer. Using Firefox or Chrome I get an error-message stating that the Zip is not valid.
It is also possible to import the file using scripting like I explained above. But since this blog is already becoming a heavy one, I keep that for a following blog. Keeps me writing. And actually with the explanation above you should be able to figure that out...

Tuesday, 20 November 2012

Target B2BUI

When you already did something with B2B11g, then you probably already know how to connect to the B2B UI, and have found out that it's not available by default.

By default the B2B UI is not targetted to a managed server. It can be done very easy in the weblogic console (http://adminserver-host:port/console). Go to Deployments and look for b2bui, when found click on it.

Then check the boxes of the  b2bui components and click on "Change Targets" (this button will activate after checking the boxes).
Then in the next screen click yes:
 And after a while the changes are applied:
After this the b2b console can be addressed via: http://managedserver-host:port/b2bconsole.

Monday, 12 November 2012

Export Exception in B2B11g

Recently I renamed some docTypes in B2B11g because of a typo. Last week I wanted to export the repository to put it into subversion and to be able to propagate the implementation to the test environment.

But when exporting I ran into the following exception:
MDS-00001: exception in Metadata Services layer MDS-00511: failure to create document /soa/b2b/Custom/DocVersion_V1.2/MyAcknowledgementDocType /MyAcknowledgement /MyAcknowledgement.xsd 
\DocVersion_V1.2\MyAcknowledgementDocType \MyAcknowledgement \MyAcknowledgement_1p2.xsd 
C:\Windows\TEMP\Export817535\soa\b2b\Custom\DocVersion_V1.2\MyAcknowledgementDocType \MyAcknowledgement \MyAcknowledgement_1p2.xsd (The system cannot find the path specified)

What I actually did in the renaming was to create a new doctype with docdefinition, along the incorrect ones, with copy/pasting the oldnames and changing the typo (...Acknowlegdement... into ...Acknowledgement...). But apparently I got an extra trailingspace in my new name.  It appeared in two docTypes.

I removed the deployments of the particular agreements, removed the document registrations at the Trading Partners (both host and remote) and then removed the DocType under the Administration/Documents menu.

After that I could create the documentTypes again (now payed extra attention to possible trailing spaces), recreated the document registration at the partners and the agreements. After that I could export the repository succesfully again.

Bottom line: you apparently can have trailing spaces in the names, but this will lead into export errors...

B2B11g with Apache 2.0 as forward proxy

In the past I wrote an article about high availability architecture for Oracle Integration B2B10g.
Currently I'm working on an AS2 implementation with Oracle B2B11g. B2B11g is now part of SOASuite11g. Although it can be seen as a gateway product, much like OSB, since it is part of SOASuite I would not recommend to place B2B in de DMZ (Demilitarized Zone).

So what you need is a proxy and reverse proxy in the DMZ to forward messages from B2B to your trading partner (forward proxy) and route messages from your trading partner to B2B (reverse proxy).

The idea in our setup was actually to use Microsoft IIS for this. The reverse proxy was succesfull implementated, where it also functions as a SSL-decoder. But the IIS-expert had difficulties to turn the forward-proxy functioning on. Now that should not be to hard, but it need to have proxy, and proxy-connect (for proxying SSL-requests) installed. Apparently those are seperate installs for IIS. You may conclude: me know nothing about IIS. Also, the IIS-expert was not around very much and we needed a proxy in place pretty much immediatly.

So I installed Apache for that. I installed a Apache 2.0.x. Not the latest  release, I know, because I hoped to be able to re-use my settings from my earlier implementation. However, those turned out to be from 1.3 (forgot about that).

Anyway I had to change my settings a little.

First you need to turn some modules (mod_proxy, mod_proxy_http and mod_proxy_connect) on in the http.conf

#2012-10-31, M. van den Akker, Darwin-IT Professionals
# mod_proxy en mod_proxy_http needed for proxy
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so

The proxy-connect module is needed to be able to proxy for SSL connections.
Since I like to seperate my customsettings from the other ones I created a separate config file for the proxy-settings. This I include at the end of the http.conf:

#2012-10-31, M. van den Akker, Darwin-IT Professionals
#Include proxy config
Include conf/proxy.conf

Then of course the proxysettings in the proxy.conf file:
#2012-10-31, M. van den Akker, Darwin-IT Professionals

Listen 1234
NameVirtualHost proxy-web-b2b:1234
ProxyRequests On
ProxyVia On
AllowCONNECT 443 8443 
<Proxy *>
  #Order deny,allow
  #Deny from all
  Allow from all
</Proxy> 
<VirtualHost proxy-web-b2b:1234>
  ServerAdmin webmaster@proxy-web-b2b
  ErrorLog logs/error_proxyfwd1234.log
  CustomLog logs/access_proxyfwd1234.log common
</VirtualHost>
The AllowCONNECT setting denotes the ports that are allowed for SSL-Connect forwards.

Since we use this temporarly only for forward proxying, I don't have the Apache 2.0 settings for reverse proxying at hand. Since B2B11g is the initiator of the https-connection it needs to have the SSL certificates of remote tradingpartners, besides the AS2 encryption/signing certificates in the keystore. So as is done in our case it does not make much sense to have the reverse-proxy (IIS in our implementation) do the un-SSL-ing (decoding). Then you'll need to maintain those certificates in two keystores.


For the forward proxy the weblogic server of B2B needs to be started with the following settings:
  • -Dhttp.proxySet=true
  • -Dhttp.proxyHost=proxy-web-b2b
  • -Dhttp.proxyPort=1234
In the B2B configuration on the delivery channels the checkbox "Use Proxy" needs to be checked.

That's about it.