Using Services

From Apache OpenOffice Wiki
Jump to: navigation, search



The concepts of interfaces and services were introduced for the following reasons:

Interfaces and services separate specification from implementation

The specification of an interface or service is abstract, that is, it does not define how objects supporting a certain functionality do this internally. Through the abstract specification of the Apache OpenOffice API, it is possible to pull the implementation out from under the API and install a different implementation if required.

Service names allow to create instances by specification name, not by class names

In Java or C++ you use the new operator to create a class instance. This approach is restricted: the class you get is hard-coded. You cannot later on exchange it by another class without editing the code. The concept of services solves this. The central object factory in Apache OpenOffice, the global service manager, is asked to create an object that can be used for a certain purpose without defining its internal implementation. This is possible because a service can be ordered from the factory by its service name and the factory decides which service implementation it returns. Which implementation you get makes no difference, you only use the well-defined interface of the service.

Interfaces

Abstract interfaces are more reusable if they are fine-grained, i.e., if they are small and describe only a single aspect of an object. To describe the many aspects of an object, objects can implement more than one of these fine-grained interfaces. Being able to implement multiple interfaces allows similar aspects of similar objects to be accessed with the same code. For example, many objects support text: text may be found in the body of a document, in text frames, in headers and footers, footnotes, table cells, and in drawing shapes. These objects all support the same interface, so a procedure can use, for example, getText() to retrieve text from any of these objects.

Services, interfaces, and methods are illustrated in the figure below for the old-style service com.sun.star.text.TextDocument, shown using UML notation. In this figure, services are shown on the left side. The arrow between services indicates that one service provided by the upper (arrowhead) service are inherited by the lower service. Interfaces exported by these services are shown on the right. All interface names in the Apache OpenOffice API start with an X, so as to be distinguishable from the names of other entities. Each interface contains methods, which are listed beneath the interface.

Figure. TextDocument inherits the methods of OfficeDocument.

A TextDocument object provides the com.sun.star.text.TextDocument service, which implements the interfaces, XTextDocument, XSearchable, and XRefreshable. These interfaces provide, for example, the methods getText(), for adding text to a document, and findAll(), for searching the document.

As indicated by the arrow, the com.sun.star.text.TextDocument service also inherits all the interfaces provided by the com.sun.star.document.OfficeDocument service, so these interfaces are also provided to a TextDocument object. These interfaces handle tasks common to the Apache OpenOffice applications: printing, XPrintable; storing, XStorable; modifying, XModifiable; and model handling, XModel.

The interfaces shown in the figure are only the mandatory interfaces of a TextDocument object. A TextDocument has optional properties and interfaces, among them the properties CharacterCount, ParagraphCount and WordCount, and the interface XPropertySet, which must be supported if properties are present at all. The implementation of the TextDocument service in Apache OpenOffice supports both required and all optional interfaces as well. The usage of a TextDocument is described thoroughly in Text Documents.

C++ and Java require that the interface name be provided when accessing a method. An old-style service may provide several interfaces to keep track of. New-style services are easier to use because, since they have just one interface, the multiple-inheritance interface, all the methods are accessed through the same interface.

Using Interfaces

The fact that every UNO object must be accessed through its interfaces has an effect in languages like Java and C++, where the compiler needs the correct type of an object reference before you can call a method from it. In Java or C++, you normally just cast an object before you access an interface it implements. When working with UNO objects this is different: You must ask the UNO environment to get the appropriate reference for you whenever you want to access methods of an interface which your object supports, but your compiler does not yet know about. Only then you can cast it safely.

The Java UNO environment has a method queryInterface() for this purpose. It looks complicated at first sight, but once you understand that queryInterface() is about safe casting of UNO types across process boundaries, you will soon get used to it. Take a look at the second example FirstLoadComponent.java (in the sample directory, if you have installed the SDK on your computer), where a new Desktop object is created and, afterwards, the queryInterface() method is used to get the XComponentLoader interface.

  Object desktop = xRemoteServiceManager.createInstanceWithContext(
                "com.sun.star.frame.Desktop", xRemoteContext);
 
  XComponentLoader xComponentLoader = (XComponentLoader)
                UnoRuntime.queryInterface(XComponentLoader.class, desktop);

We asked the service manager to create a com.sun.star.frame.Desktop using its factory method createInstanceWithContext(). This method is defined to return a Java Object type, which should not surprise you—after all the factory must be able to return any type:

  java.lang.Object createInstanceWithContext(String serviceName, XComponentContext context)

