Sunday, August 4, 2019

Dataweave Tricks: Extract Keys and Values From HashMap

Introduction

I have a requirement to extract the keys and values from a dataset like the following:
{
  "account1" : {
      "accountID" : "1234",
      "name" : "Mary Loo",
      "balance" : 234.32
     },
  "account2" : {
      "accountID" : "1234",
      "name" : "Lauren Flor",
      "balance" : 234.32
     },
  "account3" : {
      "accountID" : "1234",
      "name" : "Mary Loo",
      "balance" : 234.32
     }         
}
Apparently, the above data is a Map. Now we need to produce two dataset of values and keys from the input (Map) as the following:
[
    {
        "accountID": "1234",
        "name": "Mary Loo",
        "balance": 234.32
    },
    {
        "accountID": "1234",
        "name": "Lauren Flor",
        "balance": 234.32
    },
    {
        "accountID": "1234",
        "name": "Mary Loo",
        "balance": 234.32
    }
]
and
[
    "account1",
    "account2",
    "account3"
]

Understanding Dataweave pluck Function

Dataweave has devised a function particularly for this kind of requirement. Here is the solution to extract values of a HashMap
%dw 2.0
output application/json
---
payload pluck (item, key, index) -> item
The shorthand version:
%dw 2.0
output application/json
---
payload pluck $
And to extract the keys:
%dw 2.0
output application/json
---
payload pluck (item, key, index) -> key
The shorthand version:
%dw 2.0
output application/json
---
payload pluck $$

Saturday, August 3, 2019

Dataweave 2.0 Tricks: Sorting and Grouping

The Challenges

I have Accounts retrieved from Salesforce like the following:

