Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move from active to passive component registration #57

Open
1 of 3 tasks
smehrbrodt opened this issue Jun 12, 2017 · 26 comments
Open
1 of 3 tasks

Move from active to passive component registration #57

smehrbrodt opened this issue Jun 12, 2017 · 26 comments

Comments

@smehrbrodt
Copy link
Contributor

smehrbrodt commented Jun 12, 2017

Drop RegistrationHandler and use .component files instead.

  • Detect and package .component files
  • Generate .component files when creating a new project
  • Remove active component registration code from new project generator
smehrbrodt added a commit that referenced this issue Feb 17, 2025
When a <procjectName>.component file exists in the project root,
add it to the extension.

This allows to use passive component registration for extensions.
@smehrbrodt
Copy link
Contributor Author

.component files will now be detected if they are placed in the project root with the name <projectName>.component.

@smehrbrodt
Copy link
Contributor Author

@prrvchr Help appreciated with the two remaining tasks :)

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

@smehrbrodt , could you explain to me a little better what this change is? Thanks.

@smehrbrodt
Copy link
Contributor Author

Extensions need to tell LibreOffice about themselves - which services they offer and which implementation for these services they have.
Historically that is done with calls to __writeRegistryServiceInfo (see RegistrationHandler.java which is included in each extension generated by LOEClipse).

Nowadays, extensions no longer need to actively call that __writeRegistryServiceInfo method, but instead can supply that information via an xml file (xyz.component).

Here is a simple example for a project called DevTest:

<component loader="com.sun.star.loader.Java2"
    xmlns="http://openoffice.org/2010/uno-components">
  <implementation name="org.libreoffice.example.comp.DevTestImpl">
    <service name="org.libreoffice.example.DevTest"/>
  </implementation>
</component>

Currently, when you save that file as DevTest.component, it will be added to the oxt package, and the RegistrationHandler.java and also the RegistrationHandler.classes can be deleted.

What needs doing now is to adjust the code which writes RegistrationHandler.classes to instead write the corresponding component XML file (so users don't need to create it manually). See code in java/source/org/libreoffice/ide/eclipse/java/registration for that.

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

For now the entry in the JAR archive is given by the property RegistrationClassName of the MANIFEST.MF.
This allows to find the class RegistrationHandler.class in the archive.

After that the services offered by the archive are in the file RegistrationHandler.classes (ie: one service per line that gives the name of the class).

How is this mechanism reproduced with the use of xml files?

@stbergmann
Copy link

Yes, the __getComponentFactory method of the class named by RegistrationClassName is still needed by passive component registration. Only the __writeRegistryServiceInfo method is replaced by the XML file. (In the LO core repo, you can compare e.g. desktop/test/deployment/active/com/sun/star/comp/test/deployment/Services.java and desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services.java.)

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

It seems to me that adding a __writeRegistryServiceInfo method to a Class in Eclipse will update the file RegistrationHandler.classes. If this method is no longer used, what addition triggers the update of the xml file?

In fact, how do we recognize that it is indeed a Class implementation of a UNO service?

@smehrbrodt
Copy link
Contributor Author

smehrbrodt commented Feb 17, 2025

The RegistrationsHandler.classes is written by RegistrationHelper::writeClassesList.

That method needs to be adapted to write the component xml file instead.

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

Ok this is about creating or updating a project.component file. I assume that the xml implementation node can be present multiple times, i.e. one per service?

But if we don't write to the RegistrationsHandler.classes file anymore, then how will active registration still work?

@smehrbrodt
Copy link
Contributor Author

But if we don't write to the RegistrationsHandler.classes file anymore, then how will active registration still work?

Ah right, we need to keep writing to it when it already exists in the project.

But we can remove the RegistrationHandler.java.tpl and the code that generates the RegistrationHandler when creating a new project.

@stbergmann
Copy link

I assume that the xml implementation node can be present multiple times, i.e. one per service?

Yes, a component element can have zero or more implementation child elements (which in turn can have zero or more service and singleton child elements).

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

Yes, the __getComponentFactory method of the class named by RegistrationClassName is still needed by passive component registration.

If this method is to be kept in order to perform active and passive registration then the RegistrationsHandler.classes file must be keep and the RegistrationHandler.java.tpl file must be adapted (ie: only provide the method __getComponentFactory).

Am I wrong or not?

@smehrbrodt
Copy link
Contributor Author

If this method is to be kept in order to perform active and passive registration then the RegistrationsHandler.classes file must be keep and the RegistrationHandler.java.tpl file must be adapted (ie: only provide the method __getComponentFactory).

Yeah you are right, we can't delete it, the .tpl file must be adapted as you said.

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

Yes and maybe the method __getComponentFactory should now fetch the services from the xml file?

@smehrbrodt
Copy link
Contributor Author

Yep makes sense.

@prrvchr
Copy link
Contributor

prrvchr commented Feb 17, 2025

I have a problem.

When adding or removing an implementation it is necessary to know the name of the implementation class but also the name of the UNO service of this implementation (both information seems necessary in the project.component xml file).

I don't really see how to get the service name from the implementation file?

@prrvchr
Copy link
Contributor

prrvchr commented Feb 18, 2025

Well if I follow the example it seems that the RegistrationHandler.class file must be replaced by Services.java

On the other hand, I am surprised by the value of the RegistrationClassName property in the MANIFEST.MF file. Does this path really exist?

@smehrbrodt
Copy link
Contributor Author

On the other hand, I am surprised by the value of the RegistrationClassName property in the MANIFEST.MF file. Does this path really exist?

Yes, see this class: https://opengrok.libreoffice.org/xref/core/desktop/test/deployment/passive/com/sun/star/comp/test/deployment/Services.java?r=ffb1e88afae5b83d58337a84a27f9c6293f6fcd9#19

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

Well I can generate the .component xml file when modifying a .java file in Eclipse if the latter has the specificities:

  • A ctor with XComponentContext has parameter
  • The com.sun.star.lang.XServiceInfo interface implemented
  • A static field SERVICE_NAME

But if I try to install this extension then I have the error message:

Image

It seems to me that LibreOffice does not differentiate a passive registration from an active registration?

@smehrbrodt
Copy link
Contributor Author

Can you share the resulting oxt file?

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

The oxt file is 55.7 MiB in size (it's the jdbcDriverOOo extension) I can't upload because of the size I guess.

But this last version installs correctly, however the only difference is that it uses the .component file instead of the .classes file.

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

Here is what I found for the RegistrationHandler.class.

/*************************************************************************
 *
 * The Contents of this file are made available subject to the terms of
 * either of the GNU Lesser General Public License Version 2.1
 *
 * Sun Microsystems Inc., October, 2000
 *
 *
 * GNU Lesser General Public License Version 2.1
 * =============================================
 * Copyright 2000 by Sun Microsystems, Inc.
 * 901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 * 
 * The Initial Developer of the Original Code is: Sun Microsystems, Inc..
 *
 * Copyright: 2002 by Sun Microsystems, Inc.
 *
 * All Rights Reserved.
 *
 * Contributor(s): Cedric Bosdonnat
 *
 *
 ************************************************************************/
package io.github.prrvchr.driver;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

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;

import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lib.uno.helper.Factory;
import com.sun.star.registry.XRegistryKey;


/**
 * Component main registration class.
 * 
 * <p><strong>This class should not be modified.</strong></p>
 * 
 * @author Cedric Bosdonnat aka. cedricbosdo
 *
 */
public class RegistrationHandler {


    /**
    * Get a component factory for the implementations handled by this class.
    *
    * <p>This method calls all the methods of the same name from the classes listed
    * in the <code>RegistrationHandler.classes</code> file. <strong>This method
    * should not be modified.</strong></p>
    *
    * @param implementation the name of the implementation to create.
    *
    * @return the factory which can create the implementation.
    */

    public static XSingleComponentFactory __getComponentFactory(final String implementation) {
        System.out.println("RegistrationHandler.__getComponentFactory() 1 impl: " + implementation);
        XSingleComponentFactory factory = null;
        final String service = findServiceName(implementation);
        if (service != null) {
            try {
                final Class<?> clazz = Class.forName(implementation);
                factory = Factory.createComponentFactory(clazz, new String[] {implementation});
            } catch (Exception e) {
                // Nothing to do: skip
                System.err.println("Error happened");
                e.printStackTrace();
            }
        }
        return factory;
    }

    /**
    * Writes the services implementation informations to the UNO registry.
    *
    * <p>This method calls all the methods of the same name from the classes listed
    * in the <code>RegistrationHandler.classes</code> file. <strong>This method
    * should not be modified.</strong></p>
    *
    * @param registryKey the root registry key where to write the informations.
    *
    * @return <code>true</code> if the informations have been successfully written
    * to the registry key, <code>false</code> otherwise.
    */

    public static boolean __writeRegistryServiceInfo(final XRegistryKey registryKey) {

        System.out.println("RegistrationHandler.__writeRegistryServiceInfo() 1 KeyName: " + registryKey.getKeyName());
        Map<String, String[]> implementations = findImplementationNames();
        boolean success = true;
        for (Entry<String, String[]> entry : implementations.entrySet()) {
            try {
                success &= Factory.writeRegistryServiceInfo(entry.getKey(), entry.getValue(), registryKey);
            } catch (Exception e) {
                success = false;
                e.printStackTrace();
            }
            if (!success) {
                break;
            }
        }
        return success;
    }

    /**
     *
     * @return the implementation names. 
     */
    private static Map<String, String[]> findImplementationNames() {

        Map<String, String[]> implementations = new HashMap<>();
        InputStream in = RegistrationHandler.class.getResourceAsStream(getComponentFile());
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            Document document = factory.newDocumentBuilder().parse(in);
            NodeList nodes = document.getElementsByTagName("implementation");
            for (int i = 0; i < nodes.getLength(); i++) {
                Node node = nodes.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    NodeList services = element.getElementsByTagName("service");
                    if (services.getLength() > 0) {
                        implementations.put(getNameAttribute(node), new String[]{getNameAttribute(services.item(0))});
                    }
                }
            }
        } catch (IOException | SAXException | ParserConfigurationException e) { }
        return implementations;
    }

    /**
     * @param implementation the name of the implementation.
     *
     * @return the UNO service name. 
     */
    private static String findServiceName(String implementation) {

        String serviceName = null;
        InputStream in = RegistrationHandler.class.getResourceAsStream(getComponentFile());
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            Document document = factory.newDocumentBuilder().parse(in);
            NodeList implementations = document.getElementsByTagName("implementation");
            for (int i = 0; i < implementations.getLength(); i++) {
                Node node = implementations.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    if (implementation.equals(getNameAttribute(element))) {
                        NodeList services = element.getElementsByTagName("service");
                        if (services.getLength() > 0) {
                            serviceName = getNameAttribute(services.item(0));
                            break;
                        }
                    }
                }
            }
        } catch (IOException | SAXException | ParserConfigurationException e) { }
        return serviceName;
    }

    private static String getNameAttribute(Node node) {
        String value = null;
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            value = getNameAttribute((Element) node);
        }
        return value;
    }

    private static String getNameAttribute(Element element) {
        String value = null;
        if (element.hasAttribute("name")) {
            value = element.getAttribute("name");
        }
        return value;
    }

    private static String getComponentFile() {
        return "jdbcDriverOOo.component";
    }

}

