简体   繁体   中英

Mule 4 is modifying Java object variables

I'm migrating my Mule apps from 3.9.0 to Mule 4.4.0. I'm using a Java object to propagate metadata and other objects across transports. Here is a snippet of that class:

package com.test;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class DataWrapper implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private final Map<String, Object> properties = new HashMap<>();
    private Object payload;
    private final Object originalPayload;
    
    public DataWrapper(final Object payload) {
        this(payload, false);
    }
    
    public DataWrapper(final Object payload, boolean playback) {
        this.originalPayload = payload;
        this.payload = payload;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getProperty(String name, T defaultValue) {
        if(properties.containsKey(name)) {
            return (T) properties.get(name);
        } else {
            return defaultValue;
        }
    }
    
    public Object getProperty(String name) {
        return getProperty(name, null);
    }
    
    public void setProperty(String name, Object value) {
        properties.put(name, value);
    }
    
    public void setProperties(Map<String, Object> additional) {
        properties.putAll(additional);
    }

    public Object getPayload() {
        return payload;
    }

    public void setPayload(Object payload) {
        this.payload = payload;
    }
    
    public Object getOriginalPayload() {
        return originalPayload;
    }
    
    public static DataWrapper wrapData(Object payload) {
        return new DataWrapper(payload);
    }
    
    public static DataWrapper wrapData(Object payload, Map<String, Object> properties) {
        DataWrapper wrapper = new DataWrapper(payload);
        wrapper.setProperties(properties);
        return wrapper;
    }
}

the static methods being called (simplified for example):

public class MyTransformers {
    
    public static DataWrapper addProperties(DataWrapper wrapper, Map<String, Object> properties) {
        //process payload and add properties/objects
        wrapper.setProperties(properties);
        return wrapper;
    }
    
    public static String doSomethingWithPayload(DataWrapper wrapper, Map<String, Object> properties) {
        //process payload and add properties/objects
        return (String) wrapper.getPayload();
    }
    
    public static String doSomethingElse(DataWrapper wrapper) {
        //do something else with payload
        return (String) wrapper.getPayload();
    }
}

and the flows:

<?xml version="1.0" encoding="UTF-8"?>

<mule 
    xmlns:sockets="http://www.mulesoft.org/schema/mule/sockets"
    xmlns:vm="http://www.mulesoft.org/schema/mule/vm" 
    xmlns:java="http://www.mulesoft.org/schema/mule/java" 
    xmlns="http://www.mulesoft.org/schema/mule/core" 
    xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
        http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd
        http://www.mulesoft.org/schema/mule/java http://www.mulesoft.org/schema/mule/java/current/mule-java.xsd
