The move from OpenOffice To LibreOffice For Java Developers

After Sun Inc had been acquired by Oracle ; and because of some clashes between Sun’s open source projects communities and the -somewhat unfriendly- Oracle attitude towards Sun’s open source projects; OpenOffice has been forked to LibreOffice. This Effort is being Led by document foundation probably with joint efforts to come from major Linux companies Like Novel ,Ubuntu and RedHat .

For Java OpenOffice API developers , the move to Libreoffice should be straightforward. the basic idea is just replace OpenOffice installation and sdk with corresponding Libreoffice ones. If you consider doing serious Java Libreoffice extension development it is advised to use an IDE plugin for Libreoffice.

The only two available Libreoffice/openoffice java IDE plugins are OOEclipseIntegration and Netbeans 6.8 OpenOffice.org API Plugin . While the eclipse OO plugin is available in eclipse marketplace repos from eclipse IDE (3.6+) and is still undergoing active development; It seems the netbeans OO plugin doesn’t get upgraded into higher netbeans versions anymore. So you are stuck using the old netbeans version 6.8.

To set up your Netbeans OpenOffice.org development environment with Libreoffice do the following steps:

  1.   Get netbeans IDE 6.8 and then install plugin “OpenOffice.org API Plugin” from netbeans IDE plugins dialog.
  2.   Downlaod and Install Libreoffice and Libreoffice SDK.
  3. In Netbeans IDE select “Tools->Options->Miscellaneous”. select Tab “OOo API Plugin”. then in fields “Openoffice.org installation” and “Openoffice.org SDK installation” browse to the folder where you installed Libreoffice and libreoffice SDK respectively as described in image below:

When you create a new Project for OpenOffice.org API the currently specified Libreoffice sdk library will be automatically added to your Netbeans OpenOffice Project classpath (for example Libreoffice 3.4 library )

For a Detailed tutorial on java netbeans OpenOffice API development refer to Kay Kroll on Netbeans OpenOffice integration .  I have also included a presentation by jurgen Shmidt included in the ‘Additional Resources’ section at the bottom of this post. These two tutorials although for OpenOffice.org they also apply to Libreoffice java development in Netbeans as the same process applies for both OpenOffice and LibreOffice.

UPDATE:

For people using linux (specifically opensuse 12.+) upgrading to Libreoffice version 3.5+ causes some issues. In  netbeans 6.8 IDE the automatically added  library ‘LibreOffice 3.5 or 3.6’ to the project classpath  misses other important jars for some unknown reasons. I had to manually add the following jars from the directory ‘Your_LO_Home/ure/share/java’ :


	juh.jar
	jurt.jar
	ridl.jar
	unoloader.jar

For some unkown reason the netbeans library LibreOffice 3.5 doesn’t contains these jars and i had to add them manually to project classpath.

Another issue with netbeans OO plugin and LO sdk 3.5+ is when you clean and build your OO project and try ‘debug extension in OO Target’ there is a wiered error massage stating:

‘user doesn’t have write permission to install this extension’.

you simply overcome this error message by running again the ‘debug extension in OO Target’.


Additional Resources:


Advertisements

Runtime Introspection of OpenOffice OXT package

The OpenOffice.org Developer’s Guide defines A OpenOffice.org OXT Extension as follows :

An extension is a file intended for the distribution of code and / or data which is to be used by OpenOffice.org. The file has the file extension (formerly .uno.pkg and .zip), and it acts as a container for various items, such as libraries, JARs, configuration data, type libraries, Basic libraries, Basic dialogs, etc. Before OpenOffice.org can use any content of the extension, it needs to be installed by the Extension Manager.
An extension should also contain a description.xml and must contain a directory META-INF (all uppercase). The META-INF directory contains a manifest.xml which lists all items and their media-type.

Recently i was working on a Openoffice.org Addon in Java and at some point of the project i needed a way to extract -at runtime- some information stored in the OXT binary (in fact I was looking for the OXT Properties).
If you’re using Netbeans openoffice.org plugin, you can get the OOo Extension Properties by right-clicking your project in the projects list and selecting OXT Extension properties, it’s under the Extension Management tab.
Because I needed to dynamically query my OXT extension for some useful information stored in the “description.xml” file , I decided to write a Java class to browse and parse the OXT Extension Files and extract OXT Properties at Runtime.

The most important fields that this class must knows about are the Extension Identifier and the com.sun.start.XComponentContext Object. So it makes good sense to pass these two Objects as arguments to class constructor.

