Search code examples
javaosgideclarative-servicesmaven-bundle-plugin

How to manage several service instances with declarative services?


I'm trying to build a service in OSGi that would read files of a given format.

The service interface looks like this:

public interface FileReaderService {
    /**
     * Reads the given file.
     * @param filePath Path of the file to read
     * @return the data object built from the file
     * @throws IOException if there is an error while reading the file
     */
    Data readFile(Path filePath) throws IOException;

    /**
     * Detects if the format of the provided file is supported.
     * @param filePath the file to check
     * @return true if the format of the file is supported, false otherwise
     * @throws IOException if there is an error while reading the file
     */
    boolean isFormatSupported(Path filePath) throws IOException;
}

The Data object is a class that defines the data structure of the files to read (they are supposed to contain the same kind of data).

The idea is to have different service implementations, such as:

public class TxtFileReader implements FileReaderService {

    @Override
    public Data readFile(Path filePath) throws IOException {
            // Do something smart with the file
            return data;
        }
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
        return matcher.matches(filePath);
    }
}

There could also be other implementations, such as XmlFileReader, MdFileReader, etc.

Finally, I want a FileReaderFactory like this:

@Component
public class FileReaderFactory implements FileReaderService {

    private List<FileReaderService> availableServices = new ArrayList<>();

    @Override
    public Data readFile(Path filePath) throws IOException {

        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return reader.readFile(filePath);
            }
        }

        return null;
    }

    @Override
    public boolean isFormatSupported(Path filePath) throws IOException {
        for (FileReaderService reader : availableServices) {
            if (reader.isFormatSupported(filePath)) {
                return true;
            }
        }
        return false;
    }
}

What I would like is DS to inject the FileReaderServices in the factory list dynamically. Depending on how many services are provided, I would support (or not) a given file format.

My questions are thus:

1) Is this feasible with DS?

2) If yes, how to do it with DS annotations?

3) If not, how would you do it?

Thanks

Edit: I tried Christian's solution, but it didn't work (yet). Full code can be downloaded here: https://github.com/neopium/FileReader


Solution

  • As indicated by Christian Schneider, the solution was to use the @Reference annotation.

    Karaf did however crash when I used it as specified in his answer:

    Cannot register Component
    org.osgi.service.component.ComponentException: Component org.test.reader.service.factory.FileReaderFactory validation failed: Field value type must not be set for unary field references.
    

    By looking in Felix SCR source code I found the lines causing the exception in class org.apache.felix.scr.impl.metadata.ReferenceMetadata:

    // field value type
    if ( !m_isMultiple )
    {
        // value type must not be specified for unary references
        if ( m_field_collection_type != null )
        {
            throw componentMetadata.validationFailure( "Field value type must not be set for unary field references." );
        }
    }
    

    It seems Felix SCR is not happy because the attribute m_field_collection_type is set to "service" in the component XML (i.e. not null), while no cardinality is specified...

    I thus changed my annotation like this:

    @Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
    private List<FileReaderService> availableServices;
    

    I also added a factory service consumer in order to test the application and it works!

    For those interested, the fixed source code is available here: https://github.com/neopium/FileReader