http://www.mulesoft.org/schema/mule/sockets http://www.mulesoft.org/schema/mule/sockets/current/mule-sockets.xsd">
    
    <vm:config name="DefaultVmConnector" doc:name="VM Config" doc:id="786119a5-8fd8-40b8-a145-bfbf5df6a505" >
        <vm:queues >
            <vm:queue queueName="ReportsEndpoint" />
            <vm:queue queueName="PublishEndpoint" />
        </vm:queues>
    </vm:config>
    <sockets:listener-config name="UDP_Config" doc:name="Sockets Listener config" doc:id="b2ec3926-6a0b-4612-97e8-fe9f4011fbad" >
        <sockets:udp-listener-connection host="192.168.1.203" port="12121" />
    </sockets:listener-config>
    <flow name="TestPublishFlow" doc:id="05a95422-080b-491e-9a4d-efc82f10b399" >
        <sockets:listener doc:name="Listener" doc:id="5be9224a-be1b-4779-a137-121376cd890d" config-ref="UDP_Config" outputMimeType="text/json"/>
        <java:invoke-static doc:name="Data Wrapper" doc:id="9fa629cc-0520-4c00-beae-15f849568d3b" class="com.test.DataWrapper" method="wrapData(java.lang.Object)" outputMimeType="application/java">
            <java:args ><![CDATA[#[{payload : payload}]]]></java:args>
        </java:invoke-static>
        <vm:publish doc:name="Publish to VM" doc:id="b6497fb1-3a57-468c-bcb6-089372407787" config-ref="DefaultVmConnector" sendCorrelationId="NEVER" queueName="PublishEndpoint"/>
    </flow>
    <flow name="publisher-flow" doc:id="4c765b98-68df-4af2-a517-73bddfe8cc25" >
        <vm:listener doc:name="Publish Endpoint" doc:id="a8589268-f5ea-4ebe-a11f-760ff32c2e1f" config-ref="DefaultVmConnector" numberOfConsumers="2" queueName="PublishEndpoint"/>
        <java:invoke-static doc:name="Some Business Logic" doc:id="ee53fe99-f459-4581-892e-00e09a8a10e2" class="com.test.MyTransformers" outputMimeType="application/java" method="addProperties(com.test.DataWrapper,java.util.Map)">
            <java:args ><![CDATA[#[{ wrapper:payload, properties:{ ClientId:p('source.clientId'), Source:p('data.source'), Metadata:p('data.metadata'), MimeType:p('data.mimetype')}}]]]></java:args>
        </java:invoke-static>
        <choice doc:name="Choice" doc:id="7976bc82-67d4-4534-b47b-80ed7e287f07" >
            <when expression="#[Java::isInstanceOf(payload.payload, 'com.test.DataWrapper')]">
                <java:invoke-static doc:name="Do Something" doc:id="528cdafa-9be1-4433-aaf5-0e0352078757" outputMimeType="application/java" class="com.test.MyTransformers" method="doSomethingWithPayload(com.test.DataWrapper,java.util.Map)">
                    <java:args ><![CDATA[#[{ wrapper : payload, prettyprint : p('xml.prettyprint'), validate : p('xml.validate') }]]]></java:args>
                </java:invoke-static>
            </when>
            <otherwise >
                <java:invoke-static doc:name="Do Something Else" doc:id="6d63d9e7-12be-4d43-9990-02b897ec0cee" class="com.test.MyTransformers" method="doSomethingElse(com.test.DataWrapper)" outputMimeType="application/java">
                    <java:args ><![CDATA[#[{ wrapper : payload }]]]></java:args>
                </java:invoke-static>
            </otherwise>
        </choice>
        <logger level="INFO" doc:name="Placeholder" doc:id="800d22cd-55a5-4ee9-a280-497d2f276a63" message="#[payload]"/>
    </flow>
</mule>

I need to keep the original message and the processed message to store for later use.

When payload and originalPayload are set to a String object in DataWrapper and the mule message goes through a VM transport, those objects become org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamProvider.

Before sending to VM publisher the fields payload and originalpayload in DataWrapper are:

{ "count" : 0, "comment" : "This is a test"}

and after it is:

org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamProvider@162b291f

I'm also getting a serialization exception when going through the VM endpoint:

Caused by: java.io.NotSerializableException: org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamProvider

How can I prevent that from happening without changing the type of payload and originalPayload from Object to String?

Mule 4 implements streaming in most components, unlike Mule 3 which implemented it only for some. The payload returned by the HTTP Listener is a stream implemented by the ManagedCursorStreamProvider. It was not added by the VM connector, it was the original payload. Most components in Mule 4 now how to use the stream implementation transparently, so usually a user doesn't need to care if their JSON payload is in a stream or in a Java string. The exception is when you do actual Java code you need to know the actual classes.

It is not a good practice and discouraged to add Mule specific APIs and classes to your custom Java code to make them 'stream aware'. Instead an easier solution is to let DataWeave do its transformation magic and convert to a Java object automatically, in this case a String.

Example:

<sockets:listener doc:name="Listener" config-ref="UDP_Config" outputMimeType="text/json"/> 
<ee:transform doc:name="Transform Message">
   <ee:message >
      <ee:set-payload ><![CDATA[%dw 2.0
output application/java
---
write(payload,"application/java")
]]>
      </ee:set-payload>
   </ee:message>
</ee:transform>
<java:invoke-static doc:name="Data Wrapper" class="com.test.DataWrapper" method="wrapData(java.lang.Object)" outputMimeType="application/java">
        <java:args ><![CDATA[#[{payload : payload}]]]></java:args>
</java:invoke-static>

The write() function writes a string from the payload that you can use in your wrapper class. This works for this particular scenario. You will need to change classes if you do some other permutations of inputs and Java code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM