mailmerge Tomas O Connor How do I execute a MailMerge using the OpenOffice.org API

Compiling the sample program

============================

Set up your CLASSPATH environment variable as follows (substitute the path

to your OpenOffice.org installation for /export/home/staroffice below):

% setenv CLASSPATH ${CLASSPATH}:/export/home/staroffice/program/classes/jurt.jar:/export/home/staroffice/program/classes/ridl.jar:/export/home/staroffice/program/classes/sandbox.jar:/export/home/staroffice/program/classes/unoil.jar:/export/home/staroffice/program/classes/juh.jar:.

Now compile the sample program:

% javac MailMerger.java

Running the sample program

==========================

First start OpenOffice.org from the command line with the -accept flag:

% /export/home/staroffice/soffice "-accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager"

Note: If you use a different port than 8100, you will need to change the

DEFAULT_CONNECTION_STRING variable in MailMerger.java and recompile.

Copy the values.csv file into a directory on your machine eg. /export/home/csv

The MailMerger program takes two possible flags:

% java MailMerger -create >URL of Data Source directory& >URL to save template file& >Data Source name& >Table name&

The Data Source directory paramter is a file URL and should point to

the directory where your .csv files are stored eg. ///export/home/csv

The template file URL should have the extension .stw to specify that it

is a OpenOffice.org template you want stored eg. ///export/home/mytemplate.stw

The Table name parameter should be the name of one of the tables stored in

your Data Source directory (each .csv file in the directory is treated as

a table, it's name is the filename with the .csv extension removed).

So the command will look something like:

% java MailMerger -create ///export/home/csv ///export/home/mytemplate.stw mydatasource values

This command will:

fields in the Writer document

name provided

You can then run a MailMerge as follows:

% java MailMerger -merge >URL of template file& >Data Source name& >Table name&

This will perform a MailMerge using the template file, data source and

table names provided. These should be the same names that you used in

the -create command

So the corresponding command for the -create example above would be:

% java MailMerger -merge ///export/home/mytemplate.stw mydatasource values

Further Information

===================

http://api.openoffice.org/DevelopersGuide/DevelopersGuide.html

The First Steps chapter gives a good understanding of the basic concepts

behind the OpenOffice.org API.

references to the sections of the Developer Guide which cover the APIs

used in the program.

http://api.openoffice.org/common/ref/com/sun/star/module-ix.html

http://www.openoffice.org/dev_docs/source/sdk/index.html

This SDK will be the basis of the OpenOffice.org SDK. It contains examples

which help you to get started with writing Java programs for the OpenOffice.org

API.

Example contents of values.csv file

=========================

Name,Address,Telephone

Joe,Kerry,9933339

Tom,Dublin,9988888

/* Before writing any software using the OpenOffice.org SDK it is important to understand how OpenOffice.org uses services and interfaces to access objects and how to set object properties. The following sections in the Developers Guide give a good introduction to these topics: "Services", Section 2.5.1, p34 "Using Interfaces", Section 2.5.1, p36 "Using Properties", Section 2.5.1, p38 */ // Classes for bootstrapping connection to OpenOffice.org import com.sun.star.comp.helper.Bootstrap; import com.sun.star.uno.XNamingService; import com.sun.star.bridge.XUnoUrlResolver; // Classes for accessing interfaces of the OpenOffice.org API import com.sun.star.lang.XMultiComponentFactory; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.lang.XSingleServiceFactory; import com.sun.star.lang.XComponent; import com.sun.star.uno.XComponentContext; import com.sun.star.uno.UnoRuntime; // Classes for loading and storing documents import com.sun.star.frame.XComponentLoader; import com.sun.star.frame.XStorable; // Classes for property access import com.sun.star.beans.PropertyValue; import com.sun.star.beans.XPropertySet; import com.sun.star.beans.NamedValue; "MailMerger.java" 469 lines, 18299 characters icarus% !c cat MailMerger.java /* Before writing any software using the OpenOffice.org SDK it is important to understand how OpenOffice.org uses services and interfaces to access objects and how to set object properties. The following sections in the Developers Guide give a good introduction to these topics: "Services", Section 2.5.1, p34 "Using Interfaces", Section 2.5.1, p36 "Using Properties", Section 2.5.1, p38 */ // Classes for bootstrapping connection to OpenOffice.org import com.sun.star.comp.helper.Bootstrap; import com.sun.star.uno.XNamingService; import com.sun.star.bridge.XUnoUrlResolver; // Classes for accessing interfaces of the OpenOffice.org API import com.sun.star.lang.XMultiComponentFactory; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.lang.XSingleServiceFactory; import com.sun.star.lang.XComponent; import com.sun.star.uno.XComponentContext; import com.sun.star.uno.UnoRuntime; // Classes for loading and storing documents import com.sun.star.frame.XComponentLoader; import com.sun.star.frame.XStorable; // Classes for property access import com.sun.star.beans.PropertyValue; import com.sun.star.beans.XPropertySet; import com.sun.star.beans.NamedValue; // Classes used for manipulation of text documents import com.sun.star.text.XTextDocument; import com.sun.star.text.XText; import com.sun.star.text.XDependentTextField; // MailMerge Classes import com.sun.star.text.XMailMergeBroadcaster; import com.sun.star.text.XMailMergeListener; import com.sun.star.text.MailMergeEvent; import com.sun.star.task.XJob; // Database access Classes import com.sun.star.sdbc.XRowSet; import com.sun.star.sdbc.XResultSet; import com.sun.star.sdbc.XResultSetMetaDataSupplier; import com.sun.star.sdbc.XResultSetMetaData; public class MailMerger { // These objects are used for creating and accessing OpenOffice.org API objects private XMultiComponentFactory mxMCF; private XMultiServiceFactory mxMSF; private XComponentContext mxComponentContext; private XComponentLoader mxComponentLoader; // The default connection string used to connect to running OpenOffice.org public static final String DEFAULT_CONNECTION_STRING = "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager"; // The URL of the directory with the Data Source tables private String mDataSourceDir; // The name of the Data Source to be used for the mail merge private String mDataSourceName; // The name of the Table in that Data Source to used for the mail merge private String mTableName; // The URL to which the generated template should be saved private String mFileURL; public MailMerger(String dsdir, String file, String db, String table) { mDataSourceDir = dsdir; mFileURL = file; mDataSourceName = db; mTableName = table; } public MailMerger(String file, String db, String table) { mFileURL = file; mDataSourceName = db; mTableName = table; } public static void main(String args[]) { if (args.length < 4) { printUsage(); System.exit(1); } if (args[0].equals("-create") && args.length == 5) { MailMerger mm = new MailMerger(args[1], args[2], args[3], args[4]); mm.setupConnection(); XTextDocument myDoc = mm.openWriter(); mm.createNewDataSource(); mm.insertFields(myDoc); mm.saveAsTemplate(myDoc); } else if (args[0].equals("-merge") && args.length == 4) { MailMerger mm = new MailMerger(args[1], args[2], args[3]); mm.setupConnection(); mm.doMerge(); } else { printUsage(); System.exit(1); } } public static void printUsage() { System.err.println( "Usage: java MailMerger -create <URL of Data Source directory> " + "<URL to save template file> " + "<Data Source name> <Table name>"); System.err.println( "Usage: java MailMerger -merge " + "<URL of template file> " + "<Data Source name> <Table name>"); System.err.println("\ne.g.:"); System.err.println("java MailMerger -create file://" + System.getProperty("user.home") + "/mydatasourcedir file://" + System.getProperty("user.home") + "/mytemplate.stw mydatasource mytable"); System.err.println("java MailMerger -merge file://" + System.getProperty("user.home") + "/mytemplate.stw mydatasource mytable"); } // Refer to DevelopersGuide.pdf, p30-33, "2.3.4 First Connection". public void setupConnection() { try { /* Bootstraps a component context with the jurt base components registered. Component context to be granted to a component for running. Arbitrary values can be retrieved from the context. */ mxComponentContext = Bootstrap.createInitialComponentContext(null); /* Gets the service manager instance to be used (or null). This method has been added for convenience, because the service manager is a often used object. */ mxMCF = mxComponentContext.getServiceManager(); /* Creates an instance of the component UnoUrlResolver which supports the services specified by the factory. */ Object objectUrlResolver = mxMCF.createInstanceWithContext( "com.sun.star.bridge.UnoUrlResolver", mxComponentContext); // Create a new url resolver XUnoUrlResolver xurlresolver = (XUnoUrlResolver) UnoRuntime.queryInterface(XUnoUrlResolver.class, objectUrlResolver); // Resolves an object that is specified as follow: // uno:<connection description>;<protocol description>;<initial // object name> Object objectInitial = xurlresolver.resolve( DEFAULT_CONNECTION_STRING); // Create a service manager from the initial object mxMCF = (XMultiComponentFactory) UnoRuntime.queryInterface(XMultiComponentFactory.class, objectInitial); // Query for the XPropertySet interface. XPropertySet xpropertysetMultiComponentFactory = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, mxMCF); // Get the default context from the office server. Object objectDefaultContext = xpropertysetMultiComponentFactory.getPropertyValue( "DefaultContext"); // Query for the interface XComponentContext. mxComponentContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, objectDefaultContext); /* A desktop environment contains tasks with one or more frames in which components can be loaded. Desktop is the environment for components which can instanciate within frames. */ mxComponentLoader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, mxMCF.createInstanceWithContext( "com.sun.star.frame.Desktop", mxComponentContext)); // Query for an XMultiServiceFactory instance from the global // service manager if (mxMSF == null) { mxMSF = (XMultiServiceFactory)UnoRuntime.queryInterface( XMultiServiceFactory.class, mxComponentContext.getServiceManager()); } } catch(Exception exception) { System.err.println(exception); } } // Refer to DevelopersGuide.pdf, p371, Section 7.2.1 // "Creating and Loading Text Documents" public XTextDocument openWriter() { XTextDocument oDoc = null; XComponent aDoc = null; try { PropertyValue[] loadProps = new PropertyValue[0]; aDoc = mxComponentLoader.loadComponentFromURL( "private:factory/swriter", "_blank", 0, loadProps); oDoc = (XTextDocument) UnoRuntime.queryInterface( XTextDocument.class, aDoc); } catch(Exception e){ System.err.println("Error opening new document" + e); } return oDoc; } // Refer to DevelopersGuide.pdf, p665, Section 12.2.2 // "Adding and Editing Datasources". public void createNewDataSource() { try{ // Retrieve the database context at the global service manager Object dbContext = mxMSF.createInstance( "com.sun.star.sdb.DatabaseContext"); // Get its XSingleServiceFactory interface XSingleServiceFactory factory = (XSingleServiceFactory)UnoRuntime.queryInterface( XSingleServiceFactory.class, dbContext); // Use the XSingleServiceFactory interface to instantiate an // empty data source Object dataSource = factory.createInstance(); // Register it with the database context XNamingService xServ = (XNamingService)UnoRuntime.queryInterface( XNamingService.class, factory); xServ.registerObject(mDataSourceName, dataSource); // Set the necessary data source properties XPropertySet props = (XPropertySet)UnoRuntime.queryInterface( XPropertySet.class, dataSource); props.setPropertyValue("URL", "sdbc:flat:" + mDataSourceDir); PropertyValue[] cProps = new PropertyValue[3]; cProps[0] = new PropertyValue(); cProps[0].Name = "Extension"; cProps[0].Value = "csv"; cProps[1] = new PropertyValue(); cProps[1].Name = "ThousandsDelimiter"; cProps[1].Value = ""; cProps[2] = new PropertyValue(); cProps[2].Name = "FieldDelimiter"; cProps[2].Value = ","; props.setPropertyValue("Info", cProps); } catch(Exception e) { System.err.println ("Error creating data source: " + e); } } /* This method querys the Data Source for a rowset. Using the metadata of the resulting XResultSet we get the title of each column. We insert the title of each column as a field in the text document */ public void insertFields(XTextDocument myDoc) { try { XText xText = myDoc.getText(); // Refer to DevelopersGuide.pdf, p688-691, Section 12.3.1 // "The RowSet Service". The code is based on the example on p690. XMultiServiceFactory docFactory = (XMultiServiceFactory) UnoRuntime.queryInterface (XMultiServiceFactory.class, myDoc); // Get an instance of the XRowSet interface, set up the properties // for the Data Source we want and execute the query XRowSet xRowSet = (XRowSet)UnoRuntime.queryInterface( XRowSet.class, mxMSF.createInstance("com.sun.star.sdb.RowSet")); XPropertySet xProp = (XPropertySet)UnoRuntime.queryInterface( XPropertySet.class, xRowSet); xProp.setPropertyValue("DataSourceName", mDataSourceName); xProp.setPropertyValue("CommandType", new Integer(com.sun.star.sdb.CommandType.TABLE)); xProp.setPropertyValue("Command", mTableName); xRowSet.execute(); // Get the XResultSet interface from xRowSet XResultSet xResultSet = (XResultSet)UnoRuntime.queryInterface( XResultSet.class, xRowSet); // Refer to DevelopersGuide.pdf p711, Section 12.3.4 // "ResultSetMetaData" XResultSetMetaDataSupplier xRsMetaSup = (XResultSetMetaDataSupplier)UnoRuntime.queryInterface( XResultSetMetaDataSupplier.class, xResultSet); XResultSetMetaData xRsMetaData = xRsMetaSup.getMetaData(); int columnCount = xRsMetaData.getColumnCount(); for (int i=1 ;i <= columnCount; ++i) { String columnTitle = xRsMetaData.getColumnName(i); /* Refer to DevelopersGuide.pdf, p405-411, Section 7.3.5 "Text Fields". NB: A database field must be linked to a FieldMaster field. p411 contains an example with a Datefield. The same principles apply here as in Datefield example */ Object dbobj = docFactory.createInstance( "com.sun.star.text.TextField.Database"); XDependentTextField xUserField = (XDependentTextField) UnoRuntime.queryInterface ( XDependentTextField.class, dbobj); // Set the Content property on the XDependentTextField // Otherwise that text field will appear without any text in // the document XPropertySet props = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xUserField); props.setPropertyValue("Content", "<" + columnTitle + ">"); // Set up a FieldMaster.Database instance Object fmobj = docFactory.createInstance( "com.sun.star.text.FieldMaster.Database"); XPropertySet mprops = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, fmobj); mprops.setPropertyValue ("DataBaseName", mDataSourceName); mprops.setPropertyValue ("CommandType", new Integer(0)); mprops.setPropertyValue ("DataColumnName", columnTitle); mprops.setPropertyValue ("DataTableName", mTableName); // Attach the field master to the user field xUserField.attachTextFieldMaster (mprops); xText.insertTextContent(xText.getEnd(), xUserField, false); xText.insertString(xText.getEnd(), "\n some text ", false); } } catch (Exception e) { System.err.println("Error inserting fields: " + e); } } // Refer to DevelopersGuide.pdf, p372, Section 7.2.2 "Exporting". public void saveAsTemplate(XTextDocument doc) { try { // Query for XStorable interface of object XStorable xStorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, doc); // Set up the Overwrite and FilterName properties PropertyValue[] propertyvalue = new PropertyValue[2];; propertyvalue[0] = new PropertyValue(); propertyvalue[0].Name = "Overwrite"; propertyvalue[0].Value = new Boolean(true); propertyvalue[1] = new PropertyValue(); propertyvalue[1].Name = "FilterName"; propertyvalue[1].Value = "swriter: StarOffice XML (Writer)"; // Store the document xStorable.storeAsURL(mFileURL, propertyvalue); } catch (Exception e) { System.err.println("Error while saving: " + e); } } /* The com.sun.star.text.MailMerge service is a new API introduced in OpenOffice.org 1.1. It provides programmatic access to the Tools/Mail Merge feature available via the OpenOffice.org 1.1 UI. As the feature is new to OpenOffice.org 1.1, it is not documented in the Developers Guide or in the API reference. It should be available once the OpenOffice.org 1.1 SDK is released We were able to find out how to use the API by reading the source code of the idl files for the MailMerge service: http://api.openoffice.org/source/browse/api/offapi/com/sun/star/text/MailMerge.idl and also by looking at the tests that had been written for the service by the QA project: http://qa.openoffice.org/source/browse/qa/qadevOOo/tests/java/mod/_sw/SwXMailMerge.java If all else fails then you can probably get some help on the dev@api.openoffice.org mailing list */ public void doMerge() { Object mmservice = null; try { // Create an instance of the MailMerge service mmservice = mxMCF.createInstanceWithContext( "com.sun.star.text.MailMerge", mxComponentContext); } catch (Exception e) { System.err.println("Error getting MailMerge service: " + e); return; } // Get the XPropertySet interface of the mmservice object XPropertySet oObjProps = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, mmservice); try { // Set up the properties for the MailMerge command oObjProps.setPropertyValue("DataSourceName", mDataSourceName); oObjProps.setPropertyValue("Command", mTableName); oObjProps.setPropertyValue("CommandType", new Integer(com.sun.star.sdb.CommandType.TABLE)); oObjProps.setPropertyValue("OutputType", new Short(com.sun.star.text.MailMergeType.PRINTER)); oObjProps.setPropertyValue("DocumentURL", mFileURL); } catch (Exception e) { System.err.println("Error setting MailMerge properties" + e); return; } // Get XJob interface from MailMerge service and call execute on it XJob job = (XJob) UnoRuntime.queryInterface(XJob.class, mmservice); try { job.execute(new NamedValue[0]); } catch (com.sun.star.lang.IllegalArgumentException iae) { System.err.println("Caught IllegalArgumentException: " + iae); } catch (com.sun.star.uno.Exception e) { System.err.println("Caught UNO Exception: " + e); } } }
Initial version