Launching Business Objects from Smart Office and Running a Script from a Script

As part of a project I am working on, we have got budget to purchase additional licences for our chosen reporting tool – Business Objects Edge.

This means that we can move most of our Crystal Reports on to the Business Objects Edge server, it can run both Crystal and WebI reports. This will remove the need for most of our staff to use a custom application written to run Crystal Reports which is deployed to each staff members computer.

An added benefit is that as Business Objects Edge client interaction is designed around being run within a web-browser, so we can launch reports from Smart Office with context data via a URL.

But with every silver lining, there is a cloud…

Business Objects licensing isn’t traditionally great for this sort of thing – each URL launch consumes a license. If you are running concurrent licenses a single user can quickly eat through your licenses, in our instance we will be named user licenses. Even though we will be named user licenses soon, I still would have to obtain a login token with each report which gets launched, so my plan is to store the login token to reduce the number of calls we need to make to run a report. If we weren’t using named user licenses, we would need to control our browser session and logout when the user closes the window.

There’s a fair amount of code that is needed to log in (we don’t have single sign-on yet) to run our report and it’s not something that I really want to per program where I am creating a report link.
To resolve this, I have created a script called boeRunReport.js – we will call this script from another script with the arguments determining the name of the report and its arguments.

The basic process is as follows:

  1. Run Script which retrieves report context information
  2. Call the boeRunReport script passing the reports arguments
  3. Retrieve the location of our Business Objects Edge server from a config file
  4. Check to see if we have an existing login token saved to the SessionCache
    1. If no login token, make a Rest call to get a login token and save it to the SessionCache
  5. Build our URL and then call DashboardTaskService.Current.LaunchTask() to run the report

Below is a script which demonstrates calling our boilerplate code – in a real world situation we would be extracting some data from the panel and then including that in our parameters so our report becomes in-context.

If we were being really clever we’d also create the Task so we can keep track of the window that is created so we can log out. But given the licensing model I’m working with, fire and forget works and is easier 🙂

 

/*
**	Proof of concept, running a script from another script
**
**	
*/

import System;
import System.Windows;
import System.Windows.Controls;
import MForms;

import Mango.UI.Services;

import System.Net;
import System.Web;

package MForms.JScript
{
	class testingReports
	{
		public function Init(element: Object, args: Object, controller : Object, debug : Object) 
		{
			// iDocID = the identifier of the report we are going to run
			// sIDType = this tells Business Objects Edge how to treat the iDocID
			// lsS<parameter name> = <parameter name> 'Order Name' is the name of the report parameter 'Order Name'
			// lsS<parameter name> = <parameter name> 'Attention' is another report parameter 'Attention'
			var strArguments : String = Uri.EscapeDataString("&iDocID=Ae392dj3K_9Dq3V_4S7QxCw&sIDType=CUID&lsSOrder Number=0000000002&lsSAttention=Scott");

			// the actual launching of the task.  Call the script boeRunReport and provide the arguments to determine
			// the script and its arguments
			DashboardTaskService.Current.LaunchTask(new Uri("mforms://_runscript?name=boeRunReport&args=" + strArguments));
		}
	}
}

And now we have the boilerplate code that does all of the heavy lifting:

/*
**	boeRunReport.js
**
**	This jscript will provide the base code to run a Business Objects Edge
**	report from a URL.
**
**	We will read the base URLs, Username and Password from our configuration
**	file.  We could use our users login/password and secLDAP authentication instead
**	but in this instance we won't.
**
**	We will retrieve a login token from the restful webservices, and then
**	cache that so subsequent calls don't attempt to gain another license.
**
**	We should have another script call this script, passing in the rest of the URL
**	via the args.
**
**	An example would be a call like so:
**		DashboardTaskService.Current.LaunchTask(new Uri("mforms://_runscript?name=boeRunReport&args=%26iDocID=AXgm1vjEaLpOh06wgcuF9OE%26sIDType=CUID%26lsMOrder Number=0000000002%26lsSAttention=Scott"));
**
**	Note:
**		We don't do any logouts.  We don't really care, because the session will eventually timeout
**		and we have named user licenses
**
**	History:
**		20150612	- initial cut
*/

import System;
import System.Windows;
import System.Windows.Controls;
import MForms;

import Mango.UI;
import Mango.UI.Services;
import Mango.Services;

import System.Net;
import System.Text;
import System.IO;

import System.Web;

import System.Xml;
import System.Xml.Serialization;
import System.Xml.XPath;

