2011-08-11 13 views
7

私は現在、Service Factoryを実行している単純なバンドルを取得しようとしています。最終的にはOSGI-INF/component.xmlosgi:ServiceFactoriesを使用していますか?

<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> 

public class ServiceBImpl implements ServiceB { 

    private ServiceA svcA; 

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

} 

そして:これは

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) { 

    } 

} 

ファクトリで作成する必要があります私のサービスは次のとおりです。

は、これは私のファクトリクラスです

私のテストバンドル(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"という本でさえ、それらの使い方について私に教えてくれませんでした。 誰も私が間違っていることを私に説明してもらえますか?

+0

サービスを含むバンドルには、ServiceB.classファイルを含めないでください。そうであれば、別のバンドルに異なるServiceBを使用するでしょう - それは間違っています。 –

+0

私はあなたが誤解していると思います。 servicefactory属性を指定した場合、DSはバンドルごとに新しいインスタンスを作成します。 ComponentContextから割り当てられたバンドルを取得できます。 –

答えて

12

あなたのニーズに合ったComponentFactoryを使用した例です(other questionを支援する簡単な統合テストが含まれています)。免責事項。コードはうまく書かれていません。

いくつかのサービス・インターフェース:

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; 
    } 
} 

すべてを動かす統合テスト(注: mainメソッドがあるので、バンドルの開始/停止中に実行します)。

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())); 
    } 
} 

そして、ここでは、Mavenのポンポンです:

<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は統合テストでは失敗しますが、すべての統合テスト用に1つの統合モジュールを持つプロジェクトを見るのが一般的です。これは、現在のモジュールのバンドルをターゲットディレクトリに配置するために使用される醜いメソッドfindFileInCurrentDirectoryAndBelow(Pattern pattern)について説明し、maven-bundle-pluginとmaven-scr-pluginプラグインの非標準設定についても説明します。

また、Pax-Examが依存関係を取得する方法では、依存関係や設定の変更(たとえば、バンドルのインポート/エクスポート、DS変更)ごとにmavenビルドを実行する必要があります。しかし、これが完了すると、Eclipseからテストを実行/デバッグできます。

私はあなたがサービス工場を実装していないDSを持つので、それは... DSは、各バンドルのインスタンスを作成し、実際にはかなり単純ですtarballにhere

HTH =)

+0

それは素晴らしいです!あなたの詳細な答えと忍耐力に感謝します。 –

+0

非常に詳細な回答のための@earcamありがとうございました、pax-examドキュメントよりはるかに優れています。しかし、いくつかの混乱がありますが、これはJAVA 6で動作しますが、JAVA 8ではなく、別のPAX依存バージョンで多かったのですが、Java 8との競合を解消できませんでした。 –

0

ServiceFactoryは、コードが異なるバンドルにカスタマイズされたサービスオブジェクトを提供できるようにします。 ServiceFactoryでは、サービスのクライアントは新しいインスタンスが作成された時点では制御されず、通常どおりそのインターフェイス(ServiceB)でサービスを検索します。したがって、あなたのサービスがServiceFactoryとして登録されているかどうかに違いはありません。

宣言型サービスでは、ServiceFactoryを実装するべきではありません。 属性を<service>要素に追加するだけで、要求しているバンドルごとにコンポーネントクラスの異なるインスタンスが自動的に作成(アクティブ化)されます。コンポーネントの実装クラスとしてServiceBImplを指定する必要があります。

+0

OSGiプラットフォームはどのファクトリ・クラスを使用すべきかをどのように知っていますか? –

+0

自分のコードのテスト容易性をサポートするために、私自身の工場実装を提供したいと思います。この議論を参照してください:http://stackoverflow.com/questions/7004165/unit-testing-osgi-components/7009717#7009717私はhttp://www.knopflerfish.org/osgi_service_tutorial.htmlでこれを行う方法の例を見つけました。私はアクティベータにサービスファクトリを登録する必要があります。 DSが私のためにそれをするようにコンポーネント宣言の中でこれを行う方法はありませんか? –

+0

これは、基本的にgetService(){Object comp = clazz.newInstance();のような何かを行う独自のファクトリを作成します。 activateComponent(comp); return comp; } –

1

プロジェクトを入れています、DSはすべて苦労しています。例:

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

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

別のバンドルがこのXyzServiceを取得するたびに、DSは新しいインスタンスを作成します。 ComponentContext(オプションでactivateメソッドで渡される)を使用して、あなたを使用しているバンドルを取得できます。

+0

githubのリンクが壊れています –

関連する問題