[
    {
        "LastModifiedDate": "2015-12-09T21:29:01.000Z",
        "Id": "0016100000Kngh3AAB",
        "type": "Account",
        "Name": "AAA Inc."
    },
    {
        "LastModifiedDate": "2015-12-09T20:16:47.000Z",
        "Id": "0016100000KnXKhAAN",
        "type": "Account",
        "Name": "AAA Inc."
    },
    {
        "LastModifiedDate": "2015-12-12T02:06:48.000Z",
        "Id": "0016100000KqonvAAB",
        "type": "Account",
        "Name": "AAA Inc."
    },
...
]
The dataset contains many accounts which have the same name. These accounts with the same account are regarded as duplicates Eventually I want to delete the duplicates and just leave one in the SFDC. Before I delete the duplicates, I need to create an output for review like the following:
{
    "AAA Inc.": [
        "0016100000Kngh3AAB",
        "0016100000KnXKhAAN",
        "0016100000KqonvAAB",
        "0016100000KnggyAAB",
        "0016100000KngflAAB",
        "0016100000KqalVAAR",
        "0016100000Kngh8AAB",
        "0016100000KnVUKAA3",
        "0016100000Kngh5AAB",
        "0016100000KnVXdAAN",
        "0016100000KnVh4AAF",
        "0016100000KnVs6AAF",
        "0016100000KnggAAAR",
        "0016100000KnlokAAB",
        "0016100000KnggKAAR"
    ],
    "Adam Smith": [
        "0016100000L7sDjAAJ"
    ],
    "Alice John Smith": [
        "0016100000L7x29AAB"
    ],
    "Alice Smith.": [
        "0016100000L7sDiAAJ"
    ],
...

Solutions

I device a two-stage solution. The first transform will create LinkedHashMap which will contain account name as key and the value as array of Account as shown below:
%dw 2.0
output application/java
---
//payload groupBy $.Name orderBy $$
(payload groupBy (account) -> account.Name)  orderBy (item, key) -> key
The second stage of transformation is to extract the account ID as the following:
%dw 2.0
output application/java
---
payload mapObject (item, key, index) -> {  
 (key) : (item map (value) -> value.Id)  
}
Of course, I can put the two Dataweave scripts into one like the following:
%dw 2.0
output application/java
---
//payload groupBy $.Name orderBy $$
((payload groupBy (account) -> account.Name)  orderBy (item, key) -> key)
mapObject (item, key, index) -> {  
 (key) : (item map (value) -> value.Id)  
}

Key Learnings

The key concept of the above use case is to group the accounts with the same name and sort them in alphabetic order. The Mulesoft document about the groupBy and orderBy together with other core functions of dataweave can be found here The groupBy and orderBy have the similar signature:

1. groupBy(Array, (item: T, index: Number) -> R): { (R): Array }
2. groupBy({ (K)?: V }, (value: V, key: K) -> R): { (R): { (K)?: V } }
3. groupBy(Null, (Nothing, Nothing) -> Any): Null
The first function indicates that it can take array as input. The usage will like the following:
//payload groupBy $.Name
//payload groupBy (account, index) -> account.Name
payload groupBy (account) -> account.Name
The above code are the same. The first one is a short-cut version. The second and third lines are to show the lambda style. As a good developer, you should know all the syntax.

In my solution of the second stage, I use mapObject function as the following:

payload mapObject (item, key, index) -> {  
 (key) : (item map (value) -> value.Id)  
}
This is because the payload is a LinkedHashMap and the value of each HashMap entry is an array. That is why I have to use map function inside the mapObject function.

In my work, I also need to remove the type attribute in the Account object.

[
    {
        "LastModifiedDate": "2015-12-09T21:29:01.000Z",
        "Id": "0016100000Kngh3AAB",
        "type": "Account",
        "Name": "AAA Inc."
    },
...
]
Here is the transform to remove the type:
(payload orderBy (item) -> item.Name) map (account) -> {
 (account -- ['type'])
}
As you can see I have used function of --.

Summary

The key thinking of solve this kind of problem how to group, sort, and extract values from the Array or LinkedHashMap. Thus I have used the following core Dataweave functions:
  • groupBy
  • orderBy
  • map
  • mapObject
Also, we should know the short-hand way and Lambda style for using the Dataweave functions. The short-hand way is to use build-in variable $, $$, $$$.
If the payload is Array
  • $ - item
  • $$ - index
If the payload is LinkedHashMap
  • $ - value
  • $$ - key
  • $$$ - index.
The Lambda style is like the following:
(payload orderBy (item) -> item.Name) map (account, index) -> {
 (account -- ['type'])
}
mapObject (item, key, index) -> {  
 (key) : (item map (value) -> value.Id)  
}

Mule Application Hacking: Reveal Details Of A Connector's Connection

Introduction

In my last hacking post, I described the method to view the source code of Mule connectors using the ECD. In this article, I am go demonstrate the procedure to reveal the details about connection and communication within the Mule Connector. This is very useful for the purpose of trouble-shooting.

I will use Salesforce connector as example to demonstrate how to read connection details, query, etc.

Details

First, looking for the connector as shown in the following snapshots:
As you can see, I have added Salesforce connection version 9.7.7. Now, we need to expand the connector. Then looking for mule-salesforce-connector-9.7.7-mule-pluging.jar and expand the jar file as shown below:
Now we can see that the java class packages all under org.mule.extension.salesforce.

The next step is to add the package to log4j.xml in the dir of src/main/resources as shown below:

Now add the package of org.mule.extension.salesforce into the Loggers section of the log4j.xml file as shown below
    
        
        
        
        
        
        
    
          
        
 
        
            
        
    
Now, if you run the project, the console will deplay the debugging information about the connection information, request, and response from Salesforce connectors.

Saturday, July 27, 2019

Hacking Mule Application - View Source Code

Introduction

To become a real good Mule developer, we need to understand the source code of Mule connectors, components, and other internal source code. This helps us to learn the internal data model and interfaces of Mule classes. I have written a blog on how to compile and install the ECD in Anypoint Studio. However, that building system is broken for Mule 4. Hopefully, it will be fixed soon.

This article demonstrate another way to view the source code. It is not the best solution yet, but it help. The idea is to use Eclipse IDE and ECD plugin to review the java source code.

ECD is so far the best Java Decompiler available for Eclipse. The details can be found at here

Install Eclipse & ECD Plugin

First download the Eclipse EE from this site. Second, create a java project. Third, install ECD Eclipse Plugin. go to Eclipse Market Place of ECD
Drag the install to your project.
Now to preferences, you should see the Decompiler under Java as shown belog:
Fourth, import jar archive. right click the project --> select Achive File
browser the file from your local maven repository. In my case, it is at .m2/repository/org/mule/connectors/mule-http-connector/1.5.3.
Import the jar file to the newly created project. Drill down the classed you are interested as shown in the following snapshot:
Note that you have to chose the decompiler by right click java class --> open with, select as shown below:
At this point, we can review the source code. The next step is to decompiler the whole jar file and rebuild it with source. In this we can debug the source code and modify the behavior of the connectors. I will cover the procedure later.

Saturday, June 29, 2019

SSL Handshake Failure Connecting To Mulesooft Anypoint Exchange In Corporate Environment

The Issue

As a Mulesoft developer, we will need to download connectors from Anypoint exchange periodically. When we try to connect to Mulesoft Anypoint Exchange, which is the repository for Mulesoft related connectors and other libraries, we may get SSH Handshake exception, in particular, using corporate provided laptop. Here is the top part of the exception message:
eclipse.buildId=unknown
java.version=1.8.0_212
java.vendor=Oracle Corporation
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=en_US
Command-line arguments:  -os win32 -ws win32 -arch x86_64

org.mule.tooling.core
Error
Thu Jul 11 17:49:09 CDT 2019
The following exceptions were encountered while resolving dependency com.mulesoft.connectors:mule-salesforce-connector:9.7.6: java.lang.RuntimeException: There was an issue resolving the dependency tree for the bundleDescriptors [[BundleDescriptor{groupId='com.mulesoft.connectors', artifactId='mule-salesforce-connector', baseVersion='null', version='9.7.6', type='jar', classifier=Optional[mule-plugin]}, BundleDescriptor{groupId='org.mule.connectors', artifactId='mule-objectstore-connector', baseVersion='null', version='1.0.0', type='jar', classifier=Optional[mule-plugin]}]]
 at org.mule.maven.client.internal.AetherMavenClient.resolvePluginBundleDescriptorsDependencies(AetherMavenClient.java:322)
 at org.mule.tooling.core.m2.internal.MuleMavenClientResolver.resolvePluginDependencies(MuleMavenClientResolver.java:80)
 at org.mule.tooling.core.module.internal.runner.DownloadTask.doRun(DownloadTask.java:76)
 at org.mule.tooling.core.module.internal.runner.Task.run(Task.java:65)
 at org.mule.tooling.core.module.internal.runner.DownloadTask.run(DownloadTask.java:1)
 at org.mule.tooling.core.module.internal.runner.ArtifactResolvingRunner$ArtifactJob.run(ArtifactResolvingRunner.java:212)
 at org.eclipse.core.internal.jobs.Worker.run(Worker.java:56)
Caused by: org.eclipse.aether.collection.DependencyCollectionException: Failed to collect dependencies at com.mulesoft.connectors:mule-salesforce-connector:jar:mule-plugin:9.7.6 -> com.mulesoft.connectors:mule-connector-commons:jar:2.1.1
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.collectDependencies(DefaultDependencyCollector.java:291)
 at org.eclipse.aether.internal.impl.DefaultRepositorySystem.collectDependencies(DefaultRepositorySystem.java:316)
 at org.mule.maven.client.internal.AetherMavenClient.doResolveDependencies(AetherMavenClient.java:408)
 at org.mule.maven.client.internal.AetherMavenClient.resolvePluginBundleDescriptorsDependencies(AetherMavenClient.java:314)
 ... 6 more
Caused by: org.eclipse.aether.resolution.ArtifactDescriptorException: Failed to read artifact descriptor for com.mulesoft.connectors:mule-connector-commons:jar:2.1.1
 at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.loadPom(DefaultArtifactDescriptorReader.java:282)
 at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.readArtifactDescriptor(DefaultArtifactDescriptorReader.java:198)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.resolveCachedArtifactDescriptor(DefaultDependencyCollector.java:535)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.getArtifactDescriptorResult(DefaultDependencyCollector.java:519)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.processDependency(DefaultDependencyCollector.java:409)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.processDependency(DefaultDependencyCollector.java:363)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.process(DefaultDependencyCollector.java:351)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.doRecurse(DefaultDependencyCollector.java:504)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.processDependency(DefaultDependencyCollector.java:458)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.processDependency(DefaultDependencyCollector.java:363)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.process(DefaultDependencyCollector.java:351)
 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.collectDependencies(DefaultDependencyCollector.java:254)
 ... 9 more
Caused by: org.eclipse.aether.resolution.ArtifactResolutionException: Could not transfer artifact com.mulesoft.connectors:mule-connector-commons:pom:2.1.1 from/to mulesoft-releases (https://repository.mulesoft.org/releases/): sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:444)
 at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolveArtifacts(DefaultArtifactResolver.java:246)
 at org.eclipse.aether.internal.impl.DefaultArtifactResolver.resolveArtifact(DefaultArtifactResolver.java:223)
 at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.loadPom(DefaultArtifactDescriptorReader.java:267)
 ... 20 more


This article describes the procedures to fix this kind of issues.

Find The Root Cause

Problems solving skills are really about to find the root cause of the issue. In this issue, if we look the error message carefully, we will find the following:

Caused by: org.eclipse.aether.resolution.ArtifactResolutionException: Could not transfer artifact com.mulesoft.connectors:mule-connector-commons:pom:2.1.1 from/to mulesoft-releases (https://repository.mulesoft.org/releases/): sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

What this error message is saying that Java process was trying to transfer data from host of: repository.mulesoft.org. The problem is the SSL Handshake. To resolve this kind of problem we need to import the certificate from the site to cacerts.

Tasks

In this article, I only import 3 certificates from the following site:

  • anypoint.mulesoft.com
  • maven.anypoint.mulesoft.com
  • release.anypoint.mulesoft.com

Prerequisites

  1. On Windows install Cygwind
  2. Have Admin privilege of the laptop

Solutions

When we connect to Mulesoft Anypoint Exchange, AnypointStudio needs to go through SSL Handshake procedure before we can see the download page. If this process failed, typically, AnypointStudio (a java process) could not store the certificate), the SSHHandshakeException will be thrown by the studio. The following steps will fix the issue:

Step One: Download certificate from anypoint.mulesoft.com
openssl s_client -connect anypoint.mulesoft.com:443 -showcerts </dev/null 2>/dev/null |openssl x509 -outform PEM >anypoint.pem
Step Two: Download certificate from maven.anypoint.mulesoft.com
openssl s_client -connect maven.anypoint.mulesoft.com:443 -showcerts   </dev/null 2>/dev/null | openssl x509 -outform PEM >mulesoft.maven.pem
Step Three: Download certificate from repository.anypoint.mulesoft.com
openssl s_client -connect repository.mulesoft.org:443 -showcerts   </dev/null 2>/dev/null | openssl x509 -outform PEM >mulesoft.repo.pem
Step Four: Copy the 3 certificates
cp *.pem /cygdrive/c/’Program Files’/Java/jdk1.8.0_212/jre/lib/security
As you can see that I am using cygwin on Windows. On Macbook Pro, the JAVA_HOME is may be different. In this case the JAVA_HOME is under:
/cygdrive/c/’Program Files’/Java/jdk1.8.0_212
Step Five: Import the certificate to cacerts
/cygdrive/c/’Program Files’/Java/jdk1.8.0_212/jre/lib/security
keytool -import -alias anypoint -keystore cacerts -file anypoint.pem
Repeat the same procedure for the other 2 certificaes. Step Fix: Restart Anypoint Studio

Saturday, March 16, 2019

Email Address Validation Using Dataweave Regex

Introduction

This short article is about how to use regex with Dataweave 2.0 with regard to email validation. Regex is used in Mulesoft language very wide with regard two functions, matchs(...) and match(...). It is very important to master the regular expression in order to be professional in Mulesoft integration projects. The are a lot of reference available one. Here are few:

Use Case

We expect the output of the dataweave transformation as the following depending on the validity of the email address:
[
    {
        ...
        "invalidEmail": "johndo@yahoo"
        ...
    },
    {
        ...
        "PersonEmail": "john.smith@google.com"
        ...
    },
    ...
]

Invalid Emails

The following types of emails are invalid:
  1. beginning with a dot: .gary.liu@google.com
  2. ending with a dot: gary.liu@google.com.
  3. double dots: gary.liu@google..com
  4. domain name contains underscore: gary.liu@att_rr.com
  5. domain name contains space: gayr.liu@att rr.com
  6. domain name contains and of the following: ,<>/[]
  7. no organization email: gary@google

Solution

%dw 2.0
output application/json
var regexEmail = /^[^.][a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-](?!.*?\.\.)[^_ ; ,<>\/\\]+(?:\.[a-zA-Z0-9-]+)[^.]*$/
---
payload map using (email = $.email) {
 (validEmail: email) if (email matches regexEmail),
 (invalidEmail: email) if ( not  (email matches regexEmail))
}
The above dataweave script is self-explanatory. Few explanation is required if you are not very familiar with regular expression:
  1. negation: [^_;,\.] this expression means if the email domain contains underscore _ , semi-coma, etc. is not valid email
  2. simple ^ and $ represent the beginning and end of the line
  3. [a-zA-Z0-9] mean any charater A a, Bb ... Zz, or 0 to 9 digits are valid
  4. + sign means to match one for more
  5. * sign matches 0 or more
  6. ?! means not include, (?!.*?\.\.) --> not include double dots: ..

Saturday, March 2, 2019

How To Resolve Issue With: "General SSLEngine problem"

The Background

This happens when you enable the HTTPS with your own certificates. In my case, I have configured Anypoint runtime fabrics with self generated certification using the following command:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
The above command generates two files: cert.pem and key.pem. The purpose of them is beyond the scope of the article. The error will occur when the local mule flow call the remote application which is deployed in the Anypoint Runtime Fabrics.

Solution

To resolve this problem, we just need to import the cert.pem to cacerts file. The command is (on MacOs):
cd /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/security
sudo keytool -import -trustcacerts -keystore cacerts -storepass changeit -alias rogers-poc-cert -file /Users/gl17/anypoint/certs/poc/cert.pem
Make sure restart Anypoint Studio.

Anypoint Studio Error: The project is missing Munit lIbrary to run tests

Anypoint Studio 7.9 has a bug. Even if we following the article: https://help.mulesoft.com/s/article/The-project-is-missing-MUnit-libraries-...