Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
cancel
Showing results for 
Search instead for 
Did you mean: 


If one thinks about Javascript and processing of data, there is no other chance than to think as well about JSON. So it is said...

But as in Asterix, there exists a small "village" inside of B1i Javascript that is different.
It is true that - as being typical for B1i in other places - coping with XML inside Javascript is absolutely no fun. There are natural fits in the world - XML and XSLT is such one, as well JSON and Javascript. XML and Javascript is none.

But it might happen that you need data within the scope of Javascript that is in another representation than JSON, and possibly even in another representation than XML as well. These special purpose cases might be relatively rare, but if they come, there needs to be a way to cope with them.

This blog-post is in place in order to set the way you cope with data inside Javascript upside-down if you need to do so. As the subject is about to cope with relatively complex data (being distinct from plain and single strings), the application entirely lies in the area of B1i message-level Javascript (thus, the alternative Javascript transformation rules in the Xform-Atoms or the processing within the ECMA-adapter), but not within the scope of field-level Javascript (as applied within XSL-stylesheets in order to enrich the inbuilt XPath-functions or the direct use of Java).

For demonstrating the purpose, the discussed Javascript example below copes with simple programmatic picture processing (of course thereby using Java to do so). There is no special reason for this kind of use-case, it just only was possible to do it that way and the means were at hands within the shipping scope of B1i (means, the usage of simple picture processing functionality as being bundled within the JDK). Don't ask me about concrete details about the shown picture processing, i just might be ahead of you for half a day about that. As always for such situations, stackoverflow.com is our main friend.

The concretely depicted example already shows up one general trait that might be pretty common to whatever of such use-cases might come up: The data to be coped with very likely may not be process-able with plain vanilla Javascript; there needs to be help onboarded by using Java (as being typically for our B1i case). As a conclusion, Javascript easily may transport and handover such data, but most often not interpret / process it.

Thus, it makes sense to get familiar with Nashorn and the inbuilt interoperability with Java - that's nothing related to B1i. The supplied example needs to make a considerable use of that. On the B1i side, all of the enhancements entirely have been made within the inbuilt scriptIO Javascript object. At the time of writing this post, all of these enhancements still are brand new and not yet on site - at the end of the day, make sure to have a BizProcessor with at least V 0.44.1 under the hood in order to be able to run the example and to make usage of the supplied features.

Hands On


Enough of the introducing bla bla - let's get concrete! Here is the whole picker.js Javascript source. Using the /dev environment, create a BizFlow with a single Xform-Atom in it, having the supplied Javascript file as the transformation rule. For a full-fidelity output, make sure the BizFlow outputs the payload-type of jpg. You best embed it to a scenario-step being triggered by the HTTP-Adapter, in turn being able to use a Web-Browser for testing. For convenience, you possibly want to skip the necessity of an authentication.
If - on the other hand - you like it B1iP Bare Metal (thus, you're a kind of 'B1iP Iron Man'), find a small embedding BizFlow named pic.bfd that invokes the Javascript. Put them into the whatever named same dataset and group for proper execution. Invoke it best from Web-Browser side the same way as plain BizFlows in general are invoked (so in a similar way the B1i Admin-UI's do).
If you're in turn able to successfully invoke / execute the demo coding, you see that the data-handling inside Javascript has turned upside-down - but now, let's get to the most important aspect of the whole thing, the question

How It Works


We entirely concentrate our efforts of understanding to the Javascript side here - on the B1i side, the processing is pretty simple - just only invoke an Xform-Atom, possibly pass a parameter, in turn output the very data using the suitable payload-type - that's it!
That would be more or less the same way, no matter from where the script is called.
But now coming to the script itself:
var url = scriptIO.getProperty ("url");
var factor = 1;

// Get the necessary Java classes at hand
var ByteArrayInputStream = Java.type ("java.io.ByteArrayInputStream");
var ByteArrayOutputStream = Java.type ("java.io.ByteArrayOutputStream");
var ImageIO = Java.type ("javax.imageio.ImageIO");
var Math = Java.type ("java.lang.Math");
var URL = Java.type ("java.net.URL");
var AffineTransform = Java.type ("java.awt.geom.AffineTransform");
var BufferedImage = Java.type ("java.awt.image.BufferedImage");
//var Graphics2D = Java.type ("java.awt.Graphics2D");

First, there will be retrieved a potential calling property that possibly had been passed (or not) on invocation of the script. Dependent on that, the Javascript variable url contains a string (further on usable in Javascript) or null.
This variable might contain an URL for a picture to be loaded from the Internet as the input for the processing. All these Java.type stuffed variable definitions are the way how Java classes can be brought in for further access within Nashorn. These variables in turn will hold the class definitions of the particular classes, but not yet workable instances of them (if applicable at all - there do exist many classes that only "live" from static methods not needing any instance of the class).
var bim;

if (url == null)
{
var props = new Array ();
props [0] = "bpm.pltype";
props [1] = "jpg";


// The props-array finally will fix the payload-type to be used after
// 1. getting it right by the supplied file-name extension
// 2. messing it up by the explicitly supplied nonsense payload-type (that supersedes)
// 3. finally getting it right by evaluation of all of the passed conversion properties (that in turn supersedes)
var pic = scriptIO.readConvertedDocument ("com.sap.b1ip.system.cc",
"bin",
"Bicycles.jpg",
"SEC",
"nonsense-payloadtype",
props);

// load the picture for processing...
var bais = new ByteArrayInputStream (pic);

// We'll get a BufferedImage
bim = ImageIO.read (bais);

factor = 4;
}