/**
* constructor
* @param m_xContext
* @param extensionIdentifier
*/
public XPackageInformationParser(XComponentContext m_xContext, String extensionIdentifier) {
this.m_xContext = m_xContext;
this.extensionIdentifier = extensionIdentifier;
// some other initialization code goes here
}

OpenOffice UNO API provides a class com.sun.star.deployment.XPackageInformationProvider which is the entry class to access the OXT Package Information at runtime.
The following method configures and initialize the important UNO Objects that will be used to extract information from the UNO package:

private void intitialize(){
try {
XPackageInformationProvider xPackageInformationProvider = PackageInformationProvider.get(m_xContext);
oxtLocation = xPackageInformationProvider.getPackageLocation(this.extensionIdentifier);
Object oTransformer = m_xContext.getServiceManager().createInstanceWithContext("com.sun.star.util.URLTransformer", m_xContext);
xTransformer = (XURLTransformer) UnoRuntime.queryInterface(XURLTransformer.class, oTransformer);
} catch (Exception ex) {
_logger.error(ex);
}
}

this method calculates the OXT Package location path and stores it in a String variable OXTLocation. It also create a XURLTransformer Object that will be used to parse openoffice URLs.

To read all files contained within the OXT Extension Package I use the following method:

/**
* returns files list that are contained within current .oxt package at runtime
* @param dir directory to search files list (null for top level OXT directory)
* @return array of OXT files
*/
public String[] getFileList(String dir) {
com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1];
oURL[0] = new com.sun.star.util.URL();
oURL[0].Complete = (dir == null) ? oxtLocation + "/" : oxtLocation + "/" + dir;
xTransformer.parseStrict(oURL);
return new File(oURL[0].Path + oURL[0].Name).list();
}

By passing a null argument to this method it will return an array of all file names at top level of the OXT Package hierarchy structure.
It’s also possible to get a Java File Object reference to a given file contained within the OXT Extension Package. The below method do this :

/**
* get file within OXT package
* @param filePath
* @return reference to OXT File
*/
public File getExtensionFile(String filePath) {
com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1];
oURL[0] = new com.sun.star.util.URL();
oURL[0].Complete = this.oxtLocation + "/" + filePath;
xTransformer.parseStrict(oURL);
return new File(oURL[0].Path + oURL[0].Name);
}

To get a reference to the Java File Object of description.xml file located at top level of the OXT package , we call the method like this :

getExtensionFile(description.xml);

Now lets investigate the heart functionality of this class; which is the method that parses the OXT description.xml file and extract the OXT Properties and stores them in a Java Properties Object. The method is listed below:

/**
* parse OXT description file
*/
public void parseOXTProperties() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// For HTML, we don't want to validate without a DTD
dbf.setValidating(false);
// Ignore text elements that are completely empty:
dbf.setIgnoringElementContentWhitespace(false);
dbf.setExpandEntityReferences(true);
dbf.setCoalescing(true);
// Ensure that getLocalName() returns the HTML element name
dbf.setNamespaceAware(true);
DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) {
pce.printStackTrace();
return;
}
Document document = null;
File description = this.getExtensionFile(EXTENSION_DESC_FILE);
_logger.debug("got OXT description file: " + description.getAbsolutePath());
try {
document = db.parse(description);
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node_i = nodes.item(i);
if (node_i.getNodeType() == Node.ELEMENT_NODE) {
Element element = ((Element) node_i);
if (element.getTagName().equals(VERSION)) {
this.properties.setProperty(VERSION, element.getAttribute(VALUE));
}
if (element.getTagName().equals(IDENTIFIER)) {
this.properties.setProperty(IDENTIFIER, element.getAttribute(VALUE));
}
if (element.getTagName().equals(DISPLAY_NAME)) {
NodeList nodeList = element.getElementsByTagName("name");
_logger.debug(nodeList.item(0).getNodeName());
this.properties.setProperty(DISPLAY_NAME, nodeList.item(0).getTextContent());
}
if (element.getTagName().equals(PUBLISHER)) {
NodeList nameNode = element.getElementsByTagName("name");
_logger.debug(nameNode.item(0).getNodeName());
this.properties.setProperty(PUBLISHER, nameNode.item(0).getTextContent());
}
}
}
} catch (SAXException ex) {
_logger.error(ex);
} catch (IOException ex) {
_logger.error(ex);
}
}

