Monday, March 19, 2018

Take your OBIEE graphs and charts to new level with D3 (Part 2.1)

Let's upgrade our collapsible Tree a little to include some values for each node. Something like this ...
At first we would need data values added to the analysis. That means we need to add data values to each result columns of  "union all" section. (see Part 2)
Once we do that, rest of the steps will be very similar...



Prefix:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}

div.tooltip {
    position: absolute;
    text-align: center;
    width: 80px;
    height: 28px;
    padding: 2px;
    font: 12px sans-serif;
    background: lightsteelblue;
    border: 0px;
    border-radius: 8px;
    pointer-events: none;
}
</style>



<svg width="1200" height="1500"></svg>
<script src="/analyticsRes/libraries/d3.v3.min.js"></script>
<script>



var data=[];

Narrative:

data.push({name:"@2",parent:"@1", Expense:"@3"}); 

Postfix:



var dataMap = data.reduce(function(map, node) {
 map[node.name] = node;
 return map;
}, {});

var treeData = [];
data.forEach(function(node) {
 // add to parent
 var parent = dataMap[node.parent];
 if (parent) {
  // create child array if it doesn't exist
  (parent.children || (parent.children = []))
   // add node to child array
   .push(node);
 } else {
  // parent is null or missing
  treeData.push(node);
 }
});

// Define the div for the tooltip
var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

var margin = {top: 20, right: 300, bottom: 20, left: 300},
    width = 1500 - margin.right - margin.left,
    height = 900 - margin.top - margin.bottom;

var i = 0,
    duration = 750,
    root;

 
var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  root = treeData[0];
  root.x0 = height / 2;
  root.y0 = 0;

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);

d3.select(self.frameElement).style("height", "800px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 180; });

  // Update the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
      .on("click", click).on("mouseover", function(d) {
             div.transition()
                .duration(200)
                .style("opacity", .9);
             div.html("Expense $"+d.Expense + "<br/>"  )
                .style("left", (d3.event.pageX) + "px")
                .style("top", (d3.event.pageY - 28) + "px");
            })
  .on("mouseout", function(d) {
            div.transition()
                .duration(500)
                .style("opacity", 0);
});

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}
</script>

Thursday, March 15, 2018

Take your OBIEE graphs and charts to new level with D3 (Part 2)

Collapsible Tree


Please read my first blog here for set up needed to start your OBIEE D3 visualization.

In this blog you will see,  how you can have interactive Essbase hierarchy(or any other hierarchy) displayed through OBIEE like above.

We will do it with hierarchical data added as parent / child in analysis and then D3 will do the rest. We will use "union all" in the analysis to achieve this.

The analysis will look like this ...
sort order column will make sure data is sorted by generation. We will put simple 'A', 'B','C' to identify each generation.

Generation 1...

Generation 2 ...


 Generation 3...

And so on ...

We also need to make sure that Leaf nodes are not present in parent column.... to do that we will add filter to each generation query. Assuming at least root node exists for the tree to built. 

So Filter will look like ....  

Gen 1 filter
none

Gen 2 Filter

Gen 3 Filter


And so on. ....

The above steps will provide you data in the narrative view that you can use in your D3 code.

Lets look at the D3 code... 

Prefix

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}

</style>



<svg width="1200" height="1500"></svg>
<script src="/analyticsRes/libraries/d3.v3.min.js"></script>
<script>



var data=[];


Narrative
data.push({name:"@2",parent:"@1"});


Postfix


var dataMap = data.reduce(function(map, node) {
 map[node.name] = node;
 return map;
}, {});

var treeData = [];
data.forEach(function(node) {
 // add to parent
 var parent = dataMap[node.parent];
 if (parent) {
  // create child array if it doesn't exist
  (parent.children || (parent.children = []))
   // add node to child array
   .push(node);
 } else {
  // parent is null or missing
  treeData.push(node);
 }
});


var margin = {top: 20, right: 300, bottom: 20, left: 300},
    width = 1500 - margin.right - margin.left,
    height = 900 - margin.top - margin.bottom;