The object we receive is a com.sun.star.frame.Desktop service. The point is, while we know that the object we ordered at the factory is a DesktopUnoUrlResolver and exports among other interfaces the interface XComponentLoader, the compiler does not. Therefore, we have to use the UNO runtime environment to ask or query for the interface XComponentLoader, since we want to use the loadComponentFromURL() method on this interface. The method queryInterface() makes sure we get a reference that can be cast to the needed interface type, no matter if the target object is a local or a remote object. There are two queryInterface definitions in the Java UNO language binding:

  java.lang.Object UnoRuntime.queryInterface(java.lang.Class targetInterface, Object sourceObject)
  java.lang.Object UnoRuntime.queryInterface(com.sun.star.uno.Type targetInterface, Object sourceObject)

Since UnoRuntime.queryInterface() is specified to return a java.lang.Object just like the factory method createInstanceWithContext(), we still must explicitly cast our interface reference to the needed type. The difference is that after queryInterface() we can safely cast the object to our interface type and, most important, that the reference will now work even with an object in another process. Here is the queryInterface() call, explained step by step:

  XComponentLoader xComponentLoader = (XComponentLoader)
                  UnoRuntime.queryInterface(XComponentLoader.class, desktop);

XComponentLoader is the interface we want to use, so we define a XComponentLoader variable named xComponentLoader (lower x) to store the interface we expect from queryInterface. Then we query our desktop object for the XComponentLoader interface, passing in XComponentLoader.class as target interface and desktop as source object. Finally we cast the outcome to XComponentLoader and assign the resulting reference to our variable xComponentLoader. If the source object does not support the interface we are querying for, queryInterface() will return null.

In Java, this call to queryInterface() is necessary whenever you have a reference to an object which is known to support an interface that you need, but you do not have the proper reference type yet. Fortunately, you are not only allowed to queryInterface() from java.lang.Object source types, but you may also query an interface from another interface reference, like this:

  // loading a blank spreadsheet document gives us its XComponent interface:
  XComponent xComponent = xComponentLoader.loadComponentFromURL(
  "private:factory/scalc", "_blank", 0, loadProps);
 
  // now we query the interface XSpreadsheetDocument from xComponent
  XSpreadsheetDocument xSpreadsheetDocument = (XSpreadsheetDocument)UnoRuntime.queryInterface(
                  XSpreadsheetDocument.class, xComponent);

Furthermore, if a method is defined in such a way that it already returns an interface type, you do not need to query the interface, but you can use its methods right away. In the snippet above, the method loadComponentFromURL is specified to return an com.sun.star.lang.XComponent interface, so you may call the XComponent methods addEventListener() and removeEventListener() directly at the xComponent variable, if you want to be notified that the document is being closed. The corresponding step in C++ is done by a Reference<> template that takes the source instance as parameter:

  // instantiate a sample service with the servicemanager.
  Reference< XInterface > rInstance =
  rServiceManager->createInstanceWithContext( 
  OUString::createFromAscii("com.sun.star.frame.Desktop" ),
  rComponentContext );
 
  // Query for the XComponentLoader interface
  Reference< XComponentLoader > rComponentLoader( rInstance, UNO_QUERY );

In Apache OpenOffice Basic, querying for interfaces is not necessary; the Basic runtime engine takes care of that internally. With the proliferation of multiple-inheritance interfaces in the Apache OpenOffice API, there will be less of a demand to explicitly query for specific interfaces in Java or C++. For example, with the hypothetical interfaces

  interface XBase1 {   void fun1();
  };
  interface XBase2 {
      void fun2();
  };
  interface XBoth { // inherits from both XBase1 and XBase2
      interface XBase1;
      interface XBase2;
  };
  interface XFactory {
      XBoth getBoth();};

you can directly call both fun1() and fun2() on a reference obtained through XFactory.getBoth(), without querying for either XBase1 or XBase2.

Using Properties

An object must offer its properties through interfaces that allow you to work with properties. The most basic form of these interfaces is the interface com.sun.star.beans.XPropertySet. There are other interfaces for properties, such as com.sun.star.beans.XMultiPropertySet, that gets and sets a multitude of properties with a single method call. The XPropertySet is always supported when properties are present in a service.

In XPropertySet, two methods carry out the property access, which are defined in Java as follows:

  void setPropertyValue(String propertyName, Object propertyValue)
  Object getPropertyValue(String propertyName)

In the FirstLoadComponent example, the XPropertySet interface was used to set the CellStyle property at a cell object. The cell object was a com.sun.star.sheet.SheetCell and therefore supports also the com.sun.star.table.CellProperties service which had a property CellStyle. The following code explains how this property was set:

  // query the XPropertySet interface from cell object
  XPropertySet xCellProps = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, xCell);
 
  // set the CellStyle property
  xCellProps.setPropertyValue("CellStyle", "Result");

You are now ready to start working with an Apache OpenOffice document.

Content on this page is licensed under the Public Documentation License (PDL).
Personal tools
In other languages