OSGI: Использование ServiceFactories? - PullRequest
7 голосов
/ 12 августа 2011

В настоящее время я пытаюсь получить простой пакет, содержащий запущенную Service Factory.

Это мой заводской класс:

public class SvcFactory implements ServiceFactory<ServiceB> {

    @Override
    public ServiceB getService(Bundle bundle,
            ServiceRegistration<ServiceB> registration) {

        return new ServiceBImpl();
    }

    @Override
    public void ungetService(Bundle bundle, ServiceRegistration<ServiceB> registration,
            ServiceB service) {

    }

}

Это мой сервис, который должен быть создан на фабрике:

public class ServiceBImpl implements ServiceB {

    private ServiceA svcA;

    public void setA(ServiceA a) {
        svcA = a;
    }

}

И, наконец, OSGI-INF / component.xml

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="bundleb.internal.SvcFactory">

   <implementation class="bundleb.internal.SvcFactory"/>

  <reference bind="setA" cardinality="1..1" interface="bundlea.ServiceA" name="ServiceA" policy="static"/>

   <service servicefactory="true">
      <provide interface="bundleb.ServiceB"/>
   </service>
</scr:component>

Если я запускаю свои тестовые пакеты (A, B и C) в течение равноденствия, я получаю следующую ошибку:

org.osgi.framework.ServiceException: org.eclipse.equinox.internal.ds.FactoryReg.getService() returned a service object that is not an instance of the service class bundleb.ServiceB

Я не могу найти много информации об использовании ServiceFeactories, объявленных в определении компонента в Интернете. Даже книга «OSGi и Equinox» не рассказывала мне много об их использовании. Может ли кто-нибудь объяснить мне, что я делаю не так?

Ответы [ 3 ]

12 голосов
/ 16 августа 2011

Вот пример использования ComponentFactory, который должен соответствовать вашим потребностям (и содержит простой интеграционный тест, чтобы помочь с вашим другим вопросом ).Отказ от ответственности;код написан плохо, например, для примера.

Некоторые сервисные интерфейсы:

package net.earcam.example.servicecomponent;

public interface EchoService {

    String REPEAT_PARAMETER = "repeat";
    String FACTORY_DS = "echo.factory";
    String NAME_DS = "echo";

    String echo(String message);
}

И:

package net.earcam.example.servicecomponent;

public interface SequenceService {
    long next();
}

Затем реализации:

import static net.earcam.example.servicecomponent.EchoService.FACTORY_DS;
import static net.earcam.example.servicecomponent.EchoService.NAME_DS;
import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY;
import static org.apache.felix.scr.annotations.ReferencePolicy.DYNAMIC;
import net.earcam.example.servicecomponent.EchoService;
import net.earcam.example.servicecomponent.SequenceService;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.osgi.service.component.ComponentContext;

@Component(factory = FACTORY_DS, name = NAME_DS)
public class EchoServiceImp implements EchoService {

    @Reference(cardinality = MANDATORY_UNARY, policy = DYNAMIC)
    private SequenceService sequencer = null;
    private transient int repeat = 1;

    @Activate
protected void activate(final ComponentContext componentContext)
{
    repeat = Integer.parseInt(componentContext.getProperties().get(REPEAT_PARAMETER).toString());
}


@Override
public String echo(final String message)
{
    StringBuilder stringBuilder = new StringBuilder();
    for(int i = 0; i < repeat; i++) {
        addEchoElement(stringBuilder, message);
    }
    return stringBuilder.toString();
}


private void addEchoElement(final StringBuilder stringBuilder, final String message) {
    stringBuilder.append(sequencer.next()).append(' ').append(message).append("\n");        
}


protected void unbindSequencer()
{
    sequencer = null;
}


protected void bindSequencer(final SequenceService sequencer)
{
    this.sequencer = sequencer;
}

}

И:

package net.earcam.example.servicecomponent.internal;

import java.util.concurrent.atomic.AtomicLong;

import net.earcam.example.servicecomponent.SequenceService;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;

/**
 * @author caspar
 */
@Component
@Service
public class SequenceServiceImp implements SequenceService {

    private AtomicLong sequence;


    @Override
    public long next()
    {
        return sequence.incrementAndGet();
    }


    @Activate
    protected void activate()
    {
        sequence = new AtomicLong();
    }


    @Deactivate
    protected void deactivate()
    {
        sequence = null;
    }
}

Интеграционный тест, который управляет всем этим (обратите внимание; есть основной метод, который вы запускаете при запуске / остановке пакетов и т. Д.).

package net.earcam.example.servicecomponent.test;

