0

I have this component,

package com.path.http.impl;

import com.path.api.http.ClientManager;
import com.path.http.ConnectionManager;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;

import javax.ws.rs.client.Client;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Component
@Service
public final class DefaultClientManager implements ClientManager {
    @Reference(cardinality        = ReferenceCardinality.OPTIONAL_MULTIPLE,
               policy             = ReferencePolicy.DYNAMIC,
               bind               = "bindConnectionManager",
               unbind             = "unbindConnectionManager",
               referenceInterface = ConnectionManager.class)
    private final ConcurrentMap<String, Client> clients = new ConcurrentHashMap<>();

    @Override
    public Optional<Client> getClient(final String id) {
        return Optional.ofNullable(this.clients.get(id));
    }

    protected void bindConnectionManager(final ConnectionManager   connectionManager,
                                         final Map<String, Object> properties) {
        final Client client = connectionManager.getClient();
        this.clients.put(
            PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID), ""),
            client
        );
    }

    protected void unbindConnectionManager(final ConnectionManager   connectionManager,
                                           final Map<String, Object> properties) {
        this.clients.remove(
            PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID), "")
        );
    }
}

And I wanted to update it from the SCR annotations to the newer OSGI ones.

I wound up with something like:

package com.path.http.impl;

import com.path.http.ClientManager;
import com.path.http.ConnectionManager;
import com.path.http.impl.DefaultConnectionManagerImpl;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;

import jakarta.ws.rs.client.Client;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Component(service = ClientManager.class)
public final class DefaultClientManager implements ClientManager {
    @Reference(cardinality = ReferenceCardinality.MULTIPLE,
               policy      = ReferencePolicy.DYNAMIC,
               bind        = "bindConnectionManager",
               unbind      = "unbindConnectionManager",
               service     = ConnectionManager.class)
    private final ConcurrentMap<String, Client> clients = new ConcurrentHashMap<String, Client>();

    @Override
    public Optional<Client> getClient(final String id) {
        return Optional.ofNullable(this.clients.get(id));
    }

    protected void bindConnectionManager(final ConnectionManager   connectionManager,
                                         final Map<String, Object> properties) {
        this.clients
            .put(PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID),
                                         ""),
                 connectionManager.getClient());
    }

    protected void unbindConnectionManager(final ConnectionManager   connectionManager,
                                           final Map<String, Object> properties) {
        this.clients
            .remove(PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID),
                                            ""));
    }
}

Now, I still don't understand everything so there are some parts which make me uncertain (for example, can I expect my bind and unbind methods to still get passed a Map of properties, with OSGI?).

But the primary issue I'm running into is these two errors from my Maven build:

[ERROR] …/src/main/java/com/path/http/impl/DefaultClientManager.java:[22,30] cannot find symbol
  symbol:   method bind()
  location: @interface org.osgi.service.component.annotations.Reference
[ERROR] …/src/main/java/com/path/http/impl/DefaultClientManager.java:[20,5] annotation type not applicable to this kind of declaration

Removing bind from the @Reference annotation gets rid of the first error (though not what I want to do, in the end) but the second one always remains, even if I get rid of all optional elements.

Reading the JavaDocs, it does say, "Annotated field - There is no bind method name," but that's also in the context of what it defaults to if the optional element isn't provided so it's not clear to me whether bind can be or cannot be used with a field, like this (unbind has the same documentation wording but does not throw an error when I leave it).

As far as I can tell, I'm creating a 1-to-1 conversion of the original class, using the new annotations' and libraries' equivalents so I don't understand why this won't work.

Even if the @Reference wouldn't work on deployment, I'm pretty certain that OSGI @References can be used for fields, still, so I don't understand why Maven is still complaining when I remove all optional elements from the annotation.

At a high level, I'd love to understand how to convert my old component into one which uses OSGI annotations. At a more specific level, I would happily settle to understand why my @Reference annotation doesn't seem to want to work with the field, if I'm using any of the optional elements incorrectly.

My Maven dependencies, for the newer code, are:

<dependency>
  <groupId>org.osgi</groupId>
  <artifactId>org.osgi.service.component.annotations</artifactId>
  <version>1.5.1</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.osgi</groupId>
  <artifactId>org.osgi.service.metatype.annotations</artifactId>
  <version>1.4.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>jakarta.ws.rs</groupId>
  <artifactId>jakarta.ws.rs-api</artifactId>
  <version>3.0.0</version>
</dependency>

<dependency>
  <groupId>org.apache.sling</groupId>
  <artifactId>org.apache.sling.commons.osgi</artifactId>
  <version>2.4.2</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.httpcomponents.client5</groupId>
  <artifactId>httpclient5</artifactId>
  <version>5.2.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-common</artifactId>
  <version>3.1.1</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-client</artifactId>
  <version>3.1.1</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.connectors</groupId>
  <artifactId>jersey-apache-connector</artifactId>
  <version>3.1.1</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-multipart</artifactId>
  <version>3.1.1</version>
</dependency>

Java version is 8; AEM version is 6.5.15 (though I am running into the Maven error before anything actually gets deployed to the running AEM instance).

And ClientManager is just a simple interface which looks like:

package com.path.http;

import jakarta.ws.rs.client.Client;
import java.util.Optional;

public interface ClientManager {
    Optional<Client> getClient(final String id);
}
Jaft
  • 43
  • 2
  • 8

2 Answers2

0

Try to remove final from field. If you want to set via setter then annotate setter with @Reference.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
0

The @Reference annotation is in a wrong place. It should annotate the "bind" method, not the Map. Change the code as follows and you will be good:

@Component(service = ClientManager.class)
public final class DefaultClientManager implements ClientManager {
    private final ConcurrentMap<String, Client> clients = new ConcurrentHashMap<>();

    @Reference(cardinality = ReferenceCardinality.MULTIPLE,
            policy      = ReferencePolicy.DYNAMIC,
            bind        = "bindConnectionManager",
            unbind      = "unbindConnectionManager",
            service     = ConnectionManager.class)
    protected void bindConnectionManager(final ConnectionManager   connectionManager,
                                         final Map<String, Object> properties) {
        this.clients
                .put(PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID),
                                ""),
                        connectionManager.getClient());
    }

    protected void unbindConnectionManager(final ConnectionManager   connectionManager,
                                           final Map<String, Object> properties) {
        this.clients
                .remove(PropertiesUtil.toString(properties.get(DefaultConnectionManagerImpl.MANAGER_ID),
                        ""));
    }

    @Override
    public Optional<Client> getClient(final String id) {
        return Optional.ofNullable(this.clients.get(id));
    }

}
Yegor Kozlov
  • 106
  • 2
  • I'll try that out; is there a reason this annotation cannot be applied to a field, anymore? The documentation (assuming I'm looking in the right place) still says, "When the annotation is applied to a field, …" (https://docs.osgi.org/javadoc/osgi.cmpn/8.1.0/org/osgi/service/component/annotations/Reference.html). – Jaft May 08 '23 at 22:13
  • The documentation sais "When the annotation is applied to a method, the method is the bind method of the reference." and this is what's happening – Yegor Kozlov May 10 '23 at 19:01
  • Correct but it also clearly says that it can, also, be applied to a field; the code I'm working with, originally, was written with the annotation on the field and I could easily find examples online of the same so this use case used to be perfectly acceptable; considering the documentation hasn't changed for several versions, I don't think they meant that "if the field has a corresponding method" but the direct application to either a field or method or etc. – Jaft May 18 '23 at 04:00