Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
vadimklimov
Active Contributor

Disclaimer


Material in this blog post is provided for information and technical features demonstration purposes only. The described technique is only applicable for non-productive usage and under no circumstances shall it be considered for utilization in production-oriented scenarios in CPI tenants, as it might introduce excessive overcomplication to troubleshooting process and security risk associated with potential lack of retrospective traceability and auditability of executed integration flow logic. An area where usage of the technique might be considered, is various proof of concepts that involve development of CPI iFlows.

 

Intro


When it comes to production-oriented development in CPI, the organization in general and the developer in particular shall be inclined to set up the appropriate development lifecycle infrastructure that aims enhancement of developer efficiency and development quality, as well as automation of recurrent steps that form parts of development, testing and deployment processes. There are nice blog posts out there written by engswee.yeoh and addressing developer productivity and aspects of unit testing of Groovy scripts. An intention is to develop representative and meaningful tests alongside the actual script, so that tests will cover critical (if not all) areas of the script functionality. Consequently, such unit tests can be re-executed locally on the developer machine and a script under test can be validated before being introduced to the iFlow in CPI.

In contrast to production-oriented development, it is commonly not feasible or not reasonable to prepare test specifications for the developed proof of concept iFlow and scripts within it. Development happens at a very high pace and consists of many amendments and changes that are not necessarily unit tested before deployment to the CPI tenant. It is a common case when the iFlow undergoes many iterative changes, each requiring the iFlow to be saved, deployed and started before it can be tested end to end – these steps are repeated and take time. One of areas that often requires several iterations of adjustments, is script steps within the iFlow. In between Groovy and JavaScript scripting languages that are supported by CPI, Groovy is way more popular choice among CPI developers – hence, in this blog post, we will focus on Groovy scripts, and terms "Groovy script" and "script" will be used interchangeably.

In this blog post, I would like to illustrate a technique that, when applied under certain circumstances, can be utilized to reduce a number of redeployments of the iFlow when working with scripts. The technique is not supposed to be used in production-oriented development, but might be useful when working on proof of concepts.

 

Overview


Fundamental idea of the technique is to replace static script with a generic wrapper that can execute a script logic that is dynamically submitted to it during execution of the iFlow. Since there are no changes to the iFlow at design time (assuming we only change script logic and no other steps of the iFlow), there is no need to save and re-deploy such an iFlow every time we need to introduce a change to a script.

Underlying concept is based on scripting nature of Groovy, where a currently executed script can instruct Groovy scripting engine to load an arbitrary submitted script code from a given location, parse and run it. Location of such dynamically called script code can vary – it can be a local script file, or externally located script, or a code snippet submitted to the script by other means. We can utilize this concept and pass code snippet together with (as a part of) the call to the iFlow – as a result, a message sent to the iFlow will not only contain data that needs to processed by the iFlow, but it will also include information on how data shall be processed.

This concept has much wider application and is effectively used in local environments when testing Groovy scripts. Now it is time to illustrate how it can be brought from the ground to the cloud and how it can be used in context of CPI development. Here I’m going to cover in details the particular case and implementation example that can be further adapted as per specific needs.

 

Implementation


In the demo, code snippet is going to be submitted in a request message body:



Obviously, this is not the only way to deliver code snippet to the wrapper script. For HTTP requests, code snippet can be passed as a part of the message body (in case the message body shall also include application data), or in a custom header (preliminarily code snippet shall be encoded – for example, using Base64 – as well as overall header value length shall be considered), or retrieved from some other location accessible from the Groovy script step within the iFlow.

 

In sake of simplicity, the iFlow consists of only one script step, which is a Groovy step implementing a generic wrapper and expecting dynamically submitted script code that is to be passed to it. No other steps are added to the iFlow deliberately, so that we can see pure effect of the script on the entire outcome of the iFlow execution at a later stage.



 