import static org.ops4j.pax.exam.CoreOptions.*;
import static org.ops4j.pax.exam.OptionUtils.combine;
import static org.ops4j.pax.exam.spi.container.PaxExamRuntime.createContainer;
import static org.ops4j.pax.exam.spi.container.PaxExamRuntime.createTestSystem;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.earcam.example.servicecomponent.EchoService;
import net.earcam.example.servicecomponent.SequenceService;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.ExamReactorStrategy;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.spi.reactors.EagerSingleStagedReactorFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.ComponentInstance;


@ExamReactorStrategy(EagerSingleStagedReactorFactory.class)
@RunWith(JUnit4TestRunner.class)
public class EchoServiceIntegrationTest {


    public static void main(String[] args) {
        try {
            createContainer(
                    createTestSystem(
                            combine(
                                    new EchoServiceIntegrationTest().config(), 
                                    profile("gogo"))
                    )).start();
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }



    @Configuration
    public Option[] config()
    {
        return options(
                felix(),
                equinox(),
                junitBundles(),
                systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("TRACE"),
                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").versionAsInProject(),
                bundle("file:" + findFileInCurrentDirectoryAndBelow(
                        Pattern.compile("net\\.earcam\\.example\\.servicecomponent\\-[\\.\\d]+(\\-SNAPSHOT)?\\.[wj]ar")))
        );
    }


    @Test
    public void bundleContextIsAvailable(BundleContext context)
    {
        Assert.assertNotNull("PAX Exam BundleContext available", context);
    }


    @Test
    public void sequenceServiceIsAvailable(BundleContext context)
    {
        Assert.assertNotNull("SequenceService available", fetchService(context, SequenceService.class));
    }


    @Test
    public void serviceResponseContainsThreeEchos(BundleContext context) throws Exception
    {
        final String message = "message";
        final String expected = "1 " + message + "\n2 " + message + "\n3 " + message + "\n";

        ComponentFactory factory = fetchComponentFactory(context, EchoService.FACTORY_DS);

        Dictionary<String, String> properties = new Hashtable<String, String>();
        properties.put(EchoService.REPEAT_PARAMETER, "3");
        ComponentInstance instance = factory.newInstance(properties);
        EchoService service = (EchoService) instance.getInstance();
        String actual = service.echo(message);
        Assert.assertEquals("Expected response", expected, actual);
    }


    private ComponentFactory fetchComponentFactory(BundleContext context, String componentFactoryId) throws Exception
    {
        String filter = "(component.factory=" + componentFactoryId + ")";
        ServiceReference[] references = context.getServiceReferences(ComponentFactory.class.getCanonicalName(), filter);
        return (references.length) == 0 ?  null : (ComponentFactory) context.getService(references[0]);
    }


    private <T> T fetchService(BundleContext context, Class<T> clazz)
    {
        ServiceReference reference = context.getServiceReference(clazz.getCanonicalName());
        @SuppressWarnings("unchecked")
        T service = (T) context.getService(reference);
        return service;
    }


    private String findFileInCurrentDirectoryAndBelow(final Pattern filePattern) {
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                Matcher matcher = filePattern.matcher(pathname.getName());
                return (matcher.matches());
            }
        };
        return findFile(new File("."), filter, filePattern);
    }


    private String findFile(File directory, FileFilter filter, Pattern filePattern) {
        File[] matches = directory.listFiles(filter);
        if(matches != null && matches.length > 0) {
            return matches[0].getAbsolutePath();
        }
        File[] subdirs = directory.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        });
        for(final File subdir : subdirs) {
            String found = findFile(subdir, filter, filePattern);
            if(!"".equals(found)) {
                return found;
            }
        }
        throw new RuntimeException(new FileNotFoundException("No match for pattern: " + filePattern.pattern()));
    }
}

А вот мавен пом:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.earcam</groupId>
    <artifactId>net.earcam.example.servicecomponent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <version.java.source>1.6</version.java.source>
        <version.java.target>1.6</version.java.target>

        <version.osgi>4.2.0</version.osgi>
        <version.paxexam>2.1.0</version.paxexam>
        <version.paxrunner>1.7.4</version.paxrunner>
        <version.cometd>2.3.1</version.cometd>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>${version.osgi}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.compendium</artifactId>
            <version>${version.osgi}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.scr.annotations</artifactId>
            <version>1.4.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.3.RC2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jmock</groupId>
            <artifactId>jmock-junit4</artifactId>
            <version>2.5.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-junit4</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-container-paxrunner</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-link-assembly</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.exam</groupId>
            <artifactId>pax-exam-testforge</artifactId>
            <version>${version.paxexam}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.ops4j.pax.runner</groupId>
            <artifactId>pax-runner-no-jcl</artifactId>
            <version>${version.paxrunner}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.scr</artifactId>
            <version>1.6.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${version.java.source}</source>
                    <target>${version.java.target}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <!-- Unit Tests -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.8.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <excludes>
                        <exclude>**/*IntegrationTest.java</exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <!-- Integration Tests -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>failsafe-maven-plugin</artifactId>
                <version>2.4.3-alpha-1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <phase>integration-test</phase>
                    </execution>
                </executions>
                <configuration>
                    <includes>
                        <include>**/*IntegrationTest.java</include>
                    </includes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.ops4j.pax.exam</groupId>
                <artifactId>maven-paxexam-plugin</artifactId>
                <version>1.2.3</version>
                <executions>
                    <execution>
                        <id>generate-config</id>
                        <goals>
                            <goal>generate-depends-file</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- Process the DS annotations -->
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-scr-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <id>generate-scr-descriptor</id>
                        <goals>
                            <goal>scr</goal>
                        </goals>
                        <phase>process-classes</phase>
                        <configuration>
                            <strictMode>true</strictMode>
                            <outputDirectory>${project.build.outputDirectory}/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>


            <plugin>
                <!-- Generate OSGi bundle MAINFEST.MF entries -->
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.3.4</version>
                <extensions>true</extensions>
                <configuration>
                    <supportedProjectTypes>
                        <supportedProjectType>jar</supportedProjectType>
                    </supportedProjectTypes>
                    <instructions>
                        <Bundle-Vendor>earcam</Bundle-Vendor>
                        <Service-Component>OSGI-INF/serviceComponents.xml</Service-Component>
                        <!-- PAX mangles this, it uses the name of the project for the symbolicname 
                            of test bundle? <Bundle-SymbolicName>${project.name}</Bundle-SymbolicName> -->
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Export-Package>!${project.artifactId}.internal,${project.artifactId}.*</Export-Package>
                        <Import-Package>*</Import-Package>
                    </instructions>
                </configuration>
                <executions>
                    <execution>
                        <id>bundle-manifest</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>manifest</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project>

Несколько вещей, на которые стоит обратить внимание;Мне нравятся мои интеграционные тесты в модуле, который они тестируют, поэтому mvn clean install deploy не проходит, если мой интеграционный тест проходит, но обычно проекты с одним интеграционным модулем встречаются для всех интеграционных тестов.Это объясняет уродливый метод findFileInCurrentDirectoryAndBelow(Pattern pattern), который используется для поиска пакета текущего модуля в целевом каталоге, а также объясняет нестандартную настройку подключаемых модулей maven-bundle-plugin и maven-scr-plugin.

Также способ, которым Pax-Exam собирает зависимости, требует запуска сборки maven для каждого изменения зависимостей и конфигурации (например, импорт / экспорт пакета, изменения DS).Но как только это будет сделано, вы можете запустить / отладить тесты из Eclipse.

Я поместил проект в тарбол здесь

HTH =)

1 голос
/ 19 августа 2011

На самом деле это довольно просто ... DS создает экземпляр для каждого пакета, поэтому с DS вы не реализуете Service Factory, DS выполняет всю тяжелую работу. Например:

@Service(serviceFactory=true) 
public class MyServiceFactory implements XyzService {

   ...
   @Activate
   void activate(ComponentContext ctx) {
      System.out.println("Using bundle: " + ctx.getUsingBundle());
   }
}

Каждый раз, когда другой пакет получает этот XyzService, DS будет создавать новый экземпляр. Вы можете использовать ComponentContext (опционально переданный в методе активации), чтобы получить пакет, который использует вас.

0 голосов
/ 12 августа 2011

ServiceFactory позволяет вашему коду предоставлять настраиваемый объект обслуживания для разных пакетов.Обратите внимание, что с ServiceFactory клиенты вашей службы по-прежнему не управляют созданием нового экземпляра, а ищут службу по его интерфейсу (ServiceB) как обычно.Таким образом, для них нет разницы, если ваша служба зарегистрирована как ServiceFactory или нет.

С декларативными услугами вы не должны внедрять ServiceFactory самостоятельно.Просто добавьте атрибут servicefactory="true" к элементу <service> (вы это уже сделали), и различные экземпляры вашего класса компонентов будут созданы (активированы) автоматически для разных запрашивающих пакетов.Вам необходимо указать ServiceBImpl в качестве класса реализации компонента.

...