Recently I had a use case where I needed to change the implementation of a Java class to set its attributes value to empty string instead of nulls. The java class is used in DataWeave transformation to convert JSON data into Java object. Java class and DataWeave object looks similar to the example below:
public class MyClass {
private String x;
private String y;
public String getX() {return x;}
public void setX(string x) {this.x = x;}
public String getY() {return y;}
public void setY(string x) {this.y = y;}
}
dw:transform-message doc:name="Transform Message">
<dw:set-payload><![CDATA[%dw 1.0
%output application/java
---
payload map {
x: $.attrib1,
y: $.attrib2,
} as :object {
class : "com.apnovation.blog.MyClass"
}]]>
</dw:set-payload>
</dw:transform-message>
Now changing that is very trivial if you have access to both DataWeave component source or the Java class source. I can withe extend MyClass and override the setters (or the getters) to return empty strings instead of null. Or even I can leave MyClass as-is and change the DataWeave transformation to set x and y into empty strings if attrib1 or attrib2 are nulls.
The problem is that I do not have access to neither of them (Class or DataWeave component). Both are inside a library that I have to use the functionality as one black box where it read data from the source system, convert it, and pass it to the next stage.
The solution that came into my mind is using aspects where I change the behavour of MyClass by applying an "around" advise on my setters to change the passed parameter values into empty string if it is null.
More information about Aspects and AOP is available here . Basically AOP is intended for cross cutting concerns where you would like to apply some side concerns to a class without tight-coupling this code with existing code. A few common uses of this are application logging, auditing and transaction handling.
Given that Muleosft is a spring container too, this means I can use Spring AOP in my spring beans. Something similar to the code below:
@Aspect
public class MyAdvise {
@Around("execution(* com.appnovation.blog.MyClass.set*(..))")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
args[i] = "";
}
}
return pjp.proceed(args);
}
}
<spring:beans>
<spring:bean id="MyBean" class="com.apnovation.blog.MyClass" />
<spring:bean id="MyAdvise" class="com.apnovation.blog.MyAdvice" />
<aop:aspectj-autoproxy />
</spring:beans>
This way any call to MyBean spring bean x or y attributes will invoke the intercept method. Example #[app.registry.MyBean.x = null]
The problem with this approach is that MyClass is not created as a spring bean in my use case. It is created internally by the DataWeave java implementation code through direct Java instantiation. I need to weave the advise object into MyClass outside the spring container. This require using AspectJ compiler/weaver directly in my JVM.
More info about using Spring AOP vs. AspectJ weaver available here.
To do that, I need to add the following aop.xml file in src/main/resources/META-INF folder:
<aspectj>
<aspects>
<aspect name="com.apnovation.blog.MyAdvice" />
</aspects>
<weaver options="-verbose">
<include within="com.apnovation.blog.MyClass" />
<include within="com.apnovation.blog.MyAdvice" />
</weaver>
</aspectj>
Also the following command line argument need to be added to the mule runtime start: -javaagent:/Users/Alaa/lib/aspectjweaver-1.6.11.jar (you can add it to wrapper.conf file for Mule server, in AnypointStudio just add it as a VM argument in the run configuration).
With this approach, it doesn't matter if MyClass is created through spring container or the java code just instantiate it directly through #[new MyClass()] expression. Both cases the advise will be applied.