The entire logic of the wrapper script is based on usage of GroovyShell that allows invocation of a Groovy script from another Groovy script. There are few other alternatives that can be used to implement dynamic loading of Groovy scripts and that are not covered in this blog post – those are based on usage of GroovyScriptEngine and GroovyClassLoader. A reason why they are not used here is because GroovyScriptEngine has advanced support for dependent scripts, and GroovyClassLoader has advanced support for complex hierarchy of classloaders and additional capabilities to support loading of Groovy classes – when dealing with many Groovy script steps in CPI, both features are not somewhat commonly utilized, but it is rational to be aware of these alternatives in case GroovyShell will not be fit for purpose in certain circumstances.

Below is code snippet of the wrapper script:
import com.sap.gateway.ip.core.customdev.util.Message
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer

Message processData(Message message) {

def body = message.getBody(java.io.Reader)

def script = new StringBuilder()
def config = new CompilerConfiguration()
def customizer = new ImportCustomizer()
def binding = new Binding()

binding.message = message

body.eachLine { line ->
line = line.trim()
if (line.startsWith('import ')) {
def importClassName = line.replaceAll(';', '').minus('import ')
customizer.addImport(importClassName)
} else if (!line.empty) {
script << "${line}\n"
}
}

config.addCompilationCustomizers(customizer)

def shell = new GroovyShell(binding, config)
shell.evaluate(script.toString())

message = binding.message

return message

}

 

Let’s have a walkthrough its most critical and essential parts. Considering the following part of the blog post will contain references to specific code lines, let me provide a screenshot of the code snippet above from the code editor:



 

Code line 7. Given we expect called script’s code snippet to arrive in the message body, we firstly need to retrieve the body from message instance.

Code lines 9-12. We will need several objects that contain code of the called script to be executed, configuration for Groovy Shell and binding between the wrapper script and the called script in particular:

  • Builder (script) is used to collect code of the called script. In the demo, code snippet is going to be small and will not occupy much space, so we are going to use StringBuilder, but if there is possibility of submitted large blocks of code in code snippet, we shall take care of optimization of memory consumed by the wrapper script and avoid potential suboptimal memory consumption – in such cases, we might want to switch to usage of Reader and Writer objects to manipulate submitted code snippet.

  • Compiler configuration (config) is used to tweak and customize behaviour of Groovy compiler – for example, by setting specific supported compiler properties and flags, specifying used imports. In this demo, we will use compiler configuration to add imports that are required by the called script and that are passed as a part of the message body alongside code snippet. The wrapper script contains a minimal number of imports required for its own execution, but any specific imports that are required for correct execution of the called script, are not known to the wrapper script – hence, we can dynamically specify them when sending the request.

  • Import customizer (customizer) is used to hold information about imports that are required by the called script.

  • Binding (binding) is used to bind variables between the wrapper script and the called script and allow exchange of data between them. For example, in the demo, we will use binding to pass Message object from the wrapper script to the caller script (to allow manipulation with this object within the caller script), and then get it back from the called script and return as an outcome of the wrapper script execution.


Code line 14. Here we set up binding between variable message of the wrapper script with the similarly named variable that will be accessible within the called script. Variables don’t necessarily need to be named similarly and can carry different names, but usage of same names here reduces confusion.

Code lines 16-24. We go through code snippet retrieved from the message body line by line and split them into two categories:

  • Lines that contain import statements are retrieved from code snippet and added to import customizer to consolidate script imports.

  • Lines that contain code of the called script are retrieved from code snippet and passed to builder to consolidate script code).


Import customizer provides various methods to add imports – for example, it is possible to add static imports, wildcard imports. In the demo, we will not need static imports, and wildcard imports are generally considered as an anti-pattern as in many cases, they can be replaced with specific class imports to improve readability and avoid potential import ambiguities and conflicts, so we will not use them here. Given that Groovy doesn’t enforce usage of semicolons at the end of statements, and different developers have different styles – some prefer usage of semicolons to emphasize end of statement, whereas others prefer more compact code and avoid usage of redundant characters – we’d rather let the wrapper script support both styles when retrieving imports.

As it can be evidenced, we don’t run any validations against obtained code snippet – for example, we don’t validate imported classes against rules for fully qualified names of classes (which can be done using regular expression) and we don’t run syntax checks for parsed code of the called script before executing it. These checks are omitted in the demo and we assume correctness of input submitted in the message body – if this assumption turns to be wrong, the wrapper script is going to fail, resulting in subsequent failure of message processing by the iFlow.

The iFlow also doesn’t check if the message body exists at all – it is assumed that code snippet is provided in the body of the submitted request, otherwise the iFlow shall not be called. Should this be a production-oriented scenario, such condition should have been considered and handled appropriately – for example, by checking message body size before making use of it within the script, or by checking a value of the header Content-Length of the request message against equality to zero (which would be a reflection of a message with the empty body).

Code line 26. After we have collected all imports into import customizer, we can add it to compiler configuration.

Code lines 28-29. Groovy Shell is created and configured by submitting binding and compiler configuration to it, followed by execution of the called script with the help of the shell. In simple cases, it is possible to use Groovy Shell without binding and/or compiler configurations, but in this demo, we need them both for a good reason.

Note that here we use method GroovyShell.evaluate() – that is a convenient way of running a script once and combining steps of parsing script and executing it. An alternative to this would be usage of combination of methods GroovyShell.parse() (to parse the called script) and GroovyShell.run() (to run the called script) – this is useful and more optimal when the script has to be parsed once and executed several times. As this is not the case here in the demo, we gravitate towards a simpler form.

Code lines 31-33. Message variable is read from the one that has been bound to the called script, back in the wrapper script and finally returned from the wrapper script to the executed route.

Alternatively, we could reduce number of code lines in the wrapper script by omitting explicit assignment of the bound variable back to the message variable and returning content of the bound variable in the wrapper script straightaway – here, they are separated and split into two steps in sake of better readability and illustration.

 

Test


Let’s now put the iFlow under test. Given demo implementation of the wrapper script expects the script code to arrive in the message body, we are going to compose a corresponding request and send it to the endpoint of the iFlow using Postman. For illustrative purposes, code snippet implements logic to compose a JSON object with elements that contain both static content (fixed value) and dynamic content (message ID and current timestamp) and pass it to a body of the response message, and given object content is marshalled to JSON formatted output, we also want to set a specific content type header that indicates the response message body is a JSON document – Content-Type = application/json. Full code snippet used in the request mesage is provided below and highlighted with red colour on the following screenshot.
import groovy.json.JsonOutput;


def body = JsonOutput.toJson(
messageId: "${message.headers['SAP_MessageProcessingLogID']}",
timestamp: "${new Date().format('yyyy-MM-dd HH:mm:ss.SSS')}",
text: "For demonstration purposes only"
);

message.setBody(body);
message.setHeader('Content-Type', 'application/json');



Let’s pay attention to a response that has been received (highlighted with blue colour on the screenshot above) – this is exactly what we would expect a response message to look like, if the code that has been submitted in the request, would have been placed in the Groovy step script in the iFlow! We can evidence that the wrapper script successfully identified import requirement for JsonOutput and executed the script that was passed to it in the request.

CPI Message Monitor provides additional details and evidences that the message triggered execution of the wrapper script and demonstrates message body before and after a script step execution – all together confirming once again that execution of dynamically submitted code snippet was carried and fulfilled as expected:







 

Alternative implementation by Eng Swee Yeoh


After publication of this blog post, engswee.yeoh made a proposal how the wrapper script can be implemented in an alternative way, making source code of the script significantly more compact and straightforward. The idea behind the version provided by Eng Swee, is to pass the entire Groovy script to the iFlow and consequently to the wrapper script, and not to consider input as a combination of imports section and code snippet that have to be handled in two different ways, as well as making usage of bindings redundant. Let me express gratitude to Eng Swee for exploring, testing and suggesting a more compact alternative, and provide the version that he shared, in here.

 

Below is code snippet of the wrapper script that has to be embedded into a Groovy script step of the iFlow:
import com.sap.gateway.ip.core.customdev.util.Message

Message processData(Message message) {

def reader = message.getBody(Reader)
Script script = new GroovyShell().parse(reader)

message = script.processData(message)
return message
}

 

An amended sample request that was used in the demo, has to be slightly adjusted and shall result into the following message body to conform to the modified wrapper script:
import com.sap.gateway.ip.core.customdev.util.Message
import groovy.json.JsonOutput

Message processData(Message message) {

def body = JsonOutput.toJson(
messageId: "${message.headers['SAP_MessageProcessingLogID']}",
timestamp: "${new Date().format('yyyy-MM-dd HH:mm:ss.SSS')}",
text: "For demonstration purposes only"
)

message.setBody(body)
message.setHeader('Content-Type', 'application/json')

return message
}

 

Outro


This was indeed a simple example, but I hope it illustrated the described concept – without changing the wrapper script and keeping it generic, we could flexibly compose the entire actually required custom script logic outside of CPI (here, in Postman) and let CPI execute it at runtime without necessity of making a single change in the iFlow implementation after it has been deployed. We can now send various requests (with different scripts in the message body) to this iFlow, and each time the script in the request will get changed, the same iFlow will behave differently.

 

This kind of flexibility has certain payoff – transparency and traceability. I would like to conclude this blog post by placing emphasis on what has been mentioned earlier: this concept is useful when rapidly developing and tweaking proof of concepts, but it is an inappropriate approach for production-oriented scenarios. In cases when script code is not settled as a part of the iFlow definition during design time, but is submitted and executed at runtime, it is much more complex to ensure consistency of executed flow logic. Not only it exposes a corresponding integration scenario to possible inconsistency and lack of predictability of flow logic, but raises a security concern – if flow logic is impacted from outside of the deployed iFlow, we set strong dependency on security of externally and dynamically submitted script code and enter discussion of analysis and assurance of the code (being it static or dynamic) that is executed at runtime, to be not malicious and to be free of security vulnerabilities.
20 Comments
former_member198979
Participant
0 Kudos
Thanks vadim.klimov for this innvoative sights and its one of the very interesting use case for any integration person in furture to bring automation inside the middleware which eventaully leads to citizen integrator.

Cheers!

Chandan
vadimklimov
Active Contributor
0 Kudos
You are welcome, Chandan. Glad if this technique turns to be helpful - bearing in mind thoughtful assessment and its careful usage in scenarios.
former_member194481
Participant
0 Kudos
Hi Vadmin,

Excellent blog, once again.  Thanks for this.

 

Thanks and Regards,

Vijay
vadimklimov
Active Contributor
0 Kudos
Thank you, Vijay.
r_herrmann
Active Contributor
0 Kudos
Hi Vadim,

I use this "workaround" for a while and it works quite well as long as I use only import-statements which point to classes that are part of CPI. As soon as I add imports to classes which are part of an external Jar (uploaded via Resource tab to the IFlow) I get errors. Did you manage to run scripts which import from external Jars?

Regards,
Raffael
vadimklimov
Active Contributor
0 Kudos
Hi Raffael,

That's a very valid point. Yes, I have come across the same behaviour: classes that are contained in external dependencies that are added as archives to the iFlow, get recognized by the script when it is executed in a natural way by the iFlow, but are not recognized when using a generic way of loading script and running it via GroovyShell.

My guess here would be based on classloader that is used when an OSGi bundle is started by application runtime (and iFlow is assembled into and deployed to a runtime as an OSGi bundle) being different from the one used by GroovyShell. When the iFlow's OSGi bundle gets started, its classloader loads classes from added archives that are present in the bundle, but GroovyShell that is called generically from the script step and is agnostic to classloader of the iFlow's OSGi bundle, doesn't happen to recognize those classes. Good news are, we still have those added archives available on a file system of a runtime node (and we can get access to them as they will be found in bundles cache - for example, at /usr/sap/ljs/data/cache/{bundle ID of iFlow}/version{version}/bundle.jar-embedded/lib), so technically, it shall be possible to manually trigger loading of classes from those archives' JAR files based on their location on a file system before execution of code snippet of the submitted script. But I shall admit, I haven't yet tried to apply it to scripts executed via GroovyShell in CPI. Fair shout though - room for exploration and experiments!

Regards,

Vadim
r_herrmann
Active Contributor
0 Kudos
Hi Vadim,

please forget my last comment. It seems like the JAR I was testing was faulty. I now tested again with another library (ZXing) which isn't part of the SAP CPI standard libs. I added it to the resource tab. Then I wrote a small script to generate a QR code.

The script works - regardless if its executed directly in an Script-element or indirectly via GroovyShell. Thus I assume the GroovyShell shares the same classloader or atleast gets to know the JARs from the IFlows lib folder.

For sake of completeness - I executed the scripts via Eng Swee's method.

Regards,
Raffael
vinaymittal
Contributor
Just an enhancement to This concept: You can also supply your own message mody on which you wish to execute that script

 

Request Payload like below everything before ##*****************## is message body and after it is code

 

{
"root":
{
"first_name": "Vinay",
"last_name":"Mittal"
}
}
##****************************************************##
import groovy.json.JsonSlurper;
import groovy.json.JsonBuilder;
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
Message processData(Message message)
{

def body = message.getBody();


JsonSlurper slurper = new JsonSlurper();

Map parsedJson = slurper.parseText(body)
def builder = new JsonBuilder(parsedJson)

String name = parsedJson.root.get("first_name") + " "+ parsedJson.root.get("last_name")

message.setBody(name)
message.setHeader('Content-Type', 'application/json')

return message
}

 

 

 

 

And the groovy in CPI IFLOW will be like this

import com.sap.gateway.ip.core.customdev.util.Message

Message processData(Message message) {

String body = message.getBody(java.lang.String)
String body_code_separator = "##****************************************************##";
String code = body.substring(body.indexOf(body_code_separator)+body_code_separator.length(),body.length() );
def reader = new StringReader(code);
message.setBody(body.substring(0,body.indexOf(body_code_separator)));


Script script = new GroovyShell().parse(reader)
message = script.processData(message)
return message
}

 

 

Output is

 

vadimklimov
Active Contributor
0 Kudos
Vinay, thank you for your commend and suggestion. That's a very good point! This is indeed a valid way to pass both payload and script within the same message. As another alternative, we can also use multipart form post requests (multipart/form-data) - using this approach, we can submit script's source code, input message payload, and optionally additional headers and exchange properties (if we need to test them specifically) in separate form data elements contained in the same request message - and then handle them in the message within CPI and pass them to the submitted and executed tested script function.
jaith_eranga
Explorer
0 Kudos
Hi vadim.klimov , engswee.yeoh

 

Thanks much for sharing this vadim.klimov . Was able to levarage for testing and to make life easirer.

I have a question. Managed to found variable "messageLogFactory" is available via binding.

Is there a way to get variable "messageLogFactory" passed from wrapper script to called script, through which we could acess the MPL.

if we tries to do it now like below, getting the below error. Just adding to make it more clear.


Script passed via Postman



 

Thanks a lot.

Error get in CPI

vadimklimov
Active Contributor

Hello Jaith,

messageLogFactory is an example of so-called variable binding in Groovy - this is a type of variables that can be created or altered outside of the script, and then passed into the script. It is a handy technique in some use cases (for example, when we need to bind certain variables and make use of them in the script without a need to pass them as formal arguments in script's functions), but it is also dangerous, as variable bindings are not immediately and transparently observed, and this might lead to confusions and conflicts when using them in conjunction with more traditional global or local script variables, especially when variables' names (of variables containing in the binding and global/local variables) clash. As a result, although this technique is finely supported and has some areas of application, it shall be evaluated with great caution - there is a good reason (described above) to refrain from usage of variable bindings at scale. You can read more about this, for example, in the blog post written by markus.muenkel.

Generally speaking, when calling a script function, it is possible to pass variable binding information to it. Here, we have two scripts that are used in the scenario:

  • The first one is a generic wrapper that already has access to variable binding for messageLogFactory,
  • The second one is a called dynamically submitted script that doesn't yet have access to that variable (as well as to other variable bindings that might be accessible by the wrapper script).

To pass through a messageLogFactory variable binding to the called dynamic script (and make it possible to use it from within that script's functions), we can enhance the wrapper script in such a way that it passes binding information. It can be done in either of two ways;

  • Pass binding information at a time of creation fo a GroovyShell instance, before parsing the dynamically submitted script,
  • Pass binding information after the dynamically submitted script has been parsed and we have a Script object for it.

The main difference between these two is when you pass binding information and how reusable you would like to make it - for all scripts that are parsed by a particular GroovyShell instance, or just for a specific script. In this particular case described in the blog post, it doesn't matter much, as a GroovyShell instance that is created by a wrapper generic script function, is disposed (not reused).

Next, we can either be very specific about which particular variables we would like to pass to the called script using variable bindings technique (then we have more control over what is actually passed to the called script), or we can be more generic and pass through all variable bindings that have been passed to the wrapper script, further to the called script.

For example, if we want to pass through all available variable bindings (including messageLogFactory) to the called script, here is an enhanced sample of the wrapper generic script function (note the difference from the original one: binding information is passed when creating a GroovyShell instance here):

import com.sap.gateway.ip.core.customdev.util.Message

Message processData(Message message) {
Script script = new GroovyShell(binding).parse(message.getBody(Reader))
message = script.processData(message)
return message
}

Or an alternative - this time, when we take more control over what variable bindings are passed to the called script, and we explicitly pass only a messageLogFactory variable to the called script using variable binding for it:

import com.sap.gateway.ip.core.customdev.util.Message

Message processData(Message message) {
Binding customBinding = new Binding([
'messageLogFactory': messageLogFactory
])

Script script = new GroovyShell(customBinding).parse(message.getBody(Reader))
message = script.processData(message)
return message
}

 

Regards,

Vadim

jaith_eranga
Explorer
0 Kudos
Hello vadim.klimov

Nice ... 🙂  . It is working perfectly now. You are such a good teacher. Appriciate very much the comprehensive answer with all the background. It always encourages more and more to walk ahead. I wanted to make the environment of called script exactly as the place where it goes next and live once tested.

So, exactly what I wanted was pass through all bindings.

Thanks a lot.
Jaith Ambepitiya.
vadimklimov
Active Contributor
Glad it worked well for you, Jaith. Good luck to you with dynamic scripts execution - it is great you found a good application for this technique!

Regards,

Vadim
0 Kudos
Hi Vadim,

Nice Article, found this blog relevant to ask my question.

 

I have custom jar that is added to iflow as a resource. In the iflow, in one of the script component we are creating instance of custom java class and then setting custom java object to the message property. Further in iflow I am trying to retrieve the  message property to make use of the custom java object that was stored previously. Syntax wise everything looks good, no errors but at runtime it is failing due to below error,

Why is it trying to cast an object of the same type to it's type, and then failing to do so?

I see your comment on classloader related to below error, can you please help how we can resolve this error.

Your solution will help to make use of custom java objects in CPI to set/get from message property especially when you want to hold complex data structure where using of standard collection objects like Map and List is not sufficient.

 

Message processing failed.




Processing Time: 77 ms





Error Details








javax.script.ScriptException: java.lang.Exception: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.custom.test.EmployeeDetails1095C@5f4488f1' with class 'com.custom.test.EmployeeDetails1095C' to class 'com.custom.test.EmployeeDetails1095C'@ line 16 in TestReadCustomJavaObjects.groovy, cause: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.custom.test.EmployeeDetails1095C@5f4488f1' with class 'com.custom.test.EmployeeDetails1095C' to class 'com.custom.test.EmployeeDetails1095C'








 

Thanks

-Anand R Hungund
vadimklimov
Active Contributor
0 Kudos

Hello Anand,

The requirement that you described makes perfect sense - and given that a property can hold any Object value, we are not constrained and limited to any specific built-in types, we can store arbitrary data of literally any type. I used to use this approach to exchange different Java objects between Groovy script steps with the help of message properties. And I tried it right now to create a property and pass some arbitrary Java object to its value in one Groovy script function, and then access it and make use of it in the other Groovy script function that is called from the subsequent step in the iFlow - and this worked well for me. I feel that it will be needed to see the code snippet to assess how the property value is accessed and casted in the script function that reads it, before any more specific recommended can be given.

Regards,

Vadim

0 Kudos
Thanks Vadim for the reply.

Below are the scripts I have used in the iflow and i am getting error from 3rd script at runtime.

TestInitializeHashMaps.groovy
-------------------------------------------
import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.sdc.mck.cobra.EmployeeDetails1095C

def Message processData(Message message) {

def messageLog = messageLogFactory.getMessageLog(message)
def body = message.getBody(java.lang.String)
propertyMap = message.getProperties();

HashMap<String,EmployeeDetails1095C> codeMatrixAsMap = new HashMap<String,EmployeeDetails1095C>();
message.setProperty("CODE_MATRIX", codeMatrixAsMap);

return message
}

TestStoreCustomJavaObjects.groovy
----------------------------------------------------------
import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.sdc.mck.cobra.EmployeeDetails1095C

def Message processData(Message message) {

def messageLog = messageLogFactory.getMessageLog(message)
def body = message.getBody(java.lang.String)
propertyMap = message.getProperties();

HashMap<String,EmployeeDetails1095C> codeMatrixAsMap = propertyMap.get("CODE_MATRIX")

int empId = 0
EmployeeDetails1095C empDetails = null

int dummyEmpId = 2387
for(int i=0; i<6 ; i++){

empId = dummyEmpId + i

empDetails = new EmployeeDetails1095C(""+empId, ""+empId)
codeMatrixAsMap.put(""+empId, empDetails)
}

return message
}

TestReadCustomJavaObjects.groovy
---------------------------------------------------------
import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.sdc.mck.cobra.EmployeeDetails1095C

def Message processData(Message message) {

def messageLog = messageLogFactory.getMessageLog(message)
def body = message.getBody(java.lang.String)
propertyMap = message.getProperties();

HashMap<String,EmployeeDetails1095C> codeMatrixAsMap = propertyMap.get("CODE_MATRIX")

EmployeeDetails1095C empDetails = null
StringBuffer strBuffr = new StringBuffer()

codeMatrixAsMap.each{
empDetails = it.value
strBuffr.append(it.key + "-->")
strBuffr.append(empDetails.getPersonIdExternal() + "-")
strBuffr.append(empDetails.getWorkerId() + "-")
strBuffr.append(empDetails.getMonthlyCodeMatrix().toString() + "-")

empDetails.getMonthlyCodeMatrix().each{
strBuffr.append(empDetails.getMonthFullName() + "*")
}

strBuffr.append("\n")
}

messageLog.addAttachmentAsString("Read Custom Java Object", strBuffr.toString(), "text/xml");

return message
}

 
0 Kudos
Below is the Error,

javax.script.ScriptException: java.lang.Exception: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.sap.sdc.mck.cobra.EmployeeDetails1095C@422d62b9' with class 'com.sap.sdc.mck.cobra.EmployeeDetails1095C' to class 'com.sap.sdc.mck.cobra.EmployeeDetails1095C'@ line 16 in TestReadCustomJavaObjects.groovy, cause: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.sap.sdc.mck.cobra.EmployeeDetails1095C@422d62b9' with class 'com.sap.sdc.mck.cobra.EmployeeDetails1095C' to class 'com.sap.sdc.mck.cobra.EmployeeDetails1095C'

 

Below is how the property looks like from trace,







CODE_MATRIX {2389=com.sap.sdc.mck.cobra.EmployeeDetails1095C@422d62b9, 2388=com.sap.sdc.mck.cobra.EmployeeDetails1095C@61c2e11e, 2387=com.sap.sdc.mck.cobra.EmployeeDetails1095C@725882f8, 2392=com.sap.sdc.mck.cobra.EmployeeDetails1095C@4fd18742, 2391=com.sap.sdc.mck.cobra.EmployeeDetails1095C@66401cab, 2390=com.sap.sdc.mck.cobra.EmployeeDetails1095C@10fcdb6c}
vadimklimov
Active Contributor
0 Kudos
I have seen that you had already created a question here for the same problem. Hence, I suggest the discussion is continued there - Q&A is a more appropriate area for this kind of discussions rather than comments to a blog post (as the problem description doesn't make me think it is related to the subject of a blog post).
0 Kudos
OK, yes make sense. I have copied above code to the question also, you can respond to that.
Dmitri
Discoverer
0 Kudos
Thanks Vadim!

I am looking for a generic wrapper groovy script, that will receive metadata/payload from SOAP request and move it forward, turn on/off, collect cash data. Thanks in advance
Labels in this area