Pages

Tuesday, September 30, 2008

Generate Edifact message from a xml

The people of DataDirect made a great product called XML convertors with this you can generate an Edifact message from a xml and back. This is not new but this java software can easily embedded in your own java program. In this blog entry I will show how you can generate an edifact message in a few hours and very flexible.

This are the steps to generate an APERAK Edifact message from an Oracle table.
Step 1 generate the xsd from xml convertor for our edifact message.
In this example I want to generate an Aperak Edifact message so I first need a xml schema,then I know how the xml must look like. You have to do this only once.

ConverterFactory factory = new ConverterFactory();
try {
Result xsdResult = new StreamResult("d:/projecten/xml_edi/aperak.xsd");
String uri = "EDI:dialect=EDIFACT:version=D96A:message=APERAK:tbl=no";
SchemaGenerator generator = factory.newSchemaGenerator(uri);
generator.getSchema(xsdResult);
System.out.println("schema generator finished: --> aperak.xsd");
}
catch (Exception e)
{
System.out.println("schema generator failed with exception: " + e);
}

This how the xsd looks like.

Step 2 generate a xml from the oracle database with xmldb
With Oracle xmldb we can generate the following xml from the database.

<message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="db.xsd"
type="APERAK" date="080928" time="1347" runId="1001" test="true">
<records>
<aperak_rff nr="1" message_id="A_200508030000603782" ref_qlfier="AAN" ref_nr="T200508031110N002" message_function="27"/>
<aperak_nad nr="1" message_id="A_200508030000603782" code_ontv_afz="A" party_qlfier="GRP" ean_code_party="8716867999990"/>
<aperak_nad nr="2" message_id="A_200508030000603782" code_ontv_afz="O" party_qlfier="GRU" ean_code_party="8712423009097"/>
<aperak_dtm nr="1" message_id="A_200508030000603782" dt_qualifier="137" dt_period="200508031112" dt_format_qlfier="203"/>
<aperak_dtm nr="2" message_id="A_200508030000603782" dt_qualifier="178" dt_period="200508031113" dt_format_qlfier="203"/>
<aperak_dtm nr="3" message_id="A_200508030000603782" dt_qualifier="735" dt_period="2" dt_format_qlfier="805"/>
<aperak_erc nr="1" message_id="A_200508030000603782" error_code="00023" nr_text="1" volgnummer="505484510" error_text=" (E) "/>
</records>
</message>

You probably noticed I do not have all the required elements in this database xml to generate a complete edifact message. But we will solve this in the next step.
With Altova XMLSPY I will generate a new XSD based on this example xml.

Now we have two xsd's and a database xml, we can use Altova Mapforce to create a xslt which can transform the database xml to the more complex edifact aperak xml.
Step 3 Use Mapforce to create a XSLT.
I will use Altova Mapforce because with this program I can visual make a xslt and immediately see the result of the xlst. We need to import the two xsd's in mapforce and make the mappings between these two xsd's and add the missing data to the edifact aperak schema.

This is the output of the xslt.

<?xml version="1.0" encoding="UTF-8"?>
<EDIFACT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="D:/projecten/xml_edi/aperak.xsd">
<UNB>
<UNB01>
<UNB0101>UNOC</UNB0101>
<UNB0102>3</UNB0102>
</UNB01>
<UNB02>
<UNB0201>8712423009097</UNB0201>
<UNB0202>14</UNB0202>
</UNB02>
<UNB03>
<UNB0301>8716867999990</UNB0301>
<UNB0302>14</UNB0302>
</UNB03>
<UNB04>
<UNB0401>080928</UNB0401>
<UNB0402>1347</UNB0402>
</UNB04>
<UNB05>1001</UNB05>
<UNB11>1</UNB11>
</UNB>
<APERAK>
<UNH>
<UNH01>1001</UNH01>
<UNH02>
<UNH0201>APERAK</UNH0201>
<UNH0202>D</UNH0202>
<UNH0203>96A</UNH0203>
<UNH0204>ZZ</UNH0204>
<UNH0205>EDINE1</UNH0205>
</UNH02>
</UNH>
<BGM>
<BGM01>
<BGM0101>12E</BGM0101>
<BGM0103>9</BGM0103>
</BGM01>
<BGM02>A_200508030000603782</BGM02>
<BGM03>27</BGM03>
</BGM>
<DTM>
<DTM01>
<DTM0101>137</DTM0101>
<DTM0102>200508031112</DTM0102>
<DTM0103>203</DTM0103>
</DTM01>
</DTM>
<DTM>
<DTM01>
<DTM0101>178</DTM0101>
<DTM0102>200508031113</DTM0102>
<DTM0103>203</DTM0103>
</DTM01>
</DTM>
<DTM>
<DTM01>
<DTM0101>735</DTM0101>
<DTM0102>2</DTM0102>
<DTM0103>805</DTM0103>
</DTM01>
</DTM>
<GROUP_1>
<RFF>
<RFF01>
<RFF0101>AAN</RFF0101>
<RFF0102>T200508031110N002</RFF0102>
</RFF01>
</RFF>
</GROUP_1>
<GROUP_2>
<NAD>
<NAD01>GRP</NAD01>
<NAD02>
<NAD0201>8716867999990</NAD0201>
<NAD0203>9</NAD0203>
</NAD02>
</NAD>
<NAD>
<NAD01>GRU</NAD01>
<NAD02>
<NAD0201>8712423009097</NAD0201>
<NAD0203>9</NAD0203>
</NAD02>
</NAD>
</GROUP_2>
<GROUP_3>
<ERC>
<ERC01>
<ERC0101>00023</ERC0101>
<ERC0103>60</ERC0103>
</ERC01>
</ERC>
<FTX>
<FTX01>AAO</FTX01>
<FTX04>
<FTX0401> (E) </FTX0401>
</FTX04>
</FTX>
</GROUP_3>
<UNT>
<UNT02>1001</UNT02>
</UNT>
</APERAK>
<UNZ>
<UNZ01>1</UNZ01>
<UNZ02>1001</UNZ02>
</UNZ>
</EDIFACT>

Now we can generate the database xml and have an xlst to transform the xml to the edifact aparak xml. In the last step we can make the edifact message with the xml convertor software.
Step 4 Generate the edifact message
What really cool is that xml convertor can detect the xml message and transforms it to the right edifact message so you only need this code for all your outgoing edifact messages.

try {
Source converterSource = new StreamSource("d:/projecten/xml_edi/aperak_gen.xml");
Result converterResult = new StreamResult("d:/projecten/xml_edi/aperak.edn3");
Converter fromXml = factory.newConvertFromXML("converter:EDI");
fromXml.convert(converterSource, converterResult);
System.out.println("fromXML finished: aperak.xml -> aperak.edn2");
}
catch (Exception e)
{
System.out.println("fromXML failed with exception: " + e);
}

This is the generated edifact message
UNA:+,? '
UNB+UNOC:3+8712423009097:14+8716867999990:14+080928:1347+1001++++++1'
UNH+1001+APERAK:D:96A:ZZ:EDINE1'
BGM+12E::9+A_200508030000603782+27'
DTM+137:200508031112:203'
DTM+178:200508031113:203'
DTM+735:2:805'
RFF+AAN:T200508031110N002'
NAD+GRP+8716867999990::9'
NAD+GRU+8712423009097::9'
ERC+00023::60'
FTX+AAO+++ (E)'
UNT+11+1001'
UNZ+1+1001'

Conclusion, this is an flexible solution to generate an Edifact message. In your java application you can easily embed the xml convertor software and use the xslt to transform the xml. With this solution you can change your backoffice application or edifact message. You only have to change the xslt which is very easy in Altova Mapforce.

Sunday, September 28, 2008

Flex Ruby performance with XML, JSON and RubyAMF

One of the great things when you use Flex in combination Ruby is that you can easily change the communication method. For instance you can use xml rpc or json rpc or RubyAMF, just by changing the ruby controller. Off course when you use json then you need to download the corelib for Flex and when you use RubyAMF we need to install this plugin in ruby.
But when you have a choice then you need to know what is the best communication method for your project. In this test I will measure the performance of the three methods. For this I made a simple Flex / Ruby project.

Here are the average results in ms (100 times executed) with the total records count in our test table.
records101004001000
amf159213380731
json4575252781
xml4357197612

Conclusion
RubyAMF has a little overhead and is fast with a large recordset and you can use remoteobject in Flex. Xml is fast but the performance can vary. With 1000 records it can take 200ms but sometimes 2000ms. It looks like Ruby can cache xml and sometimes refresh the cache. Json is very stable and fast with small recordsets. If you just want to retrieve some data in Flex, I would use json but when you want to do more with this data in Flex then RubyAMF has many benefits, like association between objects, conversion to actionscript class and a lot more.

The Ruby Code
you can test the output by adding the format parameter to the url http://localhost:3000/departments/find_all?format=json or format=xml.
Here is the controller code I used

class DepartmentsController < ApplicationController

# return all Departments
def find_all
respond_to do |format|
format.amf { render :amf => Department.find(:all) }
format.json { render :text => Department.find(:all).to_json }
format.xml { render :xml => Department.find(:all) }
end
end

end

department table created in a mysql database

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end

RubyAMF configuration

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end


The Flex code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import vo.Timing;

private var idNum:int;
private var maxNum:int = 100;

[Bindable]
private var timing:ArrayCollection = new ArrayCollection();

private function startRubyAMF():void {
timing = new ArrayCollection();
idNum = 0;
loadAll();
}
private function startJSON():void {
timing = new ArrayCollection();
idNum = 0;
loadAllJson();
}
private function startXML():void {
timing = new ArrayCollection();
idNum = 0;
loadAllXML();
}
]]>
</mx:Script>


<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
private var rubyDateFrom:Date;

private function loadAll():void {
rubyDateFrom = new Date();
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyDateFrom;
timing.addItem(time);

}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
departments = event.result as ArrayCollection;
var rubyDateFinish:Number = new Date().valueOf() - rubyDateFrom.valueOf() ;
rubyLabel.text="RubyAMF departments "+rubyDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.rubyamf = rubyDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAll();
} else {
var average:Number = 0;
for ( var i:int = 0 ; i < timing.length ; i++ ) {
var time2:Timing = timing.getItemAt(i) as Timing;
average = average + time2.rubyamf;
}
average = average / timing.length;
rubyLabel.text="RubyAMF departments average "+average.toString()+" ms";

trace("end");
}
}

]]>
</mx:Script>
<mx:HBox>
<mx:Button label="refresh RubyAMF" click="startRubyAMF()"/>
<mx:Button label="refresh JSON" click="startJSON()"/>
<mx:Button label="refresh XML" click="startXML()"/>
</mx:HBox>

<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:Label id="rubyLabel" text="RubyAMF departments"/>

<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp:ArrayCollection = new ArrayCollection() ;
private var rubyJsonDateFrom:Date;

private function loadAllJson():void {
rubyJsonDateFrom = new Date();
var token:AsyncToken = AsyncToken(json.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);
}


private function resultHandlerJSON(event:ResultEvent):void
{
dp = new ArrayCollection();
var rawData:String = String(event.result);
var arr:Array = (JSON.decode(rawData) as Array);
for ( var i:int = 0 ; i < arr.length ; i++ ) {
dp.addItem(arr[i].department);
}
var rubyJsonDateFinish:Number = new Date().valueOf() - rubyJsonDateFrom.valueOf() ;
rubyJSONLabel.text="JSON departments "+rubyJsonDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.json = rubyJsonDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllJson();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.json;
}
average = average / timing.length;
rubyJSONLabel.text="JSON departments average "+average.toString()+" ms";

trace("end");
}


}
]]>
</mx:Script>

<mx:HTTPService id="json"
url="http://localhost:3000/departments/find_all?format=json"
result="resultHandlerJSON(event)" useProxy="false" />
<mx:Label id="rubyJSONLabel" text="JSON departments"/>
<mx:DataGrid id="dg4" dataProvider="{dp}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:Script>
<![CDATA[
import com.adobe.serialization.json.JSON;
[Bindable]
private var dp2:ArrayCollection = new ArrayCollection() ;
private var rubyXmlDateFrom:Date;

private function loadAllXML():void {
rubyXmlDateFrom = new Date();
var token:AsyncToken = AsyncToken(xml.send());
token.kind = idNum.toString();

var time:Timing = new Timing();
time.id = idNum;
time.startDate = rubyJsonDateFrom;
timing.addItem(time);


}

private function resultHandlerXML(event:ResultEvent):void
{
if ( event.result != null ) {
dp2 = event.result.departments.department;
}
var rubyXmlDateFinish:Number = new Date().valueOf() - rubyXmlDateFrom.valueOf() ;
rubyXmlLabel.text="XML departments "+rubyXmlDateFinish.toString()+" ms";

var time:Timing = timing.getItemAt(event.token.kind) as Timing;
time.endDate = new Date();
time.xml = rubyXmlDateFinish;

idNum = idNum +1;
if (idNum < maxNum ) {
loadAllXML();
} else {
var average:Number = 0;
for ( var ii:int = 0 ; ii < timing.length ; ii++ ) {
var time2:Timing = timing.getItemAt(ii) as Timing;
average = average + time2.xml;
}
average = average / timing.length;
rubyXmlLabel.text="XML departments average "+average.toString()+" ms";
trace("end");
}
}
]]>
</mx:Script>


<mx:HTTPService id="xml" url="http://localhost:3000/departments/find_all?format=xml"
result="resultHandlerXML(event)" useProxy="false" />
<mx:Label id="rubyXmlLabel" text="XML departments"/>
<mx:DataGrid id="dg5" dataProvider="{dp2}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

</mx:Application>

Tuesday, September 23, 2008

Google Maps for JSF (GMaps4JSF) in JDeveloper 11G

A new version of GMaps4JSF was released. GMaps4JSF is a Google maps jsf component. This blog entry will show you how you can use it in your own 11g application.
In this example you can hide and show markers. Add markers from a backing bean and add an click event on these markers so when you click on a marker you will get an alert.

The first step to get this jsf component in JDeveloper 11g is to download the GMaps4JSF jar and import this jsp tag library in your own project.

You need your own api key. Use this url http://code.google.com/apis/maps/signup.html to get the api key
Now we are ready to use it. Change my api key in the javascript and use your own.
GMaps4JSF is a good start but it is not ready yet. You still need to write your own javascript and you can not click on a marker to see a description.For this you need to add an eventhandler and use javascript to fire something.

Here is the code for the JSF page. Very important that the method of the clientlistener starts with renderMap and add the id of the maps components to it else it won't work and the ADF splashscreen will not go away.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:yui="http://code.google.com/p/gmaps4jsf/">
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<af:document title="test" id="document22">
<af:clientListener method="renderMapmapPrincipal();" type="load"/>
<f:facet name="metaContainer">
<f:verbatim>
<![CDATA[
<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAM7FSGSscPTbXiOt1No2LPRSLP72-OZgzlwHUle6cA--KWDlXYxSMtxkbiwjRJ9xjiVAYHIVo1d0VkA">
</script>
]]>
</f:verbatim>
</f:facet>
<af:form id="form22">
<af:panelGroupLayout>
<f:verbatim>
<![CDATA[
<script type="text/javascript">
function hideMarker() {
office1.hide();
office2.hide();
}

function showMarker() {
office1.show();
office2.show();
}

function marker1ClickHandler() {
alert("You clicked on the headoffice");
}

function marker2ClickHandler() {
alert("You clicked on a office");
}

</script>
]]>
</f:verbatim>
<af:panelHeader text="Ordina Offices with GMaps4JSF">
<af:panelSplitter inlineStyle="width:750px; height:550px;" splitterPosition="110">
<f:facet name="first">
<af:panelGroupLayout layout="vertical"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<f:verbatim>
<input type="button" value="Hide Offices"
onclick="hideMarker();"/>
<input type="button" value="Show Offices"
onclick="showMarker();"/>
</f:verbatim>
<af:panelGroupLayout/>
</af:panelGroupLayout>
</f:facet>
<f:facet name="second">
<yui:map id="mapPrincipal" width="500px" height="500px" zoom="7"
binding="#{GoogleBean.map}">
<yui:mapControl name="GLargeMapControl"
position="G_ANCHOR_BOTTOM_RIGHT"/>
<yui:mapControl name="GMapTypeControl"/>
</yui:map>
</f:facet>
</af:panelSplitter>
</af:panelHeader>
</af:panelGroupLayout>
</af:form>
</af:document>
</f:view>
</jsp:root>

The backing bean I used

package nl.ordina.google.backing;

import com.googlecode.gmaps4jsf.component.map.Map;
import com.googlecode.gmaps4jsf.component.marker.Marker;
import com.googlecode.gmaps4jsf.component.eventlistener.EventListener;

public class GoogleBean {
private Map map;

public void setMap(Map map) {
this.map = map;
map.setLatitude("52.05");
map.setLongitude("5.11");


Marker mark = new Marker();
mark.setLatitude("52.05");
mark.setLongitude("5.11");
mark.setJsVariable("office1");
mark.setId("mark1");
EventListener event = new EventListener();
event.setEventName("click");
event.setJsFunction("marker1ClickHandler");
mark.getChildren().add(event);
map.getChildren().add(mark);

Marker mark2 = new Marker();
mark2.setLatitude("53.19");
mark2.setLongitude("6.53");
mark2.setJsVariable("office2");
mark.setId("mark2");
EventListener event2 = new EventListener();
event2.setEventName("click");
event2.setJsFunction("marker2ClickHandler");
mark2.getChildren().add(event2);
map.getChildren().add(mark2);

}

public Map getMap() {
return map;
}

}

faces-config.xml

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<application>
<default-render-kit-id>oracle.adf.rich</default-render-kit-id>
</application>
<managed-bean>
<managed-bean-name>GoogleBean</managed-bean-name>
<managed-bean-class>nl.ordina.google.backing.GoogleBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>

Monday, September 22, 2008

JDeveloper 11G boxer release will available on the 1st of October

Today Ted Farrell annouced that JDeveloper 11G ( codename boxer ) will be released on the 1st of october. This release will contain the ADF JSF RichFaces components and the new TaskFlows. These 11g components are certified to run on Weblogic 10.3 . Next year Oracle will release the bulldog version with the WebCenter and Soa components.
For more info read Lucas blog entry http://technology.amis.nl/blog/?p=3478

Tuesday, September 16, 2008

Building a MySQL Cluster

I am now building a Mail Handling System for a customer and one of the customer non functional requirement is high availibility. So I made a MySQL cluster. Now I can run at the same time two instances of this Mail Handling System in different sites. Now one site can fail and the other site can still work on. The MySQL cluster software make sure that the data is always stored, available and in sync on two nodes. Off course you can use Oracle Data guard or RAC but this is much easier and a lot cheaper.
I was suprised how easy it is to make a cluster. This blog entry will show you the steps.
I will use three x86 sun solaris 10 servers. Two servers are the storage nodes and one is the management node (The load of the management node is very low so you can easily add this to a existing server).