And the corresponding .component xml file located in the same folder.

<?xml version="1.0" encoding="UTF-8"?><component xmlns="http://openoffice.org/2010/uno-components" loader="com.sun.star.loader.Java2">
    <implementation name="io.github.prrvchr.uno.sdbc.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdbc.Driver"/>
    </implementation>
    <implementation name="io.github.prrvchr.uno.sdbcx.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdbcx.Driver"/>
    </implementation>
    <implementation name="io.github.prrvchr.uno.sdb.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdb.Driver"/>
    </implementation>
</component>

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

I don't really understand the advantage of passive registration over active registration.

The main difference is the absence of the two static methods:

  • __getComponentFactory(String service)
  • __writeRegistryServiceInfo(XRegistryKey key)
    in classes declared as UNO service in Eclipse.

And it seems that it is not possible to get rid of the __writeRegistryServiceInfo(final XRegistryKey registryKey) method in the RegistrationHandler.class file?

Did I miss something?

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

In order for passive registration to work, it is necessary to declare the xml .component file in the META-INF/manifest.xml file and to put a copy outside the JAR file.
And indeed the file is read when installing an extension but stops with the error:

Image

I have trouble reading the source code and explaining this error

@prrvchr
Copy link
Contributor

prrvchr commented Feb 24, 2025

With an XML file like this everything works perfectly... The uri attribute is important....

<?xml version="1.0" encoding="UTF-8"?><components xmlns="http://openoffice.org/2010/uno-components">
<component loader="com.sun.star.loader.Java2" uri="jdbcDriverOOo.jar">
    <implementation name="io.github.prrvchr.uno.sdbc.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdbc.Driver"/>
    </implementation>
    <implementation name="io.github.prrvchr.uno.sdbcx.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdbcx.Driver"/>
    </implementation>
    <implementation name="io.github.prrvchr.uno.sdb.Driver">
        <service name="io.github.prrvchr.jdbcdriver.sdb.Driver"/>
    </implementation>
</component>
</components>

@smehrbrodt
Copy link
Contributor Author

Looks good! The main advantage is less code that people don't understand, replaced with a simple readable xml file.
Just push the PR once you are ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants