/* Akana Javascript utility library - contains various useful functions Author: KenC - Rogue Wave Software, Inc. 2018 Disclaimer: The information provided in this document is provided "AS IS" WITHOUT ANY WARRANTIES OF ANY KIND INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT OF INTELLECTUAL PROPERTY. Akana may make changes to this document at any time without notice. The content of this document may be out of date, and Akana makes no commitment to update this content. */ /* FUNCTION: initializeContext ( msgVarName, partNames, msgVarNames ) Initializes the context of a process by: 1. Normalizing the specified request message; 2. Getting the specified message part values and setting their values into processContext variables; 3. Creating the specified normalized Message objects. PARAMETERS: msgVarName (optional string): the name of the processContext variable containing the request message. The Message object contained in this variable will be normalized, and the normalized Message object will be saved back into the variable. Default: "message" partNames (optional string array): an array containing the names of the message parts to retrieve and save the part values into processContext variables of the same name. Default: ["logProcessing"] msgVarNames (optional string array): an array containing the names of normalized Message objects to create and save into processContext variables of the same name. Default: ["request", "response", "fault"] */ function initializeContext ( msgVarName, partNames, msgVarNames ) { // Initialize missing param values to defaults if ( msgVarName == undefined ) msgVarName = "message"; if ( partNames == undefined ) partNames = [ "logProcessing" ]; if ( msgVarNames == undefined ) msgVarNames = [ "request", "response", "fault" ]; // Normalize the request Message var normalizedMsg = normalizeMessage( msgVarName ); // Silently set the logProcessing message variable from message var logProcessingMessagePart = getMessagePartValue( msgVarName, "logProcessing", "logProcessing", false ); if ( logProcessingMessagePart == null || logProcessingMessagePart == undefined ) logProcessingMessagePart = true; //Default true if absent var logProcessing = booleanValue( logProcessingMessagePart ); // Get the query param values from the normalized message // and save them in processContext variables for ( var i = 0, len = partNames.length; i < len; i++) { getMessagePartValue( msgVarName, partNames[i], partNames[i], logProcessing ); } // Initialize message variables to new normalized Message objects setupMessages( msgVarNames, logProcessing ); }; /* FUNCTION: normalizeMessage( msgVarName, toXML ) Retrieves the Message object in the specified processContext variable name, normalizes the Message object, saves the normalized Message object back into the variable, and returns the normalized Message object. PARAMETERS: msgVarName (required string): the processContext variable containing the Message object to be normalized. Default: "message" toXML (optional boolean): If true, normalize the message to XML. RETURNS: The normalized Message object. */ function normalizeMessage( msgVarName, toXML ) { // Set defaults for unspecified params if ( msgVarName == undefined ) msgVarName = "message"; if ( toXML == undefined ) toXML = false; // default false toXML = booleanValue( toXML ); if ( typeof msgVarName == "string" ) var message = processContext.getVariable( msgVarName ); // If we got a Message object... if ( message instanceof com.soa.message.script.Message ) { var normalizedMsg; if ( toXML ) normalizedMsg = message.normalizeToXML(); else normalizedMsg = message.normalize(); processContext.setVariable( msgVarName, normalizedMsg ); return normalizedMsg; } else { auditLog.error("ERROR: Cannot normalize the message. " + "The processContext variable '" + msgVarName + "' does not contain a Message object." ); return null; } }; /* FUNCTION: booleanValue( obj ) Returns a "logical" boolean value from the specified variable. Several attempts are made to produce a logical boolean value. If all attempts fail, then this function will return the "truthy" or "falsey" value of obj. PARAMETERS: obj (required, any type): the object from which to obtain a boolean value. RETURNS: A boolean value, depending on the object. */ function booleanValue( obj ) { // Handle falsey values first... if ( obj == undefined || obj == null || isNaN( obj ) || obj == 0 || obj == false || obj == "" ) return false; // If it's a JS string or a Java String... if ( typeof obj == "string" || obj instanceof String ) { if ( obj == "true" || obj != "0" ) return true; else if ( obj == "false" || obj == "0" || obj == "" ) return false; } // Else, if it's a JS number or a Java Number... else if ( typeof obj == "number" || obj instanceof Number ) { if ( obj == 0 ) return false else return true; } else { // It's something else, so return truthy or falsey value if ( obj ) return true; else return false; } }; /* FUNCTION: getMessagePartValue( msgVarName, partName, partVarName, logProcessing ) 1. Gets the specified part from the message in the specified processContext variable, 2. optionally saves the value of the part in the specified processContext variable, 3. and optionally logs the processing. PARAMETERS: msgVarName (optional string): the processContext variable name containing the Message object. Default: "message" partName (optional string): the name of the message part to process from the Message object. Default: "entity" partVarName (optional string): the name of the processContext variable to set to the part value. If the text value of the message part is a "logical boolean" - that is, a string with a value of "true" or "false", the variable will be set to a Boolean value to reflect this. logProcessing (optional boolean): If true, log the processing. Default: false RETURNS: the message part value. */ function getMessagePartValue( msgVarName, partName, partVarName, logProcessing ) { // Set default values for unspecified parameters if ( msgVarName == undefined ) msgVarName = "message"; if ( typeof msgVarName != "string" ) return null; if ( partName == undefined ) partName = "entity"; if ( typeof partName != "string" ) return null; logProcessing = booleanValue( logProcessing ); var partValue; // Get the Message object from the variable var message = processContext.getVariable( msgVarName ); // If we got the Message object... if ( message instanceof com.soa.message.script.Message ) { var part = message.getPart( partName ); // Get the part if ( part ) { // If we got the part... // If the part is an Array, get just the first element if ( part instanceof Array ) partValue = part[0]; else partValue = part; // Change the part value to boolean if "logical boolean" if ( partValue == "true" ) partValue = true; else if ( partValue == "false" ) partValue = false; // If indicated, set processContext variable to part value if ( typeof partVarName == "string" || partVarName instanceof String ) processContext.setVariable( partVarName, partValue ); } } return partValue; }; /* FUNCTION: setupMessages( msgVarNames, logStatus ) Creates normalized Message objects and places each one in the processContext variable of the same name. PARAMETERS: msgVarNames (optional array of strings): an array of strings containing name(s) of processContext variable(s) to be set to normalized Message objects. Default: ["request","response","fault"] logProcessing (optional boolean): if true, log processing status. Default: false */ function setupMessages( msgVarNames, logProcessing ) { var msgVarNamesStr = ""; logProcessing = booleanValue( logProcessing ); // Set msgVarNames to the default variable names if not provided, // or if the provided value is not an Array if ( !( msgVarNames instanceof Array ) ) { msgVarNames = ["request", "response", "fault"]; if ( logProcessing ) auditLog.debug( "Using default message variable names: " + "'request', 'response', and 'fault'" ); } // Create normalized Message objects and set into processContext for ( var i = 0, len = msgVarNames.length; i < len; i++ ) { if ( typeof msgVarNames[i] == "string" || msgVarNames[i] instanceof String ) { processContext.setVariable( msgVarNames[i], msgFactory.createNormalized() ); msgVarNamesStr += "\n" + msgVarNames[i]; // Build log str } } if ( logProcessing ) { auditLog.debug( "Normalized Message objects created and placed in " + "process variables:\n" + msgVarNamesStr ); } }; /* FUNCTION: logResponseMsg( msgVarName, formatRsp ) Formats and logs a message containing the XML content of the Message object contained in the specified processContext variable name, and optionally sets the formatted XML message text into the Message as the string content. PARAMETERS: msgVar (required string): the name of the processContext variable containing the Message object whose XML content is to be formatted and logged. formatRsp (optional boolean): If true, the string content of the Message object will be set to the formatted XML string. Default: false logProcessing (optional boolean): if true, log the processing status. Default: false RETURNS: the Message object. */ function logResponseMsg( msgVarName, formatRsp, logProcessing ) { // Handle missing or invalid params formatRsp = booleanValue( formatRsp ); logProcessing = booleanValue( logProcessing ); // Get the variable containing the Message object var message = processContext.getVariable( msgVarName ); // If variable does not contain a Message, log and return false if ( !( message instanceof com.soa.message.script.Message ) ) { auditLog.error( "ERROR: the variable '" + msgVarName + "' does not contain a Message object." ); return false; } var vkb = new vkbeautify(); var xmlStr = String( message.getContentAsString() ); var formattedXML = vkb.xml( xmlStr ); if ( logProcessing ) auditLog.debug( "Response message:\n\n" + formattedXML + "\n" ); // If formatting response, set into msg & save in processContext if ( formatRsp ) { message.setStringContent( formattedXML ); processContext.setVariable( msgVarName, message ); } return message; }; /* FUNCTION: formatJSON( xmlStr, message, logProcessing ) Formats a JSON string from the specified XML string, and optionally sets the string content of the Message object held in the specified processContext variable name to the JSON string, if a variable name is specified. PARAMETERS: xmlStr (required): the XML string to convert to a JSON string forceTopLevelObject (optional boolean): If true, the root XML element will be a named JSON object inside an outer set of {}'s. If false, the root XML element will be treated anonymously and the root JSON object represented with the outer {}'s. Default: true. msgVarName (optional string): the processContext variable name that contains a Message object. If specified, the content of this variable will be retrieved. If the content of the variable is a Message object, the string content of that Message object will be set to the formatted JSON string. logProcessing (optional boolean): if true, log processing status. Default: false RETURNS: the formatted JSON string. */ function formatJSON( xmlStr, forceTopLevelObject, msgVarName, logProcessing ) { // Handle param value types and missing params if ( !xmlStr ) return false; if ( !( xmlStr instanceof String) ) return false; forceTopLevelObject = booleanValue( forceTopLevelObject ); logProcessing = booleanValue( logProcessing ); converter = com.soa.json.xml.script.XML2JSON.converter(); converter.setForceTopLevelObject( forceTopLevelObject ); converter.setRemoveNamespacePrefixes( true ); converter.setIgnoreNamespaces( true ); var jsonString = converter.convert( xmlStr ); var jsonObject = JSON.parse( jsonString ); var formattedJSONStr = JSON.stringify( jsonObject ); if ( logProcessing ) auditLog.debug('JSON:\n\n' + formattedJSONStr ); // If indicated, set the message content to the JSON string if ( typeof msgVarName == "string" || msgVarName instanceof String ) { var message = processContext.getVariable( msgVarName ); if ( message instanceof com.soa.message.script.Message ) { message.setStringContent( formattedJSONStr ); if ( logProcessing ) auditLog.debug( "The message string content " + "was set to JSON string:\n\n" + formattedJSONStr ); } } return formattedJSONStr; }; /* FUNCTION: convertXMLToJSON( xmlStr, forceTopLevelObj, logProcessing ) Converts an XML string to a JSON object, without namespaces. PARAMETERS: xmlStr (required string): XML string to convert to a JSON object. forceTopLevelObj (optional boolean): If true, the root XML element will be a named JSON object inside an outer set of {}. logProcessing (optional boolean): if true, log processing status. Default: false RETURNS: the JSON object. */ function convertXMLToJSON( xmlStr, forceTopLevelObj, logProcessing ) { if ( !(xmlStr instanceof String) && (typeof xmlStr != "string") ) return null; forceTopLevelObj = booleanValue( forceTopLevelObj ); logProcessing = booleanValue( logProcessing ); var converter = com.soa.json.xml.script.XML2JSON.converter(); converter.setForceTopLevelObject( forceTopLevelObj ); converter.setRemoveNamespacePrefixes( true ); converter.setIgnoreNamespaces( true ); var jsonString = converter.convert( xmlStr ); var jsonObj = JSON.parse( jsonString ); if ( logProcessing ) { var fmtJSONStr = JSON.stringify( jsonObj ); if ( logProcessing ) auditLog.debug( "Converted XML to JSON string:\n\n" + fmtJSONStr ); } return jsonObj; }; /* FUNCTION: convertJSONToXML( jsonObj, rootElemName, elemName, formatXML ) Converts a JSON object to XML. PARAMETERS: jsonObj (required object): JSON object to convert to an XML string. rootElemName (required string): the name of the root XML element to be used in the new XML string. elemName (required string): the name of the XML element to be used for the XML element within the XML root element. formatXML (optional boolean): if true, the returned XML string will be formatted for readability. If false or absent, returned XML string will not be formatted. RETURNS: the JSON object. */ function convertJSONToXML( jsonObj, rootElemName, elemName, formatXML ) { // Early returns for undefined params if ( jsonObj == undefined ) return null; if ( rootElemName == undefined ) return null; if ( elemName == undefined ) return null; formatXML = booleanValue( formatXML ); // false if absent // Get a converter and set the root elem and elem names var converter = com.soa.json.xml.script.JSON2XML.converter(); converter.setRootName( rootElemName ); converter.setElementName( elemName ); converter.setUseParentPrefix( false ); // Convert the JSON object to an XML string var formattedXML = String( converter.convert( jsonObj ) ); // Format the XML string if indicated if ( formatXML ) { var vkb = new vkbeautify(); formattedXML = vkb.xml( formattedXML ); } return formattedXML; }; /* FUNCTION: pad( input, padLen, padChar, padRight ) Left- or right-pads the specified number or string to the specified length, with the specified padding char or with the default padding char for the input type. PARAMETERS: input: (required, string or number) The value to pad. If not specified, this function returns immediately. padLen: (optional, number) length to which to pad the input value. Default: The length of the input value. padChar: (optional, string or number) character(s) to use to pad. Default: '0' for number or numeric string, space otherwise padRight: (optional, boolean) Whether to pad on the right or left (true: pad on the right, false: pad on the left). default: false for number or numeric string, else true. */ function pad( input, padLen, padChar, padRight ) { if ( !input) return input; // Return if no input // Default null params appropriately for input type var inputStr = input + ''; // Coerce input to a string padChar = padChar || ( isNaN( inputStr ) ? ' ' : '0' ); padLen = padLen || inputStr.length; // Default to input len padRight = padRight || ( isNaN( inputStr ) ? true : false ); // Create an Array of the proper length and build padding string var padStr = new Array( ( ++padLen ) - inputStr.length ) .join( padChar ); // Build return string and trim it to a length of padLen var returnStr = padRight ? inputStr + padStr : padStr + inputStr; returnStr = returnStr.substr( 0, padLen); return returnStr; }; /* The following functions are adapted from vkBeautify - a javascript module to pretty-print or minify text in XML, JSON, CSS and SQL formats. */ function createShiftArr(step) { var limit = 12; var space = ' '; if ( isNaN( parseInt(step) ) ) { // argument is string space = step; } else { // arg is integer - build space string to correct length space = ''; if ( step > limit ) step = 12; // Enforce limit in indentation for ( ix = 0; ix < step; ix++ ) space += ' '; } var shift = [ '\n' ]; // array of shifts for ( ix = 0; ix < 100; ix++ ){ shift.push( shift[ ix ] + space ); } return shift; } function vkbeautify() { this.step = ' '; // 4 spaces this.shift = createShiftArr(this.step); }; vkbeautify.prototype.xml = function(text,step) { var ar = ( text + "" ) .replace(/>\s{0,}<") .replace(/ or -1 ) { str += shift[deep] + ar[ix]; inComment = true; // end comment or // if ( ar[ix].search( /-->/ ) > -1 || ar[ix].search( /\]>/ ) > -1 || ar[ix].search( /!DOCTYPE/ ) > -1 ) { inComment = false; } } else // end comment or // if ( ar[ix].search( /-->/ ) > -1 || ar[ix].search( /\]>/ ) > -1 ) { str += ar[ix]; inComment = false; } else // // if ( /^<\w/.exec(ar[ix-1]) && /^<\/\w/.exec( ar[ix] ) && /^<[\w:\-\.\,]+/.exec( ar[ix-1] ) == /^<\/[\w:\-\.\,]+/.exec( ar[ix] )[0].replace('/', '') ) { str += ar[ix]; if ( !inComment ) deep--; } else // // if ( ar[ix].search( /<\w/ ) > -1 && ar[ix].search( /<\// ) == -1 && ar[ix].search( /\/>/ ) == -1 ) { str = !inComment ? str += shift[deep++] + ar[ix] : str += ar[ix]; } else // ... // if ( ar[ix].search( /<\w/ ) > -1 && ar[ix].search( /<\// ) > -1 ) { str = !inComment ? str += shift[deep] + ar[ix] : str += ar[ix]; } else // // if ( ar[ix].search( /<\// ) > -1 ) { str = !inComment ? str += shift[--deep] + ar[ix] : str += ar[ix]; } else // // if ( ar[ix].search( /\/>/ ) > -1 ) { str = !inComment ? str += shift[deep] + ar[ix] : str += ar[ix]; } else // // if ( ar[ix].search( /<\?/ ) > -1 ) { str += shift[deep] + ar[ix]; } else // xmlns // if ( ar[ix].search( /xmlns\:/ ) > -1 || ar[ix].search( /xmlns\=/ ) > -1 ) { str += shift[deep] + ar[ix]; } else { str += ar[ix]; } } return ( str[0] == '\n' ) ? str.slice(1) : str; } vkbeautify.prototype.json = function(text,step) { var step = step ? step : this.step; if (typeof JSON === 'undefined' ) return text; if ( typeof text === "string" ) return JSON.stringify( JSON.parse(text), null, step ); if ( typeof text === "object" ) return JSON.stringify( text, null, step ); return text; // text is not string nor object } vkbeautify.prototype.xmlmin = function(text, preserveComments) { var str = preserveComments ? text : text.replace( /\/g, "" ) .replace( /[ \r\n\t]{1,}xmlns/g, ' xmlns' ); return str.replace( />\s{0,}<" ); } vkbeautify.prototype.jsonmin = function(text) { if (typeof JSON === 'undefined' ) return text; return JSON.stringify( JSON.parse(text), null, 0 ); };