First I downloaded the MySQL 6.2.15 cluster edition from
http://dev.mysql.com/downloads/cluster/index.html#solaris_tar in my case the solaris software.

I don't use the default mysql folders. This are my mysql folder locations.
mysql folder /mysql/mysql
mysql data folder /mysql/mysql/data
mysql cluster folder /mysql/cluster

And I changed the default mysql parameters because we need to support big xml files.

Just change it or remove these parameters.

First step, installation of the mysql software.

Storage node ac-mhs20 (193.176.63.50)
mysql-ndb-1# mkdir /mysql
mysql-ndb-1# cd /mysql
mysql-ndb-1# put the mysql software in the /mysql folder
mysql-ndb-1# gunzip *.gz
mysql-ndb-1# tar xvf *.tar
mysql-ndb-1# rm *.tar
mysql-ndb-1# ln –s mysql-cluster-gpl-6.2.15-solaris10-i386 mysql
mysql-ndb-1# vi /etc/profile add PATH=$PATH:/mysql/mysql/bin

Let's make the mysql configuration file
mysql-ndb-1# vi /etc/my.cnf

[mysqld]
basedir=/mysql/mysql
datadir=/mysql/mysql/data
max_allowed_packet=64M
key_buffer_size=192M
table_cache=512
sort_buffer_size=12M
read_buffer_size=8M
wait_timeout=172800
interactive=172800


mysql-ndb-1# groupadd mysql
mysql-ndb-1# useradd -g mysql mysql
mysql-ndb-1# cd mysql ( /mysql/mysql )
mysql-ndb-1# scripts/mysql_install_db --user=mysql
mysql-ndb-1# chown -R root .
mysql-ndb-1# chown -R mysql data
mysql-ndb-1# chgrp -R mysql .
mysql-ndb-1# cp support-files/mysql.server /etc/init.d/mysql.server

change the mysqld_safe file located in /mysql/mysql/bin

first change
if test -f ./share/mysql/english/errmsg.sys -a -x ./bin/mysqld
to
if test -f ./share/english/errmsg.sys -a -x ./bin/mysqld

second change
elif test -f ./share/mysql/english/errmsg.sys -a -x ./libexec/mysqld
to
elif test -f ./share/english/errmsg.sys -a -x ./libexec/mysqld

Do the same steps for storage node two
Storage node ac-mhs21 (193.176.63.51)

Now we can put the mysql management software to the management server
Management server ac-mhs22 (193.176.63.52)

mysql-ndb-mgt # mkdir /mysql
mysql-ndb-mgt # cd /mysql
mysql-ndb-mgt # mkdir cluster
mysql-ndb-mgt # cd cluster
mysql-ndb-mgt # ftp or rcp /mysql/mysql/bin/ndb_mgm and ndb_mgmd from storage node A or B to /mysql/cluster folder of the management server
mysql-ndb-mgt # chmod u+x ndb_


Step 2 Add the cluster configuration

Management server ac-mhs22 (193.176.63.52)
Create a new file called config.ini and put this in the /mysql/cluster folder

[NDBD DEFAULT]
NoOfReplicas=2
DataDir=/mysql/cluster
DataMemory=80M
IndexMemory=18M

[MYSQLD DEFAULT]
[NDB_MGMD DEFAULT]
[TCP DEFAULT]

# Management Server
[NDB_MGMD]
id=1
HostName=193.176.63.52 # IP address of this server

# Storage Nodes
[NDBD]
id=2
HostName=193.176.63.50 # IP address of storage-node-1
DataDir= /mysql/cluster

[NDBD]
id=3
HostName=193.176.63.51 # IP address of storage-node-2
DataDir=/mysql/cluster

[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]

Storage node ac-mhs20 (193.176.63.50)
mysql-ndb-1# vi /etc/my.cnf and add this to it

ndbclusterndb-connectstring='host=193.176.63.52' # IP address of the management server
default-table-type=NDBCLUSTER
[mysql_cluster]ndb-connectstring='host=193.176.63.52' # IP address of the management


Storage node ac-mhs21 (193.176.63.51)
mysql-ndb-2# vi /etc/my.cnf and add this to it

ndbclusterndb-connectstring='host=193.176.63.52' # IP address of the management server
default-table-type=NDBCLUSTER
[mysql_cluster]ndb-connectstring='host=193.176.63.52' # IP address of the management



Step 3 Let's start the cluster

Management server ac-mhs22 (193.176.63.52)
mysql-ndb-mgt # cd /mysql/cluster
mysql-ndb-mgt # ./ndb_mgmd

Storage node ac-mhs20 (193.176.63.50)
mysql-ndb-1# cd /mysql/cluster
mysql-ndb-1# /mysql/mysql/bin/ndbd --initial
mysql-ndb-1# /etc/init.d/mysql.server start

Storage node ac-mhs21 (193.176.63.51)
mysql-ndb-2# cd /mysql/cluster
mysql-ndb-2# /mysql/mysql/bin/ndbd --initial
mysql-ndb-2# /etc/init.d/mysql.server start

