katratxo on Software Development

tail -f /var/log/brain | grep -i software

Posts Tagged ‘tips

Development tips – Part III

with one comment

FreeMarker syntax highlighting

As you may know, we are using Freemarker as templating engine to produce code. By default, you don’t have syntax highlighting in Eclipse, but you can fix this limitation installing JBoss Tools.

Since we are using Eclipse Indigo, you need use the Development Release

.

FreeMarker IDE plugin

After installing the plugin and restarting Eclipse, you’ll have some colored syntax when opening a .ftl file

ob-view-form.js.ftl

Identifying field names

A very common question is how to identify the name of the INPUT that is used when sending requests to the server. In 2.50 was quite easy, you just had to check the generated HTML code and search for an input tag and check the name attribute. This name is that one you use as request parameter in your servlet (e.g. a Callout).

In Openbravo 3, the code is generated in the client side by SmartClient, and most of the time you don’t have control over it. However you can use Elements tab in Chrome Dev Tools to check the resulting code.

Business Partner field name

You’ll find that wrapping the actual INPUT there are several DIVs and TDs. This wrapping code contains references to JavaScript variables you can use to access some properties, e.g. In the image above, the Business Partner selector has refereces to isc_OBSelectorItem_1.

If you use the console, you can access the JavaScript object and some of the properties:

isc_OBSelectorItem_1.name
"businessPartner"
isc_OBSelectorItem_1.inpColumnName
"inpcBpartnerId"

For backward compatibility, each field has a inpColumnName that can be used as request parameter in your servlet.

Choosing the proper Java package name

This section of the article goes mainly to Openbravo core developers. It’s important to know that that org.openbravo.* is reserved for Openbravo developments.

I started a sample module and picked the Java package, org.openbravo.test.security, after that I registered a ComponentProvider and some Component generating some JavaScript response. When I tried the code it didn’t work. After more than one hour trying to figure out what’s going on, I read the following:

Weld will analyze the classpath to find components which have specific annotations. To prevent searching all classes specific exclusion filters have been specified. They can be found in the META-INF/beans.xml file in the Weld module.

Bingo! To avoid trying to analyze the JUnit test cases in the org.openbravo.test package, we exclude any package that contains org.openbravo.**.test.** pattern.

Removing the test part of my test module Java package solved the problem.

Advertisements

Written by katratxo

September 6, 2011 at 9:50 am

Posted in Openbravo

Tagged with , , ,

Finding and fixing memory leaks in a SmartClient based application

leave a comment »

Finding memory leaks in a Web Application can be difficult, fortunately the tools for Web Development are getting better and better. You can read the post Finding memory leaks by Tony Gentilcore, where he explains how you can use Chrome Dev Tools Timeline and Heap Profiler to diagnose if your application is leaking memory.

In the case of a SmartClient application, you should know that when you create a new object, instance of a Class, the library pollutes the global namespace (window) with a global variable using the pattern: isc_TypeOfClass_Index e.g. If you execute the following code, you’ll end up with a new global variable isc_VLayout_0

var l = isc.VLayout.create({width: '100%', height: '100%'});

When you destroy an object, the global variable gets nullified:

l.destroy();
isc_VLayout_0 === null // true - still exists but its value is null

Taking this as a base, you could identify if your SmartClient application is leaking memory with the following procedure:

  • Take a snapshot of the current state of the global namespace
  • Perform the action you think causes the leak
  • Take a second snapshot of the global namespace and compare them
  • Every not null entry in the second snapshot that is not present in the first one, is a leak

Keep this strategy in mind.

Although most of the cases SmartClient handles objects lifecycle automatically, there are cases in which you need to destroy objects manually. Let’s take a look when this is required.

SmartClient handles child objects automatically

If you check the documentation, SmartClient has the concept of AutoChild that are subcomponents part of of a main object that gets automatically managed.

An example is the Window component and its subcomponent the “header” …
By default any auto-children created by canvas.addAutoChild or
canvas.createAutoChild will be destroyed when the canvas that created them is destroyed

Your child objects will be automatically destroyed if SmartClient knows about them, e.g. Using

Some code:

var l = isc.Window.create({
  width: '600',
  height: '400',
  items: [
    isc.Label.create({
      contents: 'Hello World'
    })
  ]
});

