So, here it is, how to call WebServices from Jscript in LSO.
In my experience with scripting languages, they typically aren’t great when it comes to handling complex tasks such as processing XML files. Sure, they can but you put a lot of effort in to developing a solution and that solution ends up being difficult to maintain.
Before we dive in to this we need to talk a little bit about some nifty things about Web Services and M3 – there will be a rant, there will be shouting and screaming and inevitably there will be tears and reconciliation, it is a long journey of discovery to get web services working.
Lawson have some smart cookies working for them, infact, I’d even go as far as saying very very smart cookies :-). To bridge the M3 and S3 clients they have weaved some pretty chaotic magic and created something that is beautiful in … um … some way…Oh, lets approach this from another angle. I have person who I am training and I thought it would be a great idea to draw a diagram which illustrates how Smart Office talks to the servers – and it gets messy very quickly (just how many servers do we need to tap to log in and run a program in M3… :-O). However, through the tangled web of communications we have been given something very kewl.
- We can wrap those dastardly M3 APIs in a WebService
- We can wrap display programs in a WebService (honestly, how mind blowing is that?)
- We can wrap an SQL query in a WebService
You’ll notice that I underlined wrap in each instance – this is an important distinction. We are literally going to wrap a web-service around existing functionality. We create the web-services ourselves with a licenced tool from Lawson call Lawson Web Service Designer (LWS Designer) – a plugin for Eclipse. I especially like the whole concept of wrapping the ‘display’ or presentation layer interface so you get all of the business engine validation, something that I have found lacking when dealing with the APIs.
So LWS Designer allows us to wrap functionality and present as a web service. It also allows us to create test scripts so we can easily test our web service and ensure that it is behaving as we expect.
In this post, I am going to do the following, I am going to create a web service to retrieve the Free Cap Units for a product, from MMS001/F. We will then add a button to MMS001/B1 which will retrieve the Free Cap Units for a specific product.
I am going to assume that you have got LWS Designer installed and configured correctly – which can be a challenge in itself (though a little easier if you read the install document ;-)) and your servers are set up to run Web Services.
Fire up LWS Designer, right click on your repository and select “New web service”
We’re going to call this Web Service ItemFreeCaps2 and we want to use the wizard to help us create the web service, so make sure there is a tick in the “Start the new web service method wizard on finish”.
Click on Finish.
In this instance I want to wrap around a display program (MMS001/F). Lawson do recommend using the APIs where-ever possible, I expect that this is because screen formats can be changed which will cause failures in your web-services – something to be very wary of when you look at upgrading.
Select your configuration (I have an environment for Test and one for Prod – these typically get set up by Lawsons)
Enter a username and password for the environment.
Select a configuration – we are going to use the Standard MoveX Display Programs.
Select the Display Program we want to wrap (MMS001)
We’re going to call our method “FreeCaps”
Click on Finish and LWS Designer will go out and create the WebService.
Because there is an API, we get a warning telling us that we should use an API rather than a display program. We will ignore this. As noted previously, please make sure that you are thorough when you are planning and executing your testing when you look at doing upgrades. I suspect that display program web-services are prime candidates to break.
Display programs act just like the program would, this means that we need to set up our web-service to effectively run through the same keystroke sequences that the client would in order to get to the panel we want before we can extract out Free Cap Units.
Our sequence of events will be
Start at Panel A
Populate the Item Number (as defined by the input from the web service)
Set the Panel Sequence (to just F)
Set the Option (5 to display)
Press Enter (this will take us to panel F for our desired Item Number)
So, we need to create our Panel Sequence. Click on New.
As mentioned previously, we want to enter on panel A. The simple fact that we have panel A defined will mean that we can add parameters on the “Input to MMS001” section later.
We need to extract data from panel F, so we need this to be defined aswell.
Now we need to define out inputs, we are only going to be using inputs on MMS001/A.
W1ITNO = the Item Number, we set this to optional without a default value. We will be specifying the Item Number in our web-service. I did originally want to return a list of items if no item number was supplied, however I forgot that you cannot retrieve a list of items from a display program wrapped in a web service.
WWOPT2 = what option we will be executing when we ‘press’ enter (5 = Display), we will be hardcoding to display with a constant.
WWPSEQ = the panel sequence (we only want to go to Panel F). we will be hardcoding to only deal with panel F
To illustrate the relationship to MMS001/A
W1ITNO to the Item Number
WWOPT2 is not visibile
WWPSEQ is the panel sequence
Now that we have our inputs we want to retrieve our Free Cap Units from panel F
Now that we have everything defined, hit Save.
Now we need to go to the Overview tab
Where we need to deploy the created web-service.
Select the environment to deploy to – in my case Test.
Before anyone tries, wlmx02.indfish.co.nz doesn’t not resolve externally – so don’t bother 😉
Select the configuration – again, Test. Click on Finish and if LWS Designer and your environments are configured correctly you should see something like:
At this point you can create Test scripts. I’m not going to do this in this posting, but I certainly recommend using the Testing so you can quickly test your web services work as you expect.
Select the Server link.
Select the List Web Services
The Service Endpoint is where the service itself actually lives and is what we will be sending our crafted SOAP request to.
We have a wsdl which we can use with the likes of Microsoft Visual Studio. Clicking on it will take you to the link that you can enter in to Visual Studios Add Service Reference
So, now we have deployed we can finally get on to jscripting.
Now I will confess that I spent the better part of a day trying to get the scripting working properly – until I found a tool called soapUI (http://www.soapui.org/) – it allows you to basically generate tests for web-services and it allowed me to finally get the correct syntax for issuing my request to the server.
For the purposes of this test I have statically defined the request. I am using a company of 100, division of IFL and an Item Number of 03265. The usernames and passwords will need to be changed for your environment.
<mws2:user>potatoitadm</mws2:user>
<mws2:password>potato</mws2:password>
<mws2:company>100</mws2:company>
<mws2:division>IFL</mws2:division>
<fre:ItemNumber>03265</fre:ItemNumber>
As per usual I have extensive comments in the code itself. And I would like to stress that this is very rough cut code, proof of concept – I intend to refine this considerably as I investigate taking advantage of this for production purposes.
Our item with its free cap units.
And our scripts result.
Anyway, here’s the code – have fun!
import System; import System.IO; import System.Net; import System.Windows; import System.Windows.Controls; import MForms; import System.Web.Services.Protocols; import System.Xml; package MForms.JScript { class FreeCapTest { var gdebug; var btnFreeCaps : Button; public function Init(element: Object, args: Object, controller : Object, debug : Object) { gdebug = debug; var content : Object = controller.RenderEngine.Content; // add a button to retrieve the free caps of an item btnFreeCaps = new Button(); // the button will display "FreeCap" btnFreeCaps.Content = "FreeCap"; // set the position of the button Grid.SetColumnSpan(btnFreeCaps, 10); Grid.SetColumn(btnFreeCaps, 11); Grid.SetRow(btnFreeCaps, 0); // actually add the button to the panel content.Children.Add(btnFreeCaps); // we want to know about the click and unloaded events, so we register our interest here btnFreeCaps.add_Click(OnbtnFreeCapsClick); btnFreeCaps.add_Unloaded(OnbtnFreeCapsClickUnloaded); } public function OnbtnFreeCapsClickUnloaded(sender: Object, e: RoutedEventArgs) { // remove the events that we are subscribed to btnFreeCaps.remove_Click(OnbtnFreeCapsClick); btnFreeCaps.remove_Unloaded(OnbtnFreeCapsClickUnloaded); } public function OnbtnFreeCapsClick(sender: Object, e: RoutedEventArgs) { gdebug.WriteLine("OnbtnFreeCapsClick() Pressed"); // call our doRequest function - this will actually go out and do all of the heavy lifting doRequest(); } public function doRequest() { // we are going to use the HttpWebRequest object // http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.aspx // and we want to connect to the ItemFreeCaps2 service var hwrRequest : HttpWebRequest = WebRequest.Create("http://wlmx02.indfish.co.nz:12000/lwstest/services/ItemFreeCaps2"); // ensure we actually managed to create something if(null != hwrRequest) { // here we're defining our actions and content types hwrRequest.Headers.Add("SOAPAction","\"\""); hwrRequest.ContentType = "text/xml;charset=\"utf-8\""; hwrRequest.Method = "POST"; hwrRequest.Accept = "text/xml"; hwrRequest.Proxy = GlobalProxySelection.GetEmptyWebProxy(); // we are going to use a stream to write out our request (and also read it later) var strmStream : Stream = hwrRequest.GetRequestStream(); if(null != strmStream) { // SOAP is basically just xml, so we are going to use the XML framework // to make our lives easier. // Create an XML Document var xmdDocument : XmlDocument = new XmlDocument(); if(null != xmdDocument) { // we have a function here which generates our request and returns it in to this // string var gfcString = getFreeCaps(); // we then add the string to our XML document xmdDocument.LoadXml(gfcString); // the save of the document to our stream actually sends the request // to the server xmdDocument.Save(strmStream); // close our stream strmStream.Close(); // this section is wrapped in a try .. catch() // block because I had a lot of problems getting // this running initially. try { // now we want to get a response var wresponse : WebResponse = hwrRequest.GetResponse(); if(null != wresponse) { // we like using streams, so get the stream // connection strmStream = wresponse.GetResponseStream(); if(null != strmStream) { // create a streamreader to retrieve the data var srStreamReader : StreamReader = new StreamReader(strmStream); if(null != srStreamReader) { // and finally we read the data var strXML : String = srStreamReader.ReadToEnd(); // close the response wresponse.Close(); // close the stream reader srStreamReader.Close(); // lets load the xml we read xmdDocument.LoadXml(strXML); var xel = xmdDocument.DocumentElement; // search through the data until we find an element // with the name "NumberOfFreeCapacityUnits" var xmnRes = findNode(xel,"NumberOfFreeCapacityUnits"); if(null != xmnRes) { // write the value out to the debug output gdebug.WriteLine(xmnRes.InnerText); } } } } else { gdebug.WriteLine("No Response was returned"); } } catch(e) { gdebug.WriteLine("Exception: " + e.message); } } } } else { gdebug.WriteLine("doRequest() unable to create"); } } // findNode will recurse through the XmlNodes until if finds // and Element with astrElementName as a name, it will // then return that node. public function findNode(axmnNode : XmlNode, astrElementName : String) { var result : XmlNode = null; if(null != axmnNode) { if(String.Compare(astrElementName, axmnNode.Name, true) == 0) { result = axmnNode; } else { if(true == axmnNode.HasChildNodes) { result = findNode(axmnNode.FirstChild, astrElementName); } if(null != axmnNode.NextSibling) { result = findNode(axmnNode.NextSibling, astrElementName); } } } return result; } // this is a convenient function where we // define our SOAP envelope. For the purposes // of testing, we have hard-coded the entire thing public function getFreeCaps() { var strXML : String = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mws2="http://mws.intentia.net/mws2" xmlns:fre="http://www.indfish.co.nz/ItemFreeCaps2/FreeCaps"> <soapenv:Header> <mws2:mws> <mws2:user>potatoitadm</mws2:user> <mws2:password>potato</mws2:password> <mws2:company>100</mws2:company> <mws2:division>IFL</mws2:division> </mws2:mws> </soapenv:Header> <soapenv:Body> <fre:FreeCaps> <fre:MMS001> <!--Optional:--> <fre:ItemNumber>03265</fre:ItemNumber> </fre:MMS001> </fre:FreeCaps> </soapenv:Body></soapenv:Envelope>'; return strXML; } } }
Wow, this is great stuff, I cant wait to try!! Thanks!
Oh yes, that looks like a really easy and fast solution. And the API’s; dastardly?
I must admit, I am biased against the APIs.
I’ve had a great deal of frustration with the lack of documentation, so you have to try and see what happens. There are holes in functionality and then there is what feels like a lack of consistency between how the APIs behave vs. MoveX Explorer or Smart Office.
And there might have been a little bit of poetic exaggeration for effect thrown in for good measure 😉
Scott — getting excited about the possibilities here (tease !). No seriously looks great and I can see that in some cases the fact that a mashup concept may be applicable, however if for example were not using LWM and want to streamline say the stocktake program that this kind of stuff will be really useful . Cheers, Paul
Hi Scott, here’s another approach for calling LWS from LSO: http://thibaudatwork.wordpress.com/2011/09/22/how-to-consume-a-lawson-web-service-from-a-personalized-script-in-smart-office/
Hi Thibaud,
thank you for this. Quite a nice solution – I do personally prefer the idea of compiling down to a .dll – scripting something like web-services just seems messy.
Cheers,
Scott
Scott, there’s also a new MIAccess object in Smart Office to call M3 APIs directly without having to go thru LWS: http://lawsonsmartoffice.wordpress.com/2011/12/19/calling-m3-apis-in-jscript-on-a-background-thread/
thibaudatwork I want your help o urgent basis I wanted ur VBA code in which you are updating GeoCodes in MMS010 using Excel Macro so i want that Excel Macro code with some comments for lines of code as i am new to Macro calling
You may have better luck posting that question on his blog:
http://thibaudatwork.wordpress.com/
Cheers,
Scott
I want a jscript to call a LWS and update values in the fields like I am working right now on data migration of Movex12.1 to M3 10.1 so there are some 22 fields new in one display program so I want the jscript for the same .Your help is required in urgent ..Thanks in advance
This article runs through how to set up a webservice and call it from a script from start to finish. What specific parts are you struggling with?
If you want someone to write the script for you, you should look at engaging a contractor in who has knowledge of M3 and JScripts – there are quite a number of them around now…
Brandix, Commactivity, JBC spring to mind. There is at least another company in Europe whose name escapes me at the moment and of-course Infor.
Cheers,
Scott