Step 4 Check the cluster status

Management server ac-mhs22 (193.176.63.52)
Start the management console

mysql-ndb-mgt # cd /mysql/cluster
mysql-ndb-mgt # ndb_mgm
ndb_mgm> show

Step 5 Create a new database

Storage node ac-mhs20 (193.176.63.50)

mysql-ndb-1# mysql -u root
mysql-ndb-1# create database foo;
mysql-ndb-1# use foo;
mysql-ndb-1# create table test1 ( i int );
mysql-ndb-1# insert into test1 () values (1);

Storage node ac-mhs21 (193.176.63.51)

mysql-ndb-2# mysql -u root
mysql-ndb-2# use foo;
mysql-ndb-2# select * from test1;


That's all the cluster is working

For more info check this great white paper. http://www.lod.com/whitepapers/mysql-cluster-howto.html

Thursday, September 11, 2008

JDeveloper 11g is Coming

Today I heard some rumours that JDeveloper 11G ADF edition will be released this month. This is very great news. I thought that it will take at least another 6 months before Oracle will release JDeveloper 11G.
I saw today that a JDeveloper 11g ADF application works perfectly with BEA as the embedded application server in JDeveloper 11g. I must say the execution of a webapp in JDeveloper has changed a little bit. In OC4J you can run the webapp without a deployment. With BEA as embedded AS, JDeveloper has to create a war and ear file first and then these files are deployed to the BEA application server ( You have to start BEA first).

Tuesday, September 9, 2008

Using Exadel Fiji for Flex in JDeveloper 11g

With Exadel Fiji you can use Flex in a JSF page. For more infomations see my previous blog. This blog will show you how you use fiji & flex in JDeveloper 11g. This will not work in jdeveloper 10.1.3.
The first step is to download Fiji from http://exadel.com. To make this work in JDeveloper, we need to disable the trinidad code and configure Exadel Richfaces and Ajax4jsf. We need to add the fiji taglib to the project. To do this, Go to the project options / jsp tag libraries and press Add, click user and press the New button. Now we can select the fiji-ui-1.0.0.jar. We need to remove all other taglibs except jsf-core and jsf-html.

Now we can add the right libraries to the project. We can use the following libraries of JDeveloper 11G Facelets runtime, commons-logging, common-digester and common-beanutils. The rest of libraries comes from the fiji lib folder. See the picture for all the used fiji libs. Don't use the commons-digester of JDeveloper. Use version 1.8 of fiji download.


This is how the web.xml look like. You can better make this file readonly else JDeveloper 11g will try to add the Trinidad filters.

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
<description>Empty web.xml file for Web Application</description>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<filter>
<display-name>Ajax4jsf Filter</display-name>
<filter-name>ajax4jsf</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
<init-param>
<param-name>createTempFiles</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>maxRequestSize</param-name>
<param-value>100000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ajax4jsf</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>35</session-timeout>
</session-config>
</web-app>


We had to remove the oracle default render kit from the faces-config.xml and add FaceletViewHandler for Ajax4jsf. Make this file readonly else JDeveloper will try to add the default render kit.

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<managed-bean>
<managed-bean-name>Test</managed-bean-name>
<managed-bean-class>nl.ordina.fiji.backing.TestBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>


The backing bean code with the retrieveWelcomeText method which I use in the remote object (Flex).

package nl.ordina.fiji.backing;
import java.util.Date;

public class TestBean {
public TestBean() {
}

private String welcomeText = "Hello Flex in JSF";


public void setWelcomeText(String welcomeText) {
this.welcomeText = welcomeText;
}

public String getWelcomeText() {
return welcomeText;
}

public String retrieveWelcomeText(Object obj) {
this.welcomeText = "Hello Flex in JSF" + new Date().toString();
return this.welcomeText;
}

}


jsf xhtml page with the fiji:swf component. This element has two sub elements the first is a simple param component which pass its values to flex as a parameter. The second component is a endpoint and this is used by the remoteobject.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:fiji="http://exadel.com/fiji">

<ui:composition>
<p>
<h:form></h:form>
<fiji:swf src="/Flex_fiji.swf" id="simpleFlex"
bgcolor="#FFFFFF" width="450" height="250" >
<f:param name="welcomeText" value="#{Test.welcomeText}" />
<fiji:endpoint name="endpoint2" binary="true" service="#{Test.retrieveWelcomeText}"/>
</fiji:swf>
</p>
</ui:composition>
</html>


Here is the flex code where we use Application.application.parameters to retrieve the parameter value or a reference url for the remoteobject.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="onCreationComplete();">

<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;

private function refresh():void {
myService.getWelcomeObject();
}

public function handleResult(event:ResultEvent):void {
welcome.text = event.result as String;
}

public function onCreationComplete():void{
Alert.show(Application.application.parameters.endpoint2);
myService.endpoint = Application.application.parameters.endpoint2;
}
]]>
</mx:Script>
<mx:RemoteObject id="myService" destination="bean"
result="handleResult(event)" />
<mx:Form x="22" y="10" width="280">
<mx:TextInput id="welcome" styleName="text"
text="{Application.application.parameters.welcomeText}" />
<mx:Button label="Refresh"
click="refresh()"/>
</mx:Form>
</mx:Application>

That's all. Here you can download the JDeveloper 11g project.

Saturday, September 6, 2008

Use Flex in JSF with Exadel Fiji

Maybe you already saw this news at the serverside or by James Ward but Exadel just released Fiji. With Fiji you can use Flex applications in a JSF page and interact with the other JSF component or backing beans. I already tried this too but Exadel got it working with a lot of great options. You can develop now better looking and richer JSF applications with cool video or charts components. These flex applications can be a part of the jsf application without using blazeds or lifecycle. And of course you can use Flex to build complex parts of your jsf page ( like Charts, Drag and Drop, Video or Trees) and you can do it a lot faster and it is easier.

Here can you see the exadel jsf flex demos or read more about Fiji at the Exadel product page .

Fiji options
1) use f:param to pass values to the flex application
2) use HTTPService to retrieve any value of a backing bean method.
3) use DataService to retrieve the result using AMF format.
4) Invoking Ajax Request to send events to other parts of the jsf page.
5) Access to the flex api from the jsf page
6) New Flex Charts components in JSF without making a flex application

A little example
You can pass parameters to the flex application. Just use f:param and el

<fiji:swf src="/simpleHello/simpleHello.swf" id="simpleHello"
bgcolor="#FFFFFF" width="320" height="180">
<f:param name="userName" value="#{bean.simpleHelloUserName}" />
</fiji:swf>

And retrieve the parameters in Flex just use Application.application.parameters.userName

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Form x="22" y="10" width="280">
<mx:Label id="userName" styleName="text" text="{Application.application.parameters.userName}" />
</mx:Form>
</mx:Application>

Wednesday, September 3, 2008

Google Chrome with ADF 10G & 11G, Applet and Flash

Google released their new browser called Chrome, which is off course still beta ( that is normal for google). This browser not only looks fancy but it is very nice to browse the web. Andrej Koelewijn already talked about the internal memory page. Let's try some sites to know what the normal memory usage is. To do this you can open the taskmanager

This gives you already a nice overview.

or you can type about:memory in the url input. This gives you more memory info. So let's test some websites ( like cnn.com and my own blog), an ADF 10.1.3 , ADF 11g application, an applet and a flash application. Here are the results.

A normal website ( Tab 2 and 5 ) use about 26 MB of memory, the most complex 10g ADF page ( Tab 9) use 22MB and ADF 11g website gives me a not supported notice. Chrome also shows flash as a different memory process. A big flex application can take 50MB of memory and a small one 22MB.

Too bad I can't not run a java applet with the current installed jre. I need to install Java SE 6 Update 10 RC beta. You can download this at http://java.sun.com.
A simple Applet takes about 29MB of memory.

Happy browsing.

Monday, September 1, 2008

Associations with Ruby on Rails, Flex and RubyAMF

In this blog item I will you show how easy it is with RoR and RubyAMF to do CRUD operations on tables with associations. RoR and RubyAMF makes it very easy. I used for this blog a simple department and their employees use case.
Here is an picture of the result. A department has employees and an employee belongs to a department

First step is to create the RoR application
rails hr_app

change directory to the new application
cd hr_app

We need to edit the database configuration file
hr_app\config\database.yml

Install RubyAMF
ruby script/plugin install http://rubyamf.googlecode.com/svn/tags/current/rubyamf

Generate the department and employee table
ruby script/generate rubyamf_scaffold department
ruby script/generate rubyamf_scaffold employee


change department table configuration in hr_app/db/migrate folder

class CreateDepartments < ActiveRecord::Migration

def self.up
create_table :departments do |t|
t.string :name
t.string :location
t.timestamps
end
end

def self.down
drop_table :departments
end
end


change employee table also located in the hr_app/db/migrate folder

class CreateEmployees < ActiveRecord::Migration

def self.up
create_table :employees do |t|
t.integer :department_id
t.string :first_name
t.string :last_name
t.string :job
t.timestamps
end
end

def self.down
drop_table :employees
end
end

Let's create the department and employee table
rake db:migrate

We can add one to many associations in RoR
Edit hr_app/app/models/department.rb file where we will add has_many

class Department < ActiveRecord::Base
has_many :employees
end

Edit hr_app/app/models/employee.rb file where we will add belongs_to

class Employee < ActiveRecord::Base
belongs_to :department
end

The RoR configuration is ready we only have to configure RubyAMF

We can generate the class mappings and copy this to the rubyamf_config.rb configuration
ruby script/generate rubyamf_mappings

The generator detects the associations between department and employees
Copy the generated block of text into config/rubyamf_config.rb:
edit hr_app/config/rubyamf_config.rb

require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false

ClassMappings.register(
:actionscript => 'Department',
:ruby => 'Department',
:type => 'active_record',
:associations => ["employees"],
:attributes => ["id", "name", "location", "created_at", "updated_at"])

ClassMappings.register(
:actionscript => 'Employee',
:ruby => 'Employee',
:type => 'active_record',
:associations => ["department"],
:attributes => ["id", "first_name", "last_name", "job", "created_at", "updated_at", "department_id"])

ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end

Now we can start the RoR application
ruby script/server

Create a new Flex application.
First we will create the employee and department actionscript class.
The employee field in the Department class is an arraycollection

package vo
{
import mx.collections.ArrayCollection;


[RemoteClass(alias="Department")]
[Bindable]
public class Department
{
public var id:int;
public var name:String;
public var location:String;
public var createdAt:Date;
public var updatedAt:Date;
public var employees:ArrayCollection;
public function Department()
{
}

}
}

In the Employee class we add the department field of type Department

package vo
{
[RemoteClass(alias="Employee")]
[Bindable]
public class Employee
{
public var id:int;
public var firstName:String;
public var lastName:String;
public var job:String;
public var departmentId:int;
public var createdAt:Date;
public var updatedAt:Date;
public var department:Department;
public function Employee()
{
}
}
}

Here is the mxml code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="loadAll(); loadAll2();">

<mx:Script>
<![CDATA[
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import vo.Department;
import vo.Employee;
import mx.collections.ArrayCollection;

[Bindable]
private var departments:ArrayCollection = new ArrayCollection();
[Bindable]
private var employees:ArrayCollection = new ArrayCollection();


private function loadAll():void {
var token:AsyncToken = AsyncToken(departmentService.find_all());
token.kind = "fill";
}
private function loadAll2():void {
var token:AsyncToken = AsyncToken(employeeService.find_all());
token.kind = "fill";
}

private function createDept():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;
var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function createDeptWithEmp():void {
var dept:Department = new Department();
dept.name = "Headquarters";
dept.location = "Putten";
dept.employees = new ArrayCollection;

var emp:Employee = new Employee;
emp.firstName = "pipo";
emp.lastName = "pipo";
emp.job = "clown"
dept.employees.addItem(emp);

var token:AsyncToken = AsyncToken(departmentService.save(dept));
token.kind = "create";
}

private function destroy():void {
var token:AsyncToken = AsyncToken(departmentService.destroy(dg.selectedItem.id));
token.kind = "delete";
}

private function faultHandler(event:FaultEvent):void {
Alert.show(event.fault.faultString + " : " + event.fault.faultCode + " : " + event.fault.faultDetail , "Error in LoginCommand");
}

private function resultHandler(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
departments = event.result as ArrayCollection;
} else {
loadAll();
loadAll2();
}
}
private function resultHandler2(event:ResultEvent):void {
if ( event.token.kind == "fill" ) {
employees = event.result as ArrayCollection;
} else {
loadAll2();
}
}
]]>
</mx:Script>


<mx:RemoteObject id="departmentService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="DepartmentsController"
showBusyCursor="true"
result="resultHandler(event)"
fault="faultHandler(event)"
/>

<mx:RemoteObject id="employeeService" destination="rubyamf"
endpoint="http://localhost:3000/rubyamf_gateway/"
source="EmployeesController"
showBusyCursor="true"
result="resultHandler2(event)"
fault="faultHandler(event)" />

<mx:HBox>
<mx:VBox>
<mx:Label text="Departments with employees"/>
<mx:HBox>
<mx:Button label="Create Department" click="createDept()"/>
<mx:Button label="Create Department with Employee" click="createDeptWithEmp()"/>
<mx:Button label="Remove Department" click="destroy()"/>
</mx:HBox>
<mx:DataGrid id="dg" dataProvider="{departments}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="location" headerText="Location"/>
</mx:columns>
</mx:DataGrid>

<mx:DataGrid id="dg_2" dataProvider="{dg.selectedItem.employees}">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
</mx:columns>
</mx:DataGrid>

</mx:VBox>
<mx:VBox>

<mx:Label text="Employees"/>

<mx:DataGrid id="dg2" dataProvider="{employees}" variableRowHeight="true">
<mx:columns>
<mx:DataGridColumn dataField="id" headerText="Key"/>
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
<mx:DataGridColumn dataField="department">
<mx:itemRenderer>
<mx:Component>
<mx:VBox>
<mx:Text text="{data.department.name}"/>
<mx:Text text="{data.department.location}"/>
</mx:VBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:VBox>

</mx:HBox>
</mx:Application>

That's all. you can easily create and retrieve departments with it's employees with RoR and RubyAMF. RoR and RubyAMF do all the hard work.