When you execute this code, you’ll have a lot of new global variables:

isc_Label_0
isc_Window_0
isc_EdgedCanvas_0
isc_Window_0_shadow
isc_Window_0_header
isc_Window_0_headerBackground
isc_Window_0_headerIcon
isc_Canvas_0
isc_Window_0_headerLabel
isc_Window_0_minimizeButton
isc_Window_0_closeButton
isc_Window_0_body

If you call the destroy method of the Window, the isc_Window_0 variable remains in the global namespace but with null value. The rest of variables are deleted.

isc_Window_0 === null // true

SmartClient doens’t handle objects that are not AutoChild

When dealing with complex composed widgets and if those subcomponets are not added using the AutoChild pattern, is quite easy to create memory leaks. An Openbravo example of this is the SelectorItem, a widget commonly used (Business Partner, Product selectors). This widget is a composite of a ComboBoxItem, a magnifier image and a ListGrid embeded in a Window that, is hidden by default, and shown when clicking the magnifier icon.

When destroying a SelectorItem you need to manually take care of destroying the associated objects.

SmartClient doesn’t destroy the dataSource object associated to a DataBound component

Another case is when SmartClient doesn’t destroy the associated dataSource object of a ListGrid.
This case is also present in the SelectorItem, since the selector Window contains a ListGrid with an associated DataSource.

The example below creates a new DataSource and a ListGrid bounded to it. The ListGrid is then added as item of a Window which is a member of a Layout that also contains a destroy Button. When the user clicks the button, the expected result is that all objects get destroyed in cascade.

As you can see the dataSource requires to get destroyed explicitly.

isc.DataSource.create({
  ID: "countryDS", // manually defining the global ID
  fields:[
    {name:"countryCode", title:"Code"},
    {name:"countryName", title:"Country"},
    {name:"capital", title:"Capital"}
  ],
  clientOnly: true,
  testData: countryData // sample data previously defined
});

isc.ListGrid.create({
  ID: "countryList",
  width: '100%', 
  height: '100%', 
  alternateRecordStyles:true, 
  showAllRecords:true,
  dataSource: countryDS,
  autoFetchData: true,
  destroy: function () { // 'overriding' destroy method
    if(this.dataSource) {
      this.dataSource.destroy(); // needs to be done manually
  }
  this.Super('destroy', arguments);
  }
});

isc.VLayout.create({
  height: 400, width: 600
})
.addMember(isc.Window.create({
  width: '100%',
  heigth: '100%',
  items: [countryList] // ListGrid gets destroyed
}))
.addMember(isc.Button.create({
  title: 'destroy',
  action: function () {
    this.parentElement.destroy();
}
}));

The Openbravo Case

In Openbravo 3 there were few places with some composed components, that child/related objects were not destroyed when destroying the main component.

Previous to the upcoming 3.0MP3 if you close a window (e.g. Sales Order), some objects were not getting destroyed, resulting in memory consumption increase. After working with the application a couple of hours (opening and closing several windows) the user got strange behavior like: slow reponse to user actions, slow repaint process, etc.

Some of the components that were not properly managed are:

  • LinkedItems section (section in Form view that shows you the related records)
  • SelectorItem (the component behind Product, Business Partner selector)
  • StatusBar (the component in the upper part of the Form view)
  • Loading Tab (temporary tab shown when requesting a View definition to the server)

Linked Items

Linked Items

The Linked Items section contains two ListGrids and a DataSource each one of them. When destroying the Form, you need to manually destroy the ListGrid and DataSource associated.

Selector

Selector

As explained before, the Selector is one of the most commonly used widget. When destroying the Form, you need to manually destroy the Window that contains a ListGrid and the associated DataSource.

Status Bar

Status Bar

The StatusBar contained some images not added using the AutoChild pattern and required to be manually destroyed when destroying the StatusBar.

Loading Tab

Loading Bar

When opening a window, a Loading Tab is shown in the TabSet. After the window gets created the Tab content is replaced using TabSet.updateTab. The documentation clearly states, “NOTE: the old pane for the tab is not destroyed”, you need to manually destroy the Loading Tab.

The Fix

Using scopeleaks

If we use the strategy described at the begining, we need to take a snapshot of the global namespace, perform the leaky action (open/close a window), take a second snapshot and compare.

We have used and modified scopeleaks, a utility tool created by Rui Lopes. The tool is intended for detecting leaks of variables to the global namespace. Since we now that SmartClient always leaks variables, we have modified it to only check for instances of a class, meaning any new global variable starting with isc_ that is not null.

Detecting leaks

The steps to detect a leak:

  • Login into the application

  • Open Web Dev Tools

  • Take a first snapshot of the global namespace

    var s1 = scopeleaks.snapshot();
  • Open and close a window (e.g. Sales Order)

  • Take a second snapshot and compare

    var leaks = scopeleaks.leaks(s1); // leaks will have an array with all the global variables that are not null

Repeating this flow, you could detect if a user interaction, creates new objects that are never released.

If you are interested in checking all the changes made on the components mentioned before, check the Issue 18227.

Conclusions

Solving simple memory leaks in a SmartClient based application is straight forward if you use this strategy. Note that we are only fixing the most obvious ones.

SmartClient is a great library, but even SmartClient code could cause memory leaks.

You need to learn and know the framework you’re using. You cannot go blindly creating objects here and there and expecting that everything will automatically collected when is not needed.

Use all tools available and measure memory consumption when using the application.

Written by katratxo

August 25, 2011 at 12:16 pm

Posted in Openbravo

Tagged with , , ,

Development tips: JavaScript debugger statement

with 2 comments

According with the MDC the debugger statement was introduced in ECMA-262, Edition 5

Invokes any available debugging functionality. If no debugging functionality is available, this statement has no effect.

This statement is very handy when trying to debug eval’ed code like the View definition in Openbravo 3.0. It seems the only way to have an entry point to the JavaScript program running in the browser.

I’ve tested this statement in Internet Explorer 8, Firefox 4b10 and Chrome 9. And works when you have a debugger running. Note: In Firefox you need Firebug available for that page.

<html>
<head>
</head>
<body>
<script type="text/javascript">
function f() {
 var message = 'hello world';
 debugger;
 if(typeof window.console !== 'undefined') {
   console.log(message);
 }
 else {
   alert(message);
 }
}
f();
</script>
</body>
</html>

Here you have some screenshots:

Internet Explorer
Internet Explorer - JS debugger

Firefox
Firefox/Firebug - JS debugger

Chromium/Chrome
Chrome/Chromium - JS debugger

Written by katratxo

February 10, 2011 at 8:04 pm

Posted in Openbravo

Tagged with , ,

Update: Making a snapshot of your Openbravo instance

with 4 comments

I just updated the snapshot.xml script adding a new property exclude where you can define as comma separated values the patterns you want to exclude from the zip file.

If you are not familiar with this script, I suggest you read my previous post on how to make a snapshot of your Openbravo instance

I tested in some recent revision from pi, and excluding Mercurial metadata and the resulting zip file is almost 350MB smaller!

~/workspace/src/openbravo $ ls -lh openbravo-2011*
-rw-r--r-- 1 iperdomo iperdomo 254M Jan  5 11:04 openbravo-2011-01-05_11-02-07.zip
-rw-r--r-- 1 iperdomo iperdomo 603M Jan  5 11:19 openbravo-2011-01-05_11-10-58.zip

How it works?

You just need to append the property with the list of excluding patterns in the ant call, e.g. Let’s exclude all the .hg folder

~/workspace/src/openbravo/pi $ ant -f snapshot.xml -Dexclude=".hg/**"

The exclude property is passed directly to the Zip task. In order to understand how can you define exclusion patters read the Zip task documentation

Happy new year!

Written by katratxo

January 6, 2011 at 10:19 am

Development tips – Part II

with one comment

In Part I I explained how to:

  • Speed up Firefox by creating a different profile for Firebug and ‘vacuum’ the
    browser’s internal databases
  • Update to the latest ‘safe’ pi revision
  • Clean up your Mercurial workspace

You also know how to configure the Autologon authentication manager that allows you login the application with the same user/password skipping the login page.

Today we’ll focus in speeding up the compilation process using just some configuration properties that will result in skipping some tasks that are not required in the day-to-day development process.

Note: I assume that you are working with Eclipse IDE, you have successfully build the application and also you have experience how to compile the application. If you don’t know how to do that, please read the how to setup your development environment in the wiki.

The Openbravo.properties

