OO-Snippets: Mail Merger

Commons

Keywordsmailmerge
LanguageJava
ApplicationWriter
AuthorsTomas O Connor (initial)
Supported Versions
Supported OS
Question How do I execute a MailMerge using the OpenOffice.org API
Answer

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

Code-Snippet-Listing (snippet-source)

/*
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);
        }
    }
} 

Changelog

DateUserModification
2004-09-30toconnorInitial version

and