The next part of the code now copes with retrieving a picture from the BizStore - if no url was supplied to the script. That's not that easy, as the default B1iP content does not have many meaningful pictures brought along. The one that is possible unfortunately is a bit small - that's why we cope with an enlargement factor.
But let's concentrate to the loading of the picture from the BizStore - here we find the first important new feature to use: The method scriptIO.readConvertedDocument (...).
This method has come in as a new one and complements the yet existing other method scriptIO.readJsonDocument (...). This alternative method allows to retrieve a document out of the BizStore doing the suggested implicit or explicitly stated payload-type conversion, as if it e.g. would happen when directly showing up this document via a Web-Browser using the dummy B1i Admin-UI HTTP-realm. For both of the methods, the parameters 1 - 4 are the same ones: Dataset, group and the name of the document, in turn the BizStoreAccessor retrieval option to choose. In this case, SEC is the choice, what means a cached selection, setting no whatsoever Coord-Service read-locks to the retrieved document. That makes retrieval fast and well co-existing to parallel accesses to this document. What follows is specific to this new method: The hint for the intended destination payload-type to convert the document to (note though that the physically stored XML-representation of the document must match to the intended payload-type to be handed out - this rule holds as always).
The pretty awkward way how this is done here solely is for demonstration purposes: As the default, the extension of the stored document will be taken as the hint for a demanded payload-type conversion. That's also the way how most Web-Servers in the world do work concerning this aspect. If the explicit payload-type to be passed is not null or an empty string, it will be taken instead; in our case, this is stated nonsense-payloadtype. Of course, this setting in practice is nonsense (as stated...). That's why only a very last attempt finally can fix it - the passing of general conversion properties to the payload-type converter (as in all other situations where payload-type conversion is in place). These conversion properties as well may comprise the everywhere-valid specific property named bpm.pltype that states the demanded payload-type to convert to (at the end of the day, all these other previous options internally finally result to the application / crafting of this property). So the last parameter of the call passes these collected properties. In order to do that, supply a native Javascript Array that shall contain everything you need to pass. The format is by supplying as subsequent elements first the name and then the value of the property. That way, an array with alternating name - value element pairs arises. This schema looks a bit cumbersome to serve, but it is the least cumbersome way to do so and only needing Javascript means and, as the most important aspect, not bringing up clashes between B1i property names and Javascript.
One might be tempted to simply pass a Javascript object instead that internally just only contains properties named like the B1i properties to address whereas their values in turn are the values to serve: But this approach falls short because Javascript property- or more general, variable- names must not contain dots, as these serve as the delimiters of property hierarchies instead. On the other hand, B1i property names may (and will) contain dots within as a kind of lightweight namespace application. That's why B1i property names only can be represented as data on the Javascript side.

Finally, the method returns the retrieved document as a native Java byte [] array (or null if not found) - a data-type not consumable by plain vanilla Javascript! But that's also not the intent for such cases; using the necessary Java-classes in turn makes a process-able image out of it.
else
{
if (url == "")
url = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Yakovlev_Yak-_130_%28modify%29.jpg/1024px-Yakovlev_Yak-_130_%28modify%29.jpg";

bim = ImageIO.read (new URL (url));
}

The alternative processing path tries to load the picture from an URL if it was supplied with an non-empty content. Otherwise, a default URL is taken. The physical URL retrieval of the picture is done by the ImageIO Java class. Note that the names of the Javascript variables holding the particular class definitions are named the same way as the respective Java classes for clarity reasons - they could have been named entirely different, at the same time making the very handling much harder to understand.
// prepare to blow it up...
var mid = new BufferedImage (bim.getWidth () * factor,
bim.getHeight () * factor,
bim.getType ());

// We'll get a Graphics2D
var g2d = mid.createGraphics ();
g2d.drawImage (bim, 0, 0, mid.getWidth (), mid.getHeight (), null);
g2d.dispose ();

// prepare to rotate...
var affineTransform = AffineTransform.getRotateInstance (Math.toRadians (180.0),
mid.getWidth () / 2,
mid.getHeight() / 2);
var res = new BufferedImage (mid.getWidth (), mid.getHeight (), mid.getType ());
var g = res.getGraphics ();
g.setTransform (affineTransform);
g.drawImage (mid, 0, 0, null);
g.dispose ();

var baos = new ByteArrayOutputStream ();
ImageIO.write (res, "jpg", baos);

var poc = baos.toByteArray ();

The following lines above entirely do the Java based picture processing, whereas the only small fractions of Javascript remaining are the variable definitions.
As promised, they contain the essence that turns the way of your processing of data upside-down...
// ... and finally set it as the output of the script
scriptIO.setConvertedOutMessage (poc, "jpg", null);

Finally, the computed result picture (being in the Java byte [] representation) gets passed back to B1i by accepting it as the binary native representation of a jpg picture. This is done by the new method scriptIO.setConvertedOutMessage (...) that complements the yet existing scriptIO.setJsonOutMessage (...). Here, the information about the supplied source payload-type is a must-do, as otherwise, there would be no way to get this hint. On B1i side, the applied payload-type conversion brings the picture to its XML-representation for the further processing. As this output is the only one of the Xform-Atom, it can directly be passed as the output of the very embedding BizFlow, thereby physically emitting a native jpg picture. In this case, there is no need to pass any conversion properties; that is why the last parameter is stated with null. (i did not want to repeat the unnecessary pltype odyssey as when reading of the picture).

In our example, we've seen these complementary method-pairs on reading a document from the BizStore and on passing it as the outbound message. The complementary methods are for coping with JSON as the one of them and dealing with any kind of payload-type as the other new one. Beside of the mentioned pairs, there as well exist pairs for reading the inbound message for the script and for writing a document to the BizStore (see also the scriptIO documentation).

With the advent of these new capabilities, data-passing problems between B1i and Javascript should have become an issue of the past...
1 Comment