The Openbravo.properties file is use to configure the application, e.g. database connection details, date format, etc. But also have properties for the build process and tweaking them allows you skip some tasks when compiling.

You can read more about the Openbravo.properties in the wiki page.

Note: Remember to always read documents in the ERP/2.50 namespace, since all other documents may have been deprecated or are no longer maintained.

minimizeJSandCSS

Some time is spent in minimizing (compressing) the JavaScript and CSS files to the context. That’s why when running the application and you try to see the
.js files everything is compressed without line breaks or spaces. The quick fix to skip this process is set to no the minimizeJSandCSS property.

# use js/css minimization (in local-context and war-file)
minimizeJSandCSS=no

deploy.mode

The deploy.mode defines where the build process needs to copy files and if it needs to generate a .war file. The default deploy.mode is class, so in every compilation call the files are sync’ed with the $CATALINA_BASE/webapps/context folder.

Since you are working with Eclipse the tomcat server you’re using is the one inside Eclipse, the $CATALIBA_BASE/webapps/context is not used at all, instead, the files are read from the WebContent and build folders.

Setting to none the deploy.mode will skip the sync of the files, and also will not generate a .war file.

#Deploy mode: valid values [class, war, none]
deploy.mode=none

Wrapping up

Setting minimizeJSandCSS=no and deploy.mode=none will speed up the day-to-day compilation process.

Remember that this changes recommended just for development environments not for production ones.

Stay tunned!

Written by katratxo

August 20, 2010 at 10:59 am

Posted in Openbravo

Tagged with ,

Making an snapshot of your Openbravo instance

with 22 comments

I have created a small ant script that automates the process of making a ‘snapshot’ of your instance. By snapshot I mean a database dump and the whole Openbravo folder. This archive file (zip format) does not exclude any file, so .class files, Mercurial metadata, etc is also included in the file, so is a ‘real snapshot’

When is this script useful?

This tool is useful when:

  • You are a newbie developing with Openbravo framework, you have an instance up and running, and you want to start testing something. Making an snapshot of your current state, gives you a easy way to step back if something goes wrong.
  • You just want to archive the current state of your instance.
  • You want to make a copy of your current instance. You can make a snapshot and restore in another folder. A couple of tweaks in the Openbravo.properties and voila! you have a copy of your instance.
  • You want to move your running instance to a another environment (e.g for testing).
  • Etc.

How to use it?

Note: The following commands are for GNU/Linux users. If you use other operating system, the script still works but the unzip process is different, e.g. I have tested on Windows using the built in extracting tool for zip files.

You can grab a copy of the snapshot.xml and place it inside your Openbravo sources folder, eg.

~/src/openbravo/pi $ wget http://bitbucket.org/iperdomo/labs/raw/8db9dcb650d3/openbravo/snapshot.xml

Making a snapshot

Once you have the snapshot.xml in the sources folder just run it (the default target is snapshot).

~/src/openbravo/pi $ ant -f snapshot.xml

Depending on your machine’s resources the script takes about 7 minutes. Is slower on Windows machines (and more slow if you have an antivirus checking every file that you touch). Here you have an example of the output.

snapshot:
     [echo] Creating instance snapshot...
     [echo] Making temp folder...
     [echo] basedir /home/iperdomo/src/openbravo/pi
     [echo] Creating a database backup...
     [exec] pg_dump: reading schemas
     [exec] pg_dump: reading user-defined functions
     [exec] pg_dump: reading user-defined types
     [exec] pg_dump: reading procedural languages
     [exec] pg_dump: reading user-defined aggregate functions
     [exec] pg_dump: reading user-defined operators
     [exec] pg_dump: reading type casts
     [exec] pg_dump: finding inheritance relationships
     [exec] pg_dump: reading column info for interesting tables

... some more pg_dump logging ...

     [exec] pg_dump: dumping contents of table s_resourceunavailable
     [exec] pg_dump: dumping contents of table s_timeexpense
     [exec] pg_dump: dumping contents of table s_timeexpenseline
     [exec] pg_dump: dumping contents of table s_timetype
     [echo] Making a zip...
      [zip] Building zip: /home/iperdomo/src/openbravo/openbravo-2010-02-19_13-18-45.zip

BUILD SUCCESSFUL
Total time: 7 minutes 33 seconds

The script will call pg_dump with all the necessary parameters read from Openbravo.properties, and creates a .backup in the temp folder inside the sources. Then it archives the whole content in a zip file in the parent folder. The name of the zip is the name of the context plus a timestamp.

Restoring a snapshot

To restore an snapshot is as simple as removing the current folder and extracting the contents of the zip file. Then run the restore target from the script.

~/src/openbravo $ rm -rf pi

~/src/openbravo $ unzip /home/iperdomo/src/openbravo/openbravo-2010-02-19_13-18-45.zip -d pi

Example output of unzip

Archive:  /home/iperdomo/src/openbravo/openbravo-2010-02-19_13-18-45.zip
   creating: pi/.hg/
   creating: pi/.hg/store/
   creating: pi/.hg/store/data/
   creating: pi/.hg/store/data/.settings/
   creating: pi/.hg/store/data/_web_content/
   creating: pi/.hg/store/data/_web_content/_m_e_t_a-_i_n_f/
   creating: pi/.hg/store/data/_web_content/_w_e_b-_i_n_f/
   creating: pi/.hg/store/data/config/
   creating: pi/.hg/store/data/config/eclipse/
   creating: pi/.hg/store/data/database/model/sequences/

... some more log ...

  inflating: pi/web/skins/Default/Popup/_ParticularItems/Workflow/iconTask.png  
  inflating: pi/web/skins/Default/Popup/_ParticularItems/Workflow/iconWorkflow.png  
  inflating: pi/web/skins/Default/RTLFlippedImages.txt

After extracting the contents of the zip file, change the directory to the Openbravo sources and execute the restore target, e.g.

~/src/openbravo $ cd pi

~/src/openbravo/pi $ ant -f snapshot.xml restore

This will drop the database and use pg_restore to restore the .backup file stored in the temp folder. Here you have some example of the log output.

Buildfile: snapshot.xml

check.backup:
     [echo] Checking if openbravo.backup file exists in /home/iperdomo/src/openbravo/pi/temp folder...

restore:
     [echo] Deleting the database ...
      [sql] Executing commands
      [sql] 1 of 1 SQL statements executed successfully
     [echo] Creating the database...
      [sql] Executing commands
      [sql] 1 of 1 SQL statements executed successfully
     [echo] Restoring the backup file...
     [exec] pg_restore: connecting to database for restore
     [exec] pg_restore: creating SCHEMA public
     [exec] pg_restore: creating COMMENT SCHEMA public
     [exec] pg_restore: creating PROCEDURAL LANGUAGE plpgsql
     [exec] pg_restore: creating FUNCTION a_amortization_process(character varying)

... some other pg_restore log ...

     [exec] pg_restore: setting owner and privileges for FK CONSTRAINT s_timeexpenseline_s_timeexpens
     [exec] pg_restore: setting owner and privileges for FK CONSTRAINT s_timeexpenseline_s_timetype
     [exec] pg_restore: setting owner and privileges for FK CONSTRAINT s_timetype_ad_client
     [exec] pg_restore: setting owner and privileges for FK CONSTRAINT s_timetype_ad_org

BUILD SUCCESSFUL
Total time: 36 seconds

That’s it, you have restored your snapshot.

How to use the script to copy an instance?

You follow the same procedure, to create the snapshot. Then you unzip the archive and another folder.

~/src/openbravo $ unzip openbravo-2010-02-19_13-18-45.zip -d pi2

After the extraction of the archive contents, you modify the Openbravo.properties and change the following properties:

# New name of the context
context.name=openbravo-copy

# Should point to the new path
source.path=/home/iperdomo/src/openbravo/pi2

# New name of the database
bbdd.sid=pi_reference

After changing the Openbravo.properties file, execute the restore process as usual.

Notes

Using the script with non default settings

The script assumes that you are working in a development environment with the ‘standard’ configuration of PostgreSQL. If your database is not in localhost or not using the default port 5432, you can override those properties when making the backup and restoring it.

~/src/openbravo/pi $ ant -f snapshot.xml -Dhost=otherHost -Dport=otherport

Why is not this part of Openbravo’s standard distribution?

As you can see the script uses pg_dump and pg_restore for dumping and restoring the database, so only PostgreSQL is supported. If you are willing you can send me a patch to include support for Oracle

That’s all, I hope this script will save you some time

Written by katratxo

February 22, 2010 at 12:25 pm

Development tips – Part I

with 5 comments

This is the first one of a series of posts about development tips that I use in a daily basis. I think that you can benefit from them, and speed up your daily work. There are some one-line commands that you can use to execute several things in one shot. Those is targeting GNU/Linux users.

Speeding up Firefox

I have found a few tips that can improve its performance.

Use different profiles

Add-ons could decrease the performance of your browser. One example of decreasing performance add-on is Firebug. It’s just a great tool to debug JavaScript, CSS, HTML, monitor requests, etc, but it has a known issue:

“… If you have Firebug installed you are probably not getting fast Javascript. Firebug doesn’t have to be active on your current page …”

That’s why is a best practice to use Firebug in a different profile.

I have a profile named “debug” where the only add-on installed is Firebug and made a simple bash script ffbug to open Firefox with that profile:

#!/bin/bash
firefox -no-remote -P "debug" &

The -no-remote parameter allows you open a new instance of Firefox and not just a new window linked to the current running one. The -P is to define which profile you want to use.

VACUUM the SQLite databases

You can improve the performance of Firefox by vacuum your bookmarks, history, etc. databases.
Close all your running Firefox instances and execute:

~ $ for i in $(find -name '*.sqlite'); do sqlite3 $i VACUUM; done;

Useful one-line commands

Updating the source code of the modules your working on

In some cases you work with several modules at the same time, and you want to get all the changes made by your colleagues. You can update your local repositories by executing this line inside your modules folder:

for i in $(ls -1); do cd $i; hg pull -u; cd ..; done;

Example of the output of this is:

~/src/openbravo/working/pi-reference/modules $ for i in $(ls -1); do cd $i; hg pull -u; cd ..; done;
pulling from https://code.openbravo.com/erp/mods/org.openbravo.base.seam
searching for changes
no changes found
pulling from https://code.openbravo.com/erp/mods/org.openbravo.client.freemarker/
searching for changes
no changes found
pulling from https://code.openbravo.com/erp/mods/org.openbravo.client.kernel/
searching for changes
no changes found
pulling from https://code.openbravo.com/erp/mods/org.openbravo.service.datasource
searching for changes
no changes found
pulling from https://code.openbravo.com/erp/mods/org.openbravo.service.json/
searching for changes
no changes found
pulling from https://code.openbravo.com/erp/mods/org.openbravo.userinterface.selector
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 13 changes to 13 files
13 files updated, 0 files merged, 0 files removed, 0 files unresolved
pulling from https://code.openbravo.com/erp/mods/org.openbravo.userinterface.smartclient
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 3 changes to 3 files
3 files updated, 0 files merged, 0 files removed, 0 files unresolved

You can easily hack this line by executing another Mercurial command, e.g. See what’s the status on each repository:

for i in $(ls -1); do cd $i; hg st; cd ..; done;

Update and build the latest pi

Assuming that you want to use the latest stable revision from pi, you can check our continuous integration framework and see what’s the latest revision that passed all the tests, then you can update your local repository to that revision and rebuild your system. Or you can copy this bash script (pi-update), and execute it inside your pi working copy:

#!/bin/bash
REVISION=`wget -q -O - http://builds.openbravo.com/job/erp_devel_pi-full-pgsql/lastSuccessfulBuild/changes | awk -F: '/https:\/\/code\.openbravo\.com\/erp\/devel\/pi\/rev\// {print substr($3,0,12)}' | head -n1`
hg pull
echo "Updating to rev $REVISION"
hg up -r $REVISION
ant smartbuild -Dlocal=no

This script uses wget and awk to get the latest revision from the last successful build page, pulls all the changesets of PI and updates your working copy to the detected revision, then updates your database and compiles the application.

Cleanup your working copy

The hg status command will tell you which files Mercurial doesn’t know about; it uses a “?” to display such files …

I often want to clean my working copy and remove all unversioned files. You can rid of those file by executing:

 hg st -nu | xargs rm -rf

That’s all folks! I’ll come back with more tips in the future. Disclaimer: I’m not a bash expert so I know that some of this commands could be simplified. I would also like to thank iarwain for simplifying some of this commands.

Written by katratxo

January 15, 2010 at 11:21 am

Posted in Openbravo

Tagged with ,