package MForms.JScript 
{
	class boeRunReport
	{
		var gstrConfigurationFileName = "citcl_boeRunReportProperties.xml";
		var gstrSessionCacheKey = "boeRunReport_LoginToken";
		
		var gstrOpenDocumentURI : String = "http://boe.indfish.co.nz:8080/BOE/OpenDocument/opendoc/openDocument.jsp?";
		var gstrRestURI : String = "http://boe.indfish.co.nz:6405/biprws/logon/long";
		
		var gUsername = null;
		var gPassword = null;
		var gToken = "";
		
		var gSettings = null;
		
		public function Init(element: Object, args: Object, controller : Object, debug : Object) 
		{
			// check to see if we have already obtained a session token, if so we will use that
			if(true == SessionCache.ContainsKey(gstrSessionCacheKey))
			{
				gToken = SessionCache.Get(gstrSessionCacheKey);
			}
			else
			{
			}
				
			var profile : ISystemProfile = ApplicationServices.SystemProfile;
			var runtimeURI = profile.GetProperty(ApplicationGroupType.M3, ApplicationType.MForms, "RuntimeUrl");
			gSettings = loadSettings(runtimeURI);
			
			if(null != gSettings)
			{
				// get the username/password from the config file
				gUsername = gSettings.user;
				gPassword = gSettings.password;
				gstrRestURI = gSettings.boeRestURL;
				gstrOpenDocumentURI = gSettings.boeOpenDocumentURL;
				
				if(true == String.IsNullOrEmpty(gToken))
				{
					gToken = getSession(gstrRestURI);
				}
				
				if(false == String.IsNullOrEmpty(gToken))
				{
					if(false == SessionCache.ContainsKey(gstrSessionCacheKey))
					{
						if(false == String.IsNullOrEmpty(gToken) && gToken != "undefined" && gToken != "null")
						{
							SessionCache.Add(gstrSessionCacheKey, gToken);
						}
					}
					runReport(args);
				}
				else
				{
				}
			}
			else
			{
				ConfirmDialog.ShowErrorDialog("Error", "Failed to load settings for running reports");
			}

		}
		
		private function runReport(args)
		{
			gUsername = gSettings.user;
			gPassword = gSettings.password;
			gstrRestURI = gSettings.boeRestURL;
			gstrOpenDocumentURI = gSettings.boeOpenDocumentURL;
			
			// remove the leading and trailing speechmark
			var strLogin : String = gToken.substring(1, gToken.length-1);
			// now we need to escape the data
			var strEscaped : String = HttpUtility.UrlEncode(strLogin, System.Text.Encoding.UTF8); //Uri.EscapeUriString(strLogin);
			
			var strFinalString : String = gstrOpenDocumentURI + args + "&token=" + strEscaped;

			DashboardTaskService.Current.LaunchTask(new Uri(strFinalString));
		}
		
		private function getSession(astrURL : String) : String
		{
			var result : String = "";
			
			try
			{
				var hwrRequest : HttpWebRequest = WebRequest.Create(astrURL);
				if(null != hwrRequest)
				{
					var credCache = new CredentialCache();
					// here we're defining our actions and content types
					hwrRequest.ContentType = "application/xml;charset=\"utf-8\"";
					hwrRequest.Method = "POST";
					hwrRequest.Accept = "application/xml";
					
					credCache.Add(new Uri(astrURL), "Basic", new NetworkCredential("secEnterprise\\" + gUsername, gPassword));
					
					hwrRequest.Credentials = credCache;
					
					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 then add the String to our XML document
							xmdDocument.LoadXml("<attrs xmlns=\"http://www.sap.com/rws/bip\"><attr name=\"userName\" type=\"string\">" + gUsername + "</attr><attr name=\"password\" type=\"string\">" + gPassword + "</attr><attr name=\"auth\" type=\"string\" possibilities=\"secEnterprise,secLDAP,secWinAD,secSAPR3\">secEnterprise</attr></attrs>");

							// the save of the document to our stream actually sends the request
							// to the server
							xmdDocument.Save(strmStream);

							// close our stream
							strmStream.Close();
							
							var wresponse : WebResponse = hwrRequest.GetResponse();
							if(null != wresponse)
							{
								// search for the Logon token
								for(var i = 0; i < wresponse.Headers.Count; i++)
								{
									if("X-SAP-LogonToken" == wresponse.Headers.Keys[i])
									{
										//gDebug.Debug("  " + wresponse.Headers.Keys[i] + ": " + wresponse.Headers[i]);
										result = wresponse.Headers[i];
									}
								}
								wresponse.Close();
							}
							else
							{
								//gDebug.Error("No response");
							}
						}
					}
				}
				else
				{
					//gDebug.Error("No hwrRequest");
				}
			}
			catch(ex)
			{
				ConfirmDialog.ShowErrorDialog("Exception", ex);
				//gDebug.Error(ex);
			}
			return(result);
		}
		
		private function loadSettings(uri : String)
		{
			try
			{
				var strFullPath : String = uri + "/xml/" + gstrConfigurationFileName;

				var doc : XPathDocument = new XPathDocument(strFullPath);
				
				var nav : XPathNavigator = doc.CreateNavigator();
				
				var user = nav.Evaluate("string(//Username)");
				var password = nav.Evaluate("string(//Password)");
				var boeOpenDocumentURL = nav.Evaluate("string(//BOEOpenDocumentURL)");
				var boeRestURL = nav.Evaluate("string(//BOERestURL)");
				
				return{user : user, password : password, boeOpenDocumentURL : boeOpenDocumentURL, boeRestURL : boeRestURL};
			}
			catch(ex)
			{
				//gDebug.Error("Exception loading gSettings: " + ex);
			}
			return(null);
		}
	}
}

 

And it looks something like this:

 

Enjoy!

Scott

This entry was posted in Development, M3 / MoveX, Misc. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s