var i = 0,
    duration = 750,
    root;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  root = treeData[0];
  root.x0 = height / 2;
  root.y0 = 0;

  function collapse(d) {
    if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
  }

  root.children.forEach(collapse);
  update(root);

d3.select(self.frameElement).style("height", "800px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 180; });

  // Update the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
      .on("click", click);

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}
</script>



Thursday, March 8, 2018

Take your OBIEE graphs and charts to new level with D3 (Part 1)



Sortable Bar Chart using d3-tip to add tooltips


It really started with a user asking me whether OBIEE has donut chart. I said “no”, not with the out of the box OBIEE. I know that OBIEE has great ability to integrate external applications through the use of java script libraries but I never had time to explore that. Once I started to dig deeper, I got fascinated by the possibility.  Stunning visualization examples of D3 (Data-Driven Documents) blew my mind.

     If you never been to D3 example page time to pay homage before you read further. https://github.com/d3/d3/wiki/Gallery



This blog is not about how you can have your first D3 + OBIEE chart. Rather, I would like you to google D3 + OBIEE. Read very informative blogs by Rittman Mead, Red Pill Analytics etc.  
Here in this blog I will share the code that I used in OBIEE narrative view to achieve what you see in the screen shot.
This is a combination of the following 2 D3 codes.


Prerequisite

1.       Set your analyticsRes folder for your D3 library. If you don’t know how to set up a custom folder in OBIEE, google “Deploy a custom folder in OBIEE”.
2.       Download libraries and keep them in analyticsRes folder.
d3.v3.min.js
d3.tip.v0.6.3.js

3.       Once you set that up properly, you should be able to call D3 libraries like below from your browser.
http://YourObieeServer:9502/analyticsRes/libraries/d3.v3.min.js

Prefix

<!DOCTYPE html>

<meta charset="utf-8">
<style>
.axis text {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.bar {
  fill: orange;
  fill-opacity: .6;
}

.bar:hover {
  fill: orangered ;
}

.x.axis path {
  display: none;
}
.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 2px;
}

/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -1px 0 0 0;
  top: 100%;
  left: 0;
}
</style>
<svg width="960" height="1060"></svg>
<script src="/analyticsRes/libraries/d3.v3.min.js"></script>
<script src="/analyticsRes/libraries/d3.tip.v0.6.3.js"></script>

<script>
var data=[];


Narrative

data.push({letter:"@1",frequency:"@2"});


Postfix



var margin = {top: 20, right: 20, bottom: 90, left: 40},
    width = 640 ;
    height = 290 ;
var formatPercent = d3.format(".2s");

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1, 1);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(formatPercent);

var svg = d3.select("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(d) {
    return "<strong>Vendor: " +d.letter+"</strong> <span> <p> Expense:$"   +d.frequency +"</p></span>";
  });


svg.call(tip);
  data.forEach(function(d) {
    d.frequency = +d.frequency;
  });

  x.domain(data.map(function(d) { return d.letter; }));
  y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis).selectAll("text").style("text-anchor", "end")
                        .style("font-style","italic")
            .attr("dx", ".5em")
            .attr("dy", ".1em")
            .attr("transform", function(d) {
                  return "rotate(-25)"
                });
     

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 8)
      .attr("dy", ".8em")
      .style("text-anchor", "middle")
      .text("Expense");

  svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.letter); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.frequency); })
      .attr("height", function(d) { return height - y(d.frequency); })     
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide);


   d3.select("#obiee").append("input").attr("checked", true)
    .attr("type", "checkbox").on("change", change);


  var sortTimeout = setTimeout(function() {
    d3.select("#obiee").attr("type", "checkbox").each(change);
  }, 2000);

  function change() {
    clearTimeout(sortTimeout);

    // Copy-on-write since tweens are evaluated after a delay.
    var x0 = x.domain(data.sort(this.checked
        ? function(a, b) { return b.frequency - a.frequency; }
        : function(a, b) { return d3.ascending(a.letter, b.letter); })
        .map(function(d) { return d.letter; }))
        .copy();

    svg.selectAll(".bar")
        .sort(function(a, b) { return x0(a.letter) - x0(b.letter); });

    var transition = svg.transition().duration(750),
        delay = function(d, i) { return i * 50; };

    transition.selectAll(".bar")
        .delay(delay)
        .attr("x", function(d) { return x0(d.letter); });

    transition.select(".x.axis")
        .call(xAxis).selectAll("text").style("text-anchor", "end")
            .attr("dx", ".5em")
            .attr("dy", ".1em")
            .attr("transform", function(d) {
                  return "rotate(-25)"
                }).delay(delay);
  }
;



</script>

<div id="obiee">

</div>


Read Part 2 on D3 Collapsible Tree


Friday, June 30, 2017

Move data "On-Save" from Planning BSO to Reporting ASO (Part 2)

This is continuation from previous post to provide more 'out of the box' solution for data movement from BSO planning to ASO Reporting cube. This solution is only possible if you are in 11.1.2.4. 

  We will have exactly the same process like part 1 of this blog. The only thing that we will be skipping here is writing CDF part. Because Oracle has calc manager CDF that can run a MaxL stored in the server using RUNJAVA com.hyperion.calcmgr.common.cdf.MaxLFunctions. More over from 11.1.2.4 we can have formatted MDX output in a flat file. What else you need ! You already guessed where it is going...

Solution:

  1. Level0 export MDX query with 'NONEMPTYBLOCK‘  keyword. 
  2. Create MaxL script to spool MDX value to a flat file. Use set column_separator "|" ; to get a formatted output. More information on this available on other very useful blogs.
  3. Load the flat file to ASO cube with MaxL. 
  4. Call all of these MaxL scripts from BSO calculation script using RUNJAVA com.hyperion.calcmgr.common.cdf.MaxLFunctions. (Check internet blogs)
  5. Add this calculation script in your web-form to run it on save.

Move data "On-Save" from Planning BSO to Reporting ASO (Part 1)

Moving data from BSO planning application to ASO reporting application ? 
Want to have this data movement really fast ? 
Want to move that data as soon as users save web forms ? 
     
      Well, you can do that in many different ways. The time it takes to move the data between 2 cubes depends mostly on how fast you can extract level 0 data from the Planning BSO cube. 

    To do that I have used MDX export from planning BSO cube with 'NONEMPTYBLOCK‘ keyword. I have tested this against 3 different BSO planning cubes and found that MDX with 'NONEMPTYBLOCK' keyword export level 0 data faster than data export calculation script. But I imagine, it won't be the case always. So, test test and test before you select your data export process. 

We have built this solution last year using Essbase Java API and CDF. I agree this is not really out of the box solution. You need to use bit of Essbase Java API to format MDX output to get this to work.  But if you are in Essbase version 11.1.2.4, this can be a solution where you don't have to write any Java code. I will write about that in my next post.


Here is the solution steps:


    

  1. Get your level0 export MDX query ready. Don't forget to   add 'NONEMPTYBLOCK‘  keyword. 
  2. Now you have to create Java Custom defined function(CDF). With in the CDF use Essbase Java API to parse and load MDX output to ASO database. If you need more details, I have blogged earlier about this here
  3. Run CDF from a calculation script in the planning cube using RUNJAVA command.
  4. Add this calculation script in your web-form to run it on save.
  5. Success of this 'On-Save' load depends on speed. You don't want your user to watch their frozen web-form for 5 min while data is getting transferred. Try to have this data movement more targeted by passing POV information to your CDF from planning web-form via calculation script. By doing that you can minimize amount of data movement and restrict it only to POV of the web-form. 




 Demo time ... 






Thursday, November 3, 2016

Truncating DATAEXPORT target relational table with CDF

First of all, I am a big fan of Essbase DATAEXPORT calc script to relational database. It provides flexible way of communication between cubes. You can export data to a relational table then manipulate through SQL load rule and load it back.  Certain type of mapping / data management is much easier in SQL. Moreover entire script can be called from Maxl. 
    Well, can it be? One problem though, Essbase DATAEXPORT calc does not allow you to truncate / delete from the underline table. Bummer!! How hard it could be for oracle to provide that function? It would have save my time of writing this blog and yours reading it. 
We will solve this issue with CDF (custom defined function). If you have not created your first CDF yet, then this is a good place to start. Once you have that CDF ready you can use it in your calc script like this…

Steps to install CDF

  1. Install JDK. In my case I have JDK 1.7 
  2. Install Eclipse (Java IDE). 
  3. In Eclipse create a new Java project. (File->New->Java Project). I have named it CDF.
  4. Expand the project and right click on src folder, create new -> package. I named it  com.williams.cdf
  5. Right click on com.williams.cdf and select Build path -> Configure Build Path. Click on the Libraries tab then add 2 External JARs essbase.jar and odbc14.jar. File essbase.jar is available in Oracle/Middleware/EPMSystem11R1/products/Essbase/EssbaseServer/java directory. you can download odbc14.jar from internet. It is required to connect to your underline oracle database. If your relational database is not oracle then you need corresponding java jar file to connect with that database.
Now the set up is done,  it is time for coding.
 6. Right click on package com.williams.cdf and create new -> Class.  I named it relationalDDL. Once done, it should look like this...
Here is the code that I have for relationalDDL.java ...

package com.williams.cdf;
  
import java.sql.*;
import oracle.jdbc.pool.OracleDataSource;

public class relationalDDL {
    static Connection sqlcon=null;
    static Statement sqlstmt = null;
    static OracleDataSource ods = null;
  public static void main(com.hyperion.essbase.calculator.Context ctx,String args[]) {         
         truncateTable(args[0],args[1],args[2],args[3]); 
    }

   private static void openConnection( String URL, String userid, String passwd) {
                    try {
                                                ods = new OracleDataSource();
                                } catch (SQLException e1) {
                                                System.out.println("New Connection Object creation Failed: " + e1.getMessage());
                                }
                ods.setURL(URL);
                ods.setUser(userid);
                ods.setPassword(passwd);
                                try {
                                                sqlcon = ods.getConnection();
                                                System.out.println("Oracle database connection established to "+ URL);
                                } catch (SQLException e) {
                                                System.out.println("Connection Failed to "+ URL);
                                                System.out.println("SQLException: " + e.getMessage());
                                }              
           
    }
    private static void closeConnection(Connection oraConn){
                if(oraConn != null)
                                {
                                                try
                                                {
                                                                oraConn.close();
                                                }
                                                catch (SQLException x)
                                                {
                                                                System.out.println("SQLException: " + x.getMessage());
                                                }
                                }
    }
    public static void truncateTable(String table, String URL, String userid, String passwd) {
     
                System.out.println("CDF Started");
                openConnection( URL,userid, passwd);
                try {
                                                sqlstmt=sqlcon.createStatement();
                                                sqlstmt.execute("TRUNCATE TABLE "+ table);
                                                System.out.println("Trancated table: "+table);
                                } catch (SQLException e) {
                                                System.out.println("SQLException: " + e.getMessage());
                                }
                closeConnection(sqlcon);
    }
}

If you have everything set up properly then you should be able to save the code above without any error in eclipse. Once saved right click on relationalDDL.java in package explorer to create a run configuration. 


We will configure something like shown below but we will not run it. Just hit apply and close.


Once configuration is saved, right click on relationalDDL.java in package explorer and click Export. Select Runnable JAR file



Provide the path where you want to export in your local machine. Ignore any warning that says main class not found. I named the jar file as DDL.jar. 

copy this jar file to your EPMSystem11R1/products/Essbase/EssbaseServer/java/udf folder.

update udf.policy file in EPMSystem11R1/products/Essbase/EssbaseServer/java
add following lines.

// Grant all permissions to CDF DDL.jar 
grant codeBase "file:${essbase.java.home}/udf/DDL.jar" {
 permission java.security.AllPermission; 
};

Now as this CDF is ready to  run, invoke it from calc script with RUNJAVA command. For any error check Essbase.log.