This method simply uses a java DOM parser to parse file description.xml ( file resides by-default at top level of OXT package) .It extracts useful xml document’s data (such as “publisher”, “version”, “display-name” etc..) then stores them in a Java Properties Object.
(the complete listing of XPackageInformationParser class is down-loadable from resources section at top bottom of this article.)

A Test code for printing OXT Package properties at runtime would look like :

XPackageInformationParser oxtParser=new XPackageInformationParser(m_xContext,org.openoffice.example);
System.out.println(oxtParser.getProperties());

you just replace string org.openoffice.example with your actual OOo Extension identifier (which you defined while creating your Addon component).

A sample output obtained by running above test code for my OXT Extension gives me the following Properties :

{display-name=OpenOffice.org Addon for SugarCRM , version=0.0.1, identifier=com.sun.star.addon.sugarcrm.OOoSugarAddOn, publisher=Othman El Moulat}

Now Of course you can do more useful things with this class by examining each file retrieved from OXT Package at runtime. for example the /META-INF/manifest.xml file contains some useful information that could be used at runtime by your Java OOo Add-on.

Below is the complete listing of the XPackageInformationParser class.



import com.sun.star.deployment.PackageInformationProvider;
import com.sun.star.deployment.XPackageInformationProvider;
import com.sun.star.uno.Exception;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.util.XURLTransformer;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* utility class to parse a OXT extension package.
* @author othman
*/
public class XPackageInformationParser {

private static Logger _logger = Logger.getLogger(XPackageInformationParser.class);
private static final String EXTENSION_IDENTIFIER = "com.sun.star.addon.sugarcrm.OOoSugarAddOn";
private static final String EXTENSION_DESC_FILE = "description.xml";
private static final String VERSION = "version";
private static final String PUBLISHER = "publisher";
private static final String IDENTIFIER = "identifier";
private static final String DISPLAY_NAME = "display-name";
private static final String VALUE = "value";
private XComponentContext m_xContext;
private XURLTransformer xTransformer;
private String extensionIdentifier;
private String OXTLocation;
private Properties properties;

/**
*constructor
* @param m_xContext
*/
public XPackageInformationParser(XComponentContext m_xContext) {
this(m_xContext, EXTENSION_IDENTIFIER);
}

/**
* constructor
* @param m_xContext
* @param extensionIdentifier
*/
public XPackageInformationParser(XComponentContext m_xContext, String extensionIdentifier) {
this.m_xContext = m_xContext;
this.extensionIdentifier = extensionIdentifier;
properties = new Properties();
this.intitialize();
this.parseOXTProperties();

}

private void intitialize() {
try {
XPackageInformationProvider xPackageInformationProvider = PackageInformationProvider.get(m_xContext);
OXTLocation = xPackageInformationProvider.getPackageLocation(this.extensionIdentifier);
Object oTransformer = m_xContext.getServiceManager().createInstanceWithContext("com.sun.star.util.URLTransformer", m_xContext);
xTransformer = (XURLTransformer) UnoRuntime.queryInterface(XURLTransformer.class, oTransformer);
} catch (Exception ex) {
_logger.error(ex);
}
}

/**
* returns files list that are contained within current .OXT package at runtime
* @param dir directory to search files list (null for top level OXT directory)
* @return array of OXT files
*/
public String[] getFileList(String dir) {

com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1];
oURL[0] = new com.sun.star.util.URL();
oURL[0].Complete = (dir == null) ? OXTLocation + "/" : OXTLocation + "/" + dir;
xTransformer.parseStrict(oURL);
return new File(oURL[0].Path + oURL[0].Name).list();
}

/**
* get file within OXT package
* @param filePath
* @return reference to OXT File
*/
public File getExtensionFile(String filePath) {

com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1];
oURL[0] = new com.sun.star.util.URL();
oURL[0].Complete = this.OXTLocation + "/" + filePath;
xTransformer.parseStrict(oURL);
return new File(oURL[0].Path + oURL[0].Name);

}

/**
* parse OXT description file
*/
public void parseOXTProperties() {

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

// For HTML, we don't want to validate without a DTD
dbf.setValidating(false);
// Ignore text elements that are completely empty:
dbf.setIgnoringElementContentWhitespace(false);
dbf.setExpandEntityReferences(true);
dbf.setCoalescing(true);

// Ensure that getLocalName() returns the HTML element name
dbf.setNamespaceAware(true);

DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException pce) {
pce.printStackTrace();
return;
}

