Monday, March 16, 2009

Integrating SAP NetWeaver in Zero PHP

I've been playing with the Big Daddy of enterprise systems, SAP

I thought it would be interesting to hook up a Zero PHP application with a SAP NetWeaver system. So last weekend I summoned up the courage and, armed with a bottle of red wine, set about the task. Surprisingly enough it wasn't too difficult (setting aside finding 20GB of disk space needed by the installer).

The bit I was interested in was the SAP Java connector. SAP Java Connector (SAP JCo) is a middleware component that enables the development of SAP-compatible components and applications in Java. Since we have a PHP/Java Bridge in Zero I thought it should be possible to wire them up.

Here is an example that invokes a SAP remote function call (RFC):

<?php
try {

java_import("com.sap.mw.jco.JCO", NULL, FALSE, NULL, FALSE);
// Establish the client connection to SAP
  $connection = JCO::createClient("000", "bcuser", 
  "minisap", null, "localhost", "00");
$connection->connect();
$repository = JCO::createRepository("ZeroRepository", $connection);
  $template = $repository->getFunctionTemplate("RFC_SYSTEM_INFO");
  $function = new Java("com.sap.mw.jco.JCO.Function", $template);
$connection->execute($function);
$list = $function->getExportParameterList();
  $structure = $list->getStructure("RFCSI_EXPORT");

$iterator = $structure->fields();
  while ($iterator->hasMoreElements()) {
  $field = $iterator->nextField();
  echo $field->getName()." : ".$field->getString()."\n";
}
$connection->disconnect();

} catch (Exception $exception) {
echo $exception->getMessage();
}
?>

The code turns out to be pretty straightforward. In a production environment it would be better to use some of the connection pooling options to avoid the repetitive connect/disconnect overhead. The function RFC_SYSTEM_INFO doesn't require any arguments but those can be passed as well if required.

If anyone is interested in SAP there is an un-conference event being organised by Nigel James and Zoe Slattery on Saturday 4th April. More details here (beware the SAP/SDN sites do not work in Google Chrome). I am planning to be there to chat about Zero/Java/PHP with SAP NetWeaver.

Friday, March 6, 2009

developerWorks Article: Business Intelligence

The developerWorks article on business intelligence I mentioned a while back has been published. It covers several use cases for integrating BIRT reports into a Zero PHP application. It uses the sample database and reports provided by BIRT and runs against a MySQL database.

From my perspective the most interesting example is the last one: scripted data sources. In this use case the report doesn't get its data from a database query. Instead the PHP script creates the data and pushes it into the BIRT runtime ready for the report to render. Here is the sample code:

<?php
require_once 'startup.php';
$engine = startup();
$root_directory = zget("/config/root");
$report_engine = $root_directory."/ReportEngine";
$design_file = $root_directory.
    "/samples/data_source.rptdesign";
$images_directory = $root_directory.
    "/public/images/logos";

java_import(
    "org.eclipse.birt.report.engine.api.HTMLRenderOption");
java_import(
    "org.eclipse.birt.report.engine.api.HTMLServerImageHandler");

$design = $engine->openReportDesign($design_file);
$task = $engine->createRunAndRenderTask($design);
$task->validateParameters();
$generated_report_file = 
    tempnam(sys_get_temp_dir(), 'report');

$models[] = array("ANG Resellers", 
"1952 Alpine Renault 1300", "Red");
$models[] = array("AV Stores, Co.", 
"1969 Harley Davidson Ultimate Chopper", "Blue");
$models[] = array("Alpha Cognac", 
"1969 Ford Falcon", "Blue");
$models[] = array("Asian Shopping Network, Co", 
"1969 Dodge Charger", "Plum Crazy Purple");
$models[] = array("Asian Treasures, Inc.", 
"1969 Corvair Monza", "Red");

zput("/request/report", $models);

$options = new HTMLRenderOption();
$options->setOutputFileName(
    $generated_report_file);
$options->setOutputFormat("html");
$options->setImageDirectory($images_directory);
$options->setBaseImageURL("/images/logos/");
$options->setImageHandler(new HTMLServerImageHandler());

$task->setRenderOption($options);
$task->run();
$task->close();

echo file_get_contents($generated_report_file);
unlink($generated_report_file);
?>

The bold section hard codes some data for the report. A more realistic example might query an external system like a web service to build the data set. 

The magic that makes this possible is the JavaScript engine that BIRT embeds. When BIRT renders the report it executes JavaScript embedded in the report with the Rhino scripting engine. Because the BIRT runtime and the PHP application are all running on the same JVM they can share data very easily and efficiently. In this case the PHP code puts the data collection into the Global Context, and the JavaScript in the report pulls it out.


Tuesday, March 3, 2009

Business Intelligence - The Code

A little later than intended - here is the code we were using on the Zero stand at the (most excellent) PHP London conference last Friday. I had it running as a blog in Drupal but this will run just as well standalone. More detailed destructions for getting this running are up on YouTube here (the video is six minutes long).

<?php
require_once 'startup.php';
$engine = startup();

$root_directory = zget("/config/root");
$design_file = $root_directory."/samples/hello_world.rptdesign";
$design = $engine->openReportDesign($design_file);
$task = $engine->createRunAndRenderTask($design);
$task->validateParameters();
java_import("org.eclipse.birt.report.engine.api.HTMLRenderOption");
$generated_report_file = tempnam(sys_get_temp_dir(), 'report');

$options = new HTMLRenderOption();
$options->setOutputFileName($generated_report_file);
$options->setOutputFormat("html");
$task->setRenderOption($options);
$task->run();
$task->close();

echo file_get_contents($generated_report_file);
unlink($generated_report_file);
?>

And the include file that configures the BIRT runtime is as follows:

<?php
function startup() {
  zpost("/app#lock", "/engine"); 
  $engine = zget("/tmp/factory");
  if ($engine != NULL) {
zpost("/app#unlock", "/engine"); 
return $engine; // Did someone else get in first?
  }

  $root_directory = zget("/config/root");
$report_directory = $root_directory."/birt-runtime-2_3_1/ReportEngine";
  $log_directory = zget("/config/appLogDir");

java_import("org.eclipse.birt.core.framework.Platform");
  java_import("java.util.logging.Level");
  java_import("org.eclipse.birt.report.engine.api.EngineConfig");
  java_import("org.eclipse.birt.report.engine.api.IReportEngineFactory");

  $config = new EngineConfig();
  $config->setBIRTHome($report_directory);
  $config->setLogConfig($log_directory, Level::$SEVERE);
  Platform::startup($config);

$factory = Platform::createFactoryObject(
  IReportEngineFactory::$EXTENSION_REPORT_ENGINE_FACTORY);

  // Store in process wide non persistent zone
$engine = $factory->createReportEngine($config);
  zput("/tmp/factory", $engine);

  // Unlock when we are finished!
  zpost("/app#unlock", "/engine"); 

return $engine;
}
?>