Friday, 26 October 2018

Recursion in XSLT

Last week I helped someone on the Oracle community forums with transforming a comma separated string to a list of elements. He needed this to process each element in BPM Suite, but it is a use case that can come around in SOA Suite or even in Oracle Integration Cloud.

You would think that you could do something like a for-each and trimming the element from the variable.

Recursion

One typical thing with XSLT is that variables are immutable. That means that you can declare a variable and assign a value to it, but you cannot change it. So it is not possible to assign a new value to a variable based on a substring of that same variable.

To circumvent this, you should implement a template that conditionally calls itself until an end-condition is met. This is a typical algorithm called recursion. Recursion is a way of implementing a function that calls itself, for example to calculate the faculty of a number. Recursion can help circumventing the immutability of variables, because with every call to the function you can pass (a) calculated and thus different value(s) through the parameter(s).

I wrote about this earlier, but last week a co-worker asked a similar question, but just the other way around: transforming a list into a comma separated string.

So, apparently it's time to write an article about it.

Transforming CSV to a List

I refactored the xsd's from the question as follows. First the source xsd:
<?xml version="1.0" encoding="windows-1252" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.example.org/Approvals/Source"
            targetNamespace="http://www.example.org/Approvals/Source" elementFormDefault="qualified">
  <xsd:element name="ApprovalRoute" type="tns:approvalRouteByInvoiceNatureResponse"/>
  <xsd:complexType name="approvalRouteByInvoiceNatureResponse">
    <xsd:sequence>
      <xsd:element type="xsd:string" name="approvalRoute" minOccurs="0"/>
      <xsd:element type="xsd:boolean" name="autoApprove" minOccurs="0"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

And the target schema is:
<?xml version="1.0" encoding="windows-1252" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.example.org/Approvals/Target"
            targetNamespace="http://www.example.org/Approvals/Target" elementFormDefault="qualified">
  <xsd:element name="ApprovalRoute" type="tns:ApprovalRouteType"/>
  <xsd:complexType name="ApprovalRouteType">
    <xsd:sequence>
      <xsd:element name="Approver" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

To start with, we have an ApprovalRoute element based on a complex type with the approvalRoute sub-element being the comma-separated list of approvers. Then as a target we have an ApprovalRoute, based on a list of Approver elements.

I generated the following source xml to transform:
<?xml version="1.0" encoding="UTF-8" ?>
<ApprovalRoute xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.example.org/Approvals/Source SOA/Schemas/Approvals-Source.xsd"
               xmlns="http://www.example.org/Approvals/Source">
  <approvalRoute>Approver1,Approver2,Approver3,Approver4,Approver5</approvalRoute>
  <autoApprove>true</autoApprove>
</ApprovalRoute>

Now, we need to split the approvalRoute value in a part before the first comma, and after the first comma. The value before the first comma can be put in an element. But the remainder has to be fed into the same template again. Then, at the end there is no comma in the remainder, so the part before the comma will be empty. There is no comma anymore, so we should not call the template with the remainder, but simply put the remainder in an element. Therefor, the non-existence of the comma can be the end-condition.

Remember, using recursion, you should always have a finalizing condition. To be honest, in my first piece of code in the answer of the question, I forgot about that. But, to my defence: I just put it together by heart and haven't been able to test.

The explanation above results in the following template:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
                xmlns:tns="http://www.example.org/Approvals/Target"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:ns0="http://www.example.org/Approvals/Source" xmlns:xsl="
                http://www.w3.org/1999/XSL/Transform">
  <!-- https://community.oracle.com/thread/4178385 -->
  <xsl:template match="/">
    <tns:ApprovalRoute>
      <xsl:call-template name="parseDelimitedString">
        <xsl:with-param name="delimitedStr" select="/ns0:ApprovalRoute/ns0:approvalRoute"/>
      </xsl:call-template>
    </tns:ApprovalRoute>
  </xsl:template>
  <xsl:template name="parseDelimitedString">
    <xsl:param name="delimitedStr"/>
    <!-- https://www.w3schools.com/xml/xsl_functions.asp -->
    <xsl:variable name="firstItem" select="substring-before($delimitedStr, ',')"/>
    <xsl:variable name="restDelimitedStr" select="substring-after($delimitedStr, ',')"/>
    <tns:Approver>
      <xsl:value-of select="$firstItem"/>
    </tns:Approver>
    <xsl:choose>
      <xsl:when test="contains($restDelimitedStr, ',')">
        <xsl:call-template name="parseDelimitedString">
          <xsl:with-param name="delimitedStr" select="$restDelimitedStr"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <tns:Approver>
          <xsl:value-of select="$restDelimitedStr"/>
        </tns:Approver>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

(I created this as an XSL Map, but removed the comments that were included by JDeveloper.
I tested this with the following input:
<?xml version="1.0" encoding="UTF-8" ?>
<ApprovalRoute xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.example.org/Approvals/Source SOA/Schemas/Approvals-Source.xsd"
               xmlns="http://www.example.org/Approvals/Source">
  <approvalRoute>Approver1,Approver2,Approver3,Approver4,Approver5</approvalRoute>
  <autoApprove>true</autoApprove>
</ApprovalRoute>

And this resulted in the following output:
<?xml version = '1.0' encoding = 'UTF-8'?>
<tns:ApprovalRoute xmlns:tns="http://www.example.org/Approvals/Target" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.org/Approvals/Target file:/D:/Projects/2018-ODC/XSL-Demo/XSL-Demo/SOA/Schemas/Approvals-Target.xsd">
   <tns:Approver>Approver1</tns:Approver>
   <tns:Approver>Approver2</tns:Approver>
   <tns:Approver>Approver3</tns:Approver>
   <tns:Approver>Approver4</tns:Approver>
   <tns:Approver>Approver5</tns:Approver>
</tns:ApprovalRoute>

This I used for input for the following xslt.

The other way around: List to CSV

For didactional reasons I'll show the other way around too. Although, we'll see that this can be done easier.

In this case I mean to loop over a series of elements, starting with an index of 1, and adding the elements to a partial string. That means I have 3 parameters:
  • loopApprovers: the parent element, containing all the elements to loop over
  • index: the loop index, with a default of 1
  • partialApprovalRoute: the partial CSV list, defaulted to an empty string

The template loopApprovers can be called with only the approvalRoute. Then with an index of 1, the template is called recursively the first time, with a partialApprovalRoute assigned with the first Approver occurence and an index increased with 1.
For the other occurences where index > 1 and index <= count of elements, the template is called again recursively, but with an increased index and the indexed element added to the partialApprovalRoute separated with a comma.
Then the end situation is when the template is called where index exceeds the count of elements. Then just the partialApprovalRoute is 'returned'  (by the value-of instruction) where it is substringed to a 20000 characters:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
                xmlns:ns0="http://www.example.org/Approvals/Target"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:tns="http://www.example.org/Approvals/Source" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <tns:ApprovalRoute>
      <tns:approvalRoute>
        <xsl:call-template name="loopApprovers">
          <xsl:with-param name="approvalRoute" select="/ns0:ApprovalRoute"/>
        </xsl:call-template>
      </tns:approvalRoute>
    </tns:ApprovalRoute>
  </xsl:template>
  <xsl:template name="loopApprovers">
    <xsl:param name="approvalRoute"/>
    <xsl:param name="index" select="1"/>
    <xsl:param name="partialApprovalRoute" select="''"/>
    <xsl:choose>
      <xsl:when test="number($index)=1">
        <xsl:call-template name="loopApprovers">
          <xsl:with-param name="approvalRoute" select="$approvalRoute"/>
          <xsl:with-param name="index" select="$index+1"/>
          <xsl:with-param name="partialApprovalRoute" select="$approvalRoute/ns0:Approver[1]"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="number($index)> 1 and number($index)&lt;=count($approvalRoute/ns0:Approver)">
        <xsl:call-template name="loopApprovers">
          <xsl:with-param name="approvalRoute" select="$approvalRoute"/>
          <xsl:with-param name="index" select="$index+1"/>
          <xsl:with-param name="partialApprovalRoute"
                          select="concat($partialApprovalRoute,',',$approvalRoute/ns0:Approver[number($index)])"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="substring($partialApprovalRoute,1,20000)"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Simpler transformation from list to csv

As can be found here for instance, a for-each does not necessarily need to return an element. It can return just a value. So, it can be a bit simpeler:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
                xmlns:ns0="http://www.example.org/Approvals/Target"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:tns="http://www.example.org/Approvals/Source" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- http://p2p.wrox.com/xslt/72164-xslt-need-concatenate-strings-loop-hold-them-later-use.html -->
  <xsl:template match="/">
    <tns:ApprovalRoute>
      <tns:approvalRoute>
        <xsl:call-template name="loopApprovers">
          <xsl:with-param name="approvalRoute" select="/ns0:ApprovalRoute"/>
        </xsl:call-template>
      </tns:approvalRoute>
    </tns:ApprovalRoute>
  </xsl:template>
  <xsl:template name="loopApprovers">
    <xsl:param name="approvalRoute"/>
    <xsl:variable name="approvalRouteCsv">
      <xsl:for-each select="$approvalRoute/ns0:Approver">
        <xsl:value-of select="concat(substring(.,1,20000),',')"/>
      </xsl:for-each>
    </xsl:variable>
    <xsl:value-of select="substring($approvalRouteCsv,1,string-length($approvalRouteCsv)-1)"/>
  </xsl:template>
</xsl:stylesheet>

Conclusion

Understanding Recursion with XSLT will help you with solving much complexer problems in transformations. The last example of transforming a list to a comma separated list is of course structural easier. But the recursive variant allows for more calculations or conditional processing.

Friday, 12 October 2018

Enable X11 on Oracle Cloud Infrastructure

Today my colleague was starting with the installation of Oracle Database on the Oracle Cloud Infrastrcture, for a customer. He phoned me for help on enabling VNC to have a graphical UI to install the database.

Install an ssh client with XServer emulator

Most of my co-workers of around my age, have grown up with Putty. And apparently we as computer consultants are quite hooked to our tools. I know, only yesterday I mentioned it to a co-worker, that everywhere I come the two first tools I install are:
  1. Firefox (unfortunately to be downloaded using IE or Edge, it's from then on immediately the last time I use those browsers, as far as I'm concerned)
  2. Total Commander (of course downloaded with Firefox): I really hate Windows Explorer, ever since it is introduced with Windows 95/NT. Windows 3.1 had FileManager (yes I"m that old...)

    I liked that much better than the successor Windows Explorer. But little filemanager beat the revival of Norton Commander: Total Commander. I use it almost all of my carreer, and so much that quite early on I bought a key for what we would call 'an apple and an egg'.

    But about the the third tool I install is:
  3. MobaXterm:this tool is so much richer than Putty. It includes a SCP/SFTP client that can follow your terminal. Which means, every time you cd to a certain folder, your SFTP window will cd to it as well. I like the fonts, the looks more, it's more comfortable. But it also includes a XServer, with no additional install! It's free, with a limited number of sessions though. However, the cost for a lifetime license is really low.
So, as soon as I see a co-worker work with Putty, I recommend MobaXterm (no, I don't have shares).
Quite because of the same reasons I would recommend Total Commander over Win Explorer.

When connecting to a server, MobaXterm, by default (although you can uncheck it) will do X11Forwarding.

However, in the case of my colleague, unfortunately X11-forwarding was disabled:

We installed xclock which additionally installed several X-libraries. We checked XForwarding in /etc/ssh/sshd_config. All with no luck. But, we were so close. In the end, the answer (thanks Radu) was in this whitepaper. We needed to set the X11UseLocalhost property to no in /etc/ssh/sshd_config.

The complete setup, following the whitepaper:
  1. Log into the instance (obviously)
  2. Configure SSHD to not use localhost for X11:
    1. Open /etc/ssh/sshd_config in your favorite editor.
    2. Search for the line that has X11UseLocalhost (it’scommented out).
    3. Uncomment  the line by removing the # from the beginning.
    4. Set the property to no.
    5. While you're at it, check if the property X11Forwarding is set to yes.
    6. Save the file.
    7. Restart the ssh daemon by executing: sudo systemctl restart sshd
  3. Install xauth by executing: sudo yum-y install xauth
  4. Install xterm (used to verify X configuration) by executing: sudo yum -y install xterm
  5. Also isntall xclock for simple testing of the X Forwarding: sudo yum -y install xclock
Now, reconnect using MobaXterm, and you should see that X11-forwarding is enabled:

When running xclock on the remote terminal will show a clock on your local desktop.
As can be seen in the screendump, you might run into the message 'Missing charsets in String to FontSet conversion'. This can be solved following this hint by RedHat. It is caused by improper locale environment variable. Run the following:
export LC_ALL=C

You might want to add this to your ~/.bash_profile. Then run xclock again.

But, but, but... I can't log on to oracle...

To make things slightly more complicated, in most situations, you can't logon as the oracle user. You get a user to connect, and then have to sudo to oracle. In those cases you ned to redirect your xauth.

So, perform the following:
  1. Connect as the user provided 
  2. List your xauth by executing: xauth list $DISPLAY (you might need to check the DISPLAY variable)
    This would provide a line like:
    darlin123/unix:11  MIT-MAGIC-COOKIE-1  1231a6f34cca12394d3233456230df26
  3. Sudo to oracle: sudo su - oracle
  4.  Then set the DISPLAY using the port from the xauth list above:
    export DISPLAY=darlin123:11
    (In some examples explaining this X forwarding across users, you might see export DISPLAY=localhost:11. But, remember: we disabled the use of localhost above).
  5. Then add the autorisation with:
    xauth add darlin123/unix:11  MIT-MAGIC-COOKIE-1  1231a6f34cca12394d3233456230df26

    Also take over the port in the display, as well as the GUID (in green) from the xauth list.

That should work!

ODC Appreciation Day : SOA Suite 12c and the Community

Yesterday Tim - oracle-base.com - Hall had the ODC Appreciation Day initiative.

I gladly join in, however yesterday I hadn't had the change to write something. I did a tweet to remember it:
But today, on the day-after I do like to write something.

SOA Suite 11g/12c

Just yesterday I mentioned a coworker that I indeed still like SOA Suite.
I worked at Oracle Consulting when Oracle acquired Collaxa in 2004, and some other companies whose technologies were at the base of Oracle SOASuite. Since then I work with SOASuite and BPEL, and I still like it.

When in 2008 BEA was acquired, SOASuite 11g was released in 2009 based on Weblogic. It was actually delayed because of making Weblogic as a strategic platform.

The thing with SOA Suite11g and 12c, based on Weblogic, is that it has become a quite draconic system, as I used to call it. The footprint is quite large, both on disk as well as in memory.

I understand the MicroServices up coming for two reasons:
  • Especially when it comes together with containerization, MicroService applications startup quite fast. SOA Suite startups can last 10 minutes to about a quarter of an hour.
  • We must admit that we messed up SOA projects. Yes, we made them quite complex and we did not manage to do proper Service Governance, not even Service Registration, Service Reuse, etc. Driven by project-funding, time-pressures, lack of enforcing proper scrum project procedures, etc., we deliver mostly one-purpose services, not following the proper SOA principles. Also yesterday, at my current customer, my co-worker and I concluded that there are mostly point-to-point integrations. Back in 2008 I invented the term POPOTOPI for it.
Are MicroServices the answer then? I'm not really sure, I must admit:
  • It seems to me that with MicroServices initiatives we're going to build services in Java again. Indeed with frameworks like SpringBoot and JPA. But still we seem to program the logic in Java again. And when we're able to mess up BPEL with lacking standards (no use of Scopes and local variables for instance), Java allows us for even bigger messes. Are we really going to program services in Java frameworks with 50+ possible elements?
    Where end user Application development earlier transformed from for instance Oracle Forms to Java/ADF, now transforms to Low Code (VBCS, OutSystems, Oracle Forms 😉?, etc.) I expect a same transformation in MicroServices.
  • With containerization we apparently create CI/CD initiatives delivering the complete Service Application with an app server (often Tomcat) complete in a container. So a new bug-release deliveres a container with a complete installation. Which add to a large library of the different versions of the containers.
  • MicroServices have good thoughts underneath it, as said it is an answer to problems encountered in SOA projects. But why not build MicroServices in SOA Suite or OSB? SOA Suite and OSB are the current LowCode environments in Services. 
I don't see that the technology is the problem here. I've seen several times with failing projects that the problem is not the SOA Technologies, but more the project politics and governance.

So, I still like working with SOA Suite. However, I would like some improvements:
  • JDeveloper could be made more stable. Although on the same IDE platform, SQLDeveloper is as stable as a castle. JDeveloper, however, is stuffed with loads of designers, addons, build by numerous teams. But, please, prevent us from all those Null-pointer exceptions, and exception dialogs asking me to save my work and quite or continue.
  • In a SOA Suite installation I encounter loads of jars of different version for the same library at different location. It seems to me that we could get rid of a significant amount of those.
    The main differentiator between Oracle 9i database and Oracle 10g was the foot print. I think with FMW (SOA Suite, but also OSB and other products), could take advantage of a great "spring cleaning". 
  • One of the new features of Java 9, is the modularisation. In SOA Suite we have loads of adapters installed (some of them not activated), and other functionality that aren't used. So a modularisation of Weblogic and FMW products on top of it would be a great idea. Only install what I need and provide a package manager that allows me to install let's say an SAP Adapter or SAML (either 1 or 2.0) support when I need it later on.

 How about Cloud?

Yes, cloud. Of course cloud is important, and I completely understand why Oracle has a near 100% focus on Cloud. But, I seem to be one of the few people that has difficulty with believing that in let's say 5 years 100% of all our customers are for 100% of their business 'in the cloud'. Let alone, the Oracle Cloud. And to straighten my statements: I don't see IaaS as actual Cloud. To me, IaaS is the same as 'On Premise', but in another Data Center. Of course Oracle might have different License policies if you want to run SOA Suite on Amazon or Azure or a local IaaS provider, with regards to  running on Oracle Cloud. But I don't encounter any customers that won't run their software on Virtual Machines (VMWare ESX or alike). So even in their own DataCenters, software is run virtualized. When not on an external IaaS provider, they are essentially their own IaaS provider.

And although Oracle Integration Cloud is promising, there is much I'd rather do on SOA Suite. And I expect that there are several good reasons for current customers to stay on a On Premise or local IaaS serviced SOA Suite installation.

Also, Oracle Integration Cloud, and other Oracle PaaS-es, are based on the exact same platforms and engines (BPEL/BPM Process Engine, Business Rules, WSM, Service Bus, etc., etc.). So, my above suggested improvements would improve the PaaS-es as well.

So, Oracle Please add SOA Suite and OSB to the Fusion Middleware 19c category in the Supported Systems and Configurations:
And as has been asked on the community.oracle.com forums this week a few times: please add support for Java 9/10/11 on the upcoming Weblogic and FMW releases.

community.oracle.com

Since a few years I try to keep up and be active on the community.oracle.com forums for SOA, Weblogic, etc. It's great to be of any help, answering questions and collect points (to me it's a bit of playing something like Forge of Empires). I sometimes get questions via email. But it's better to go over there. You could go to Stack Overflow, but why not form a community at Oracle's own community page? Come over and join us. Meet me there. But when you do, please:
  • Edit your profile and enter a proper name: I do like to know how you want to becalled, instead of user 12345656 of alike,
  • Ask a question together with your error-message, in stead of just posting your error message. It happens that some one just posts the error message and I often must refrain my self from answering "Congratulations!".
  • I'm putting in my time answering questions. The only rewards I get for it are points. Please, give me my points by marking my answers as help-full (50 points) or 'Answered' (even 100 points and I can register them for my ACE review).

I'm looking forward for another year of Oracle Fusion Middleware and other infrastructure technologies in the Oracle Developer Community.

Thursday, 4 October 2018

Persisting of settings in a SOA Suite Enterprise Deployment

About a year ago, at my previous customer, a co-worker and I encountered and described a persistence problem with setting the Global Tokens in SOA Suite.

What are Global Tokens again?

The problem with a middleware product as Oracle Service Bus, SOA Suite (and the same probably counts for MuleSoft, or any other integration tool) is that when you move an integration through the development lifecycle from development, to test, preproduction and production, you need to update the endpoints. When I have an integration with a (BPEL) Process that does a check-in of a document in WebCenter Content, for instance, then on the test environment it should do the check-in to another WCC server than on pre-production or production. We don't want to have our test documents in production, do we?

To solve that, in OSB we have customization files, and in SOA Suite 11g and onwards, we use config plans. But, in 11g PatchSet 6 (11.1.1.7), SOA Suite introduced Global Tokens. That way you can create a token that refers to the WCC host, eg. ${wcc_url}, and use that as a reference in your binding properties.

These properties can be set using Enterprise Manager FMW Control 12c:
which lead to:
Where you can add tokens or import a file with the tokens.

These settings are stored in the mdm-url-resolver.xml file, in the $DOMAIN_HOME/config/fmwconfig folder.

What about Enterprise Deployment?

The Enterprise Deployment Guide of SOA Suite 12c is quite complex. But in short, as we implemented it, we installed Fusion Middleware Infrastructure and SOA Suite on one node/host. Then, of course, ran the Repository Creation Utility, and configured the domain for the AdminServer. That domain was configured on a shared disk, let's say, /u01/data/oracle/domains/soa1_domain. Then it is cloned to cater for the managed servers, using pack/unpack, to local storage, for instance, /u02/data/oracle/domains/soa1_domain. In short we have 2 domain homes:
  • ASERVER_DOMAIN_HOME=/u01/data/oracle/domains/soa1_domain
  • MSERVER_DOMAIN_HOME=/u02/data/oracle/domains/soa1_domain
Where  /u01 is mounted on a shared disk and /u02 on local storage.

So, the AdminServer runs from the ASERVER_DOMAIN_HOME domain, that is on shared storage. This way, when the host running the AdminServer goes down, the AdminServer can be brought up on the second host. The Managed Servers run on a clone of the domain on local storage.

Side note: in 12c we have a per-domain NodeManager by default. So cloning the domain, implicitly clones the nodemanager config. And running that against another adapter, allows for a nodemanager for the Admin server and one for the ManagedServers.

Why is this important? Well, this allows for a High Available setup, including functionality as Zero Downtime Patching.

What is the problem then?

Updating the Global Tokens is done in FMW Control, that runs on the AdminServer. It stores the properties in the mdm-url-resolver.xml. But, which particular mdm-url-resolver.xml file? Well, the changes are stored in the one in $MSERVER_DOMAIN_HOME/config/fmwconfig!
After that you need to restart the SOA Server, to get the properties loaded. And then something very smart happens. When starting the SOA Server, the AdminServer sends it's copy from the $ASERVER_DOMAIN_HOME/config/fmwconfig to the SOA Server. And so the changes are cleared by the version from the AdminServers domain!

So, in an Enterprise Deployment configuration of the SOA Suite a restart of the SOA Server, will clear the changes of the Global Tokens.

But there is more!

As I wrote above, we found this a year ago. And we created a Prio 1 Service Request. The issue is very straight forward, reproducable, and in the status Development Working for about a year now:
(I'm not writing this to bash Support by the way. No offense intended, altough I would really like a patch by now...)

But, today another co-worker and I encountered a very similar problem with configuring the email driver of the User Messaging Services. A description on how to configure that can be found here. The email driver settings are stored in driverconfig.xml in the $MSERVER_DOMAIN_HOME/config/fmwconfig/servers/soa_server1/applications/usermessagingdriver-email/configuration.

And again, restarting the domain, or soa_server1, these are overwritten by the driverconfig.xml at the same subfolder location in the $ASERVER_DOMAIN_HOME! And since this works like this for the Global Tokens and the email-driver it problably works like this for other UMS drivers, or even other functionality.

The workaround?

Is quite simple: copy the updated mdm-url-resolver.xml or driverconfig.xml in the
$MSERVER_DOMAIN_HOME to the counterparts in $ASERVER_DOMAIN_HOME. Then start the servers again. On startup the AdminServer will copy it's variant (that is a copy of the correct, updated one) to the SOA Server again.

Conclusion

I still do like SOA Suite. It's an impressive Middleware suite. But I really hope Oracle does invest in making it more stable, decreasing the footprint and adapt the functionality to the Enterprise Deployment Guide. Since, the behavior above does not match the recommendations as described in the EDG. And I think SOA Suite, OSB and even Weblogic could be a lot smaller and faster with the same functionality. I encounter a lot of duplicated libraries throughout the ORACLE_HOME. Or several different versions of the same library. I assume those can be reduced quite a bit. And that will benefit both the Cloud variants of the software as the On Premise variant.