Document document = null;

File description = this.getExtensionFile(EXTENSION_DESC_FILE);
_logger.debug("got OXT description file: " + description.getAbsolutePath());

try {
document = db.parse(description);
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node_i = nodes.item(i);

if (node_i.getNodeType() == Node.ELEMENT_NODE) {
Element element = ((Element) node_i);

if (element.getTagName().equals(VERSION)) {

this.properties.setProperty(VERSION, element.getAttribute(VALUE));
}

if (element.getTagName().equals(IDENTIFIER)) {

this.properties.setProperty(IDENTIFIER, element.getAttribute(VALUE));
}

if (element.getTagName().equals(DISPLAY_NAME)) {

NodeList nodeList = element.getElementsByTagName("name");
_logger.debug(nodeList.item(0).getNodeName());

this.properties.setProperty(DISPLAY_NAME, nodeList.item(0).getTextContent());
}

if (element.getTagName().equals(PUBLISHER)) {

NodeList nameNode = element.getElementsByTagName("name");
_logger.debug(nameNode.item(0).getNodeName());

this.properties.setProperty(PUBLISHER, nameNode.item(0).getTextContent());
}

}
}
} catch (SAXException ex) {
_logger.error(ex);
} catch (IOException ex) {
_logger.error(ex);
}

}

/**
* checks if OXT description file exists.
*
* @return true if OXT description file exists
*/
public boolean ensureExtensionDescription() {
boolean exists = false;
String[] OXTFile = this.getFileList(null);
for (int i = 0; i < OXTFile.length; i++) {
if (OXTFile[i].equals(EXTENSION_DESC_FILE)) {
return true;
}
}
return exists;

}

public String getOxtLocation() {
return OXTLocation;
}

public Properties getProperties() {
return properties;
}

/**
*
* @return
*/
public String getVersion() {
return properties.getProperty(VERSION);
}

public String getPublisher() {
return properties.getProperty(PUBLISHER);
}

public String getIdentifier() {
return properties.getProperty(IDENTIFIER);
}

public String getDisplayName() {
return properties.getProperty(DISPLAY_NAME);
}
}

Resources:

A Combined Chart Using OpenOffice.org Chart2 API

This Post describes a method to programatically combine different OpenOffice chart types in same coordinate system. This will be done using OpenOffice.org chart2 API as the old chart1 API is not flexible enough to allow combining multiple OpenOffice.org chart-types in same Graph. ( see my previous topic : OpenOffice Chart2 Not yet Ready! )

This Tutorial suppose you are already familiar with OpenOffice UNO API development in Java. If you are new to this field you can get started by taking a look to the Openoffice.org API developer guide.
From my own experience I found the Netbeans OpenOffice API plugin to be an Excellent Tool that could simplify the somewhat complicated tasks of java UNO API development. Kay Kroll from Netbeans.org has an article on Netbeans OpenOffice integration. He mentioned that we could extend OpenOffice.org’s charting capabilities by using the Netbeans OpenOffice Integration tool.

Now let’s walk through the steps to produce a combined chart using the chart2 API and the Java programming language. Again a warning about using com.sun.star.chart2 API; It is not provided for external usage yet. It is an internal API that will change further. When the API evolves your code will not work with future versions. If you nevertheless want to use the chart2 API on your own risk do the following:

1-Query your chart document for interface com.sun.star.chart2.XChartDocument

2-Call methods getFirstDiagram() and getDataProvider() on the returned Object from step one

3-Query the received diagram for interface com.sun.star.chart2.XCoordinateSystemContainer

4-Receive the first and only coordinate system from the container

5-Query the coordinate system for interface com.sun.star.chart2.XChartTypeContainer

The Java code snippet below shows the implementation for steps 1 to 5:

 public com.sun.star.chart2.XChartTypeContainer getChartTypeContainer() {
    /* let the Calc document create a data provider, set it at the chart */
    com.sun.star.chart2.data.XDataProvider oDataProv = xChartDoc.getDataProvider();
    com.sun.star.chart2.XDiagram  oDiagram  = xChartDoc.getFirstDiagram();
    // insert a coordinate system into the diagram
com.sun.star.chart2.XCoordinateSystemContainer oCoordSysCnt =(com.sun.star.chart2.XCoordinateSystemContainer) UnoRuntime.queryInterface(
                com.sun.star.chart2.XCoordinateSystemContainer.class, oDiagram);
 com.sun.star.chart2.XCoordinateSystem[] oCoordSys = oCoordSysCnt.getCoordinateSystems();
 // Query the coordinate system for interface com.sun.star.chart2.XChartTypeContainer
   xChartTypeContainer = (com.sun.star.chart2.XChartTypeContainer) UnoRuntime.queryInterface(
    com.sun.star.chart2.XChartTypeContainer.class, oCoordSys[0]);
        return xChartTypeContainer;
    }

