0

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

Ben
  • 6,321
  • 9
  • 40
  • 76

2 Answers2

2

Annotate the list with @Reference:

@Reference(service = FileReaderService.class)
private List<FileReaderService> availableServices;

You will need DS 1.3 fo this to work. The newest felix scr version supports this.

I would not recommend to export the FileReaderFactory as a FileReaderService as it could cause a recursion.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
  • Unfortunately, I use the latest version of karaf, which ships with Felix SCR 1.8.2. As far as I know, it's not compatible with DS 1.3. I agree with your recommendation to prevent the factory from being a FileReaderService itself. – Ben Jan 10 '16 at 21:00
  • In previous versions of DS, would a method such as the following one work? @Reference public void addService(final FileReaderService serviceToAdd) { availableServices.add(serviceToAdd); } – Ben Jan 10 '16 at 21:20
  • The newest karaf supports scr 2. With older versions you need a bind and unbind method. – Christian Schneider Jan 11 '16 at 06:25
  • Indeed. But I can't get it to compile. My project depends on org.osgi.compendium version 5. What did I miss? – Ben Jan 11 '16 at 11:08
  • For DS 1.3 you need the OSGi specs version 6. See my example here: https://github.com/cschneider/Karaf-Tutorial/tree/master/tasklist-ds – Christian Schneider Jan 11 '16 at 12:21
  • Thanks. I switched from org.osgi.compendium version 5 to osgi.cmpn version 6. However, Karaf fails when starting: cannot register Component org.osgi.service.component.ComponentException: Component FileReaderFactory validation failed: Field value type must not be set for unary field references. What does this mean? – Ben Jan 11 '16 at 13:08
  • Probably you left the "= new ArrayList<>();" in. Try to remove it. – Christian Schneider Jan 11 '16 at 13:21
0

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

Ben
  • 6,321
  • 9
  • 40
  • 76