6-create an instance of the line chart type :

public com.sun.star.chart2.XChartType getChartType(String type) throws Exception {
        Object  object    = xMCF.createInstanceWithContext(type, xContext);
        com.sun.star.chart2.XChartType chartType =(com.sun.star.chart2.XChartType)
        UnoRuntime.queryInterface(com.sun.star.chart2.XChartType.class, object);
        return chartType;
    }

7-add the line chart type to chart type container:

com.sun.star.chart2.XChartType lineChartType =chart.getChartType("com.sun.star.chart2.LineChartType");
  // add line chart type
  chart.getXChartTypeContainer().addChartType(lineChartType);

8-Query the new chart type for interface com.sun.star.chart2.XDataSeriesContainer:

com.sun.star.chart2.XDataSeriesContainer dataSeriesCnt=(com.sun.star.chart2.XDataSeriesContainer)UnoRuntime.queryInterface(com.sun.star.chart2.XDataSeriesContainer.class,lineChartType);

9-Create a new data series ( create an instance of service com.sun.star.chart2.DataSeries) and add it to the container:

public com.sun.star.chart2.XDataSeries getDataSeries() throws Exception {
Object object = xMCF.createInstanceWithContext("com.sun.star.chart2.DataSeries",xContext);
com.sun.star.chart2.XDataSeries oSeries =(com.sun.star.chart2.XDataSeries)
UnoRuntime.queryInterface(com.sun.star.chart2.XDataSeries.class, object);
return oSeries;
    }

dataSeriesCnt.addDataSeries(oSeries);

To connect data to the data series do the following:

10- Query the series for interface com.sun.star.chart2.data.XDataSink:

com.sun.star.chart2.data.XDataSink dataSink=(com.sun.star.chart2.data.XDataSink)UnoRuntime.queryInterface(com.sun.star.chart2.data.XDataSink.class,oSeries);

11- Use method createDataSequenceByRangeRepresentation() at the formerly received data provider to create a data sequences from the cell ranges of your choice:

com.sun.star.chart2.data.XDataSequence oSequence=oDataProv.createDataSequenceByRangeRepresentation("$Sheet1.$A$70:$C$777");

12-Set the property ‘Role’ at the data sequence to ‘values-y’:

XPropertySet cProp = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oSequence);
cProp.setPropertyValue("Role", "values-y");

13-Create an instance of UNO service ‘com.sun.star.chart2.LabeledDataSequence’ and use method setValues() to attach the formerly created sequence:

Object object =xMCF.createInstanceWithContext("com.sun.star.chart2.data.LabeledDataSequence", xContext);
com.sun.star.chart2.data.XLabeledDataSequence oLabeledSequence=(com.sun.star.chart2.data.XLabeledDataSequence)UnoRuntime.queryInterface(com.sun.star.chart2.data.XLabeledDataSequence.class, object);
 oLabeledSequence.setValues(oSequence);

14-Use method ‘setData()’ at the formerly received interface XDataSink to attach the labeled data sequences to the series:

com.sun.star.chart2.data.XLabeledDataSequence[] aLabeledSequence={ dLabeledSequence,oLabeledSequence,hLabeledSequence, lLabeledSequence, cLabeledSequence };
 dataSink.setData(aLabeledSequence);

Using the Netbeans OpenOffice.org plugin i created a OpenOffice Addon that implements the above 14 steps to draw a combined candle chart and 2 line charts super-imposed in the same Graph coordinate system.
the results are shown in Image below :

combined candle and line charts

(Graph programatically drawn using openoffice.org chart2 API)

As you can see this method gives quit satisfactory results. However, No doubt the Openoffice Charting component is not yet mature and is by no way a robust charting API for Java developers. SUN OpenOffice.org charting developer team admit these facts and say there is still lot of ongoing work.

Waiting for the official OpenOffice Chart2 API release , i Hope this Tutorial could be useful for some Java UNO API developers willing to start playing with the unofficial OpenOffice chart2 API.

You can check the source code demo of this project at Github.


Resources: