2011-08-11 18 views
7

Actualmente estoy tratando de obtener un paquete simple que contenga una fábrica de servicios en ejecución.osgi: ¿Utilizando ServiceFactories?

Esta es mi clase de fábrica:

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

    } 

} 

Ésta es mi servicio que debe ser creado por la fábrica:

public class ServiceBImpl implements ServiceB { 

    private ServiceA svcA; 

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

} 

Y, finalmente, la 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> 

Si ejecuto mis paquetes de prueba (A, B y C) dentro del equinoccio recibo el siguiente error:

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 

No encuentro mucha información sobre el uso de ServiceFeactories declarado en una definición de componente en internet. Incluso el libro "OSGi y Equinox" no me dice mucho sobre su uso. ¿Alguien podría explicarme qué estoy haciendo mal?

+0

el paquete que contiene su servicio no debe tener el archivo ServiceB.class. Si lo hace, usaría ServiceB diferente para un paquete diferente, eso está mal. –

+0

Creo que has entendido mal. Si especifica el atributo servicefactory, DS creará una nueva instancia para cada paquete. Puede obtener el paquete que le asignaron desde ComponentContext. –

Respuesta

12

Aquí hay un ejemplo que usa ComponentFactory que debe ajustarse a sus necesidades (y contiene una prueba de integración simple para ayudar con su other question). Renuncia; el código no está bien escrito, solo por el bien del ejemplo.

Algunas interfaces de servicio:

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

Y:

package net.earcam.example.servicecomponent; 

public interface SequenceService { 
    long next(); 
} 

Entonces las implementaciones:

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

}

Y

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

Una prueba de integración que lo maneja todo (nota; hay un método principal para que lo ejecutes mientras comienzas/detengas paquetes, etc.).

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

Y aquí está el POM 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> 

Un par de cosas a tener en cuenta; Me gustan mis pruebas de integración dentro del módulo que prueban, de esa manera mvn clean install deploy falla si mi prueba de integración lo hace, pero es común ver proyectos con un solo módulo de integración para todas las pruebas de integración. Esto explica el feo método findFileInCurrentDirectoryAndBelow(Pattern pattern) que se utiliza para localizar el paquete del módulo actual en el directorio de destino, y también explica la configuración no estándar de los plugins maven-bundle-plugin y maven-scr-plugin.

También la forma en que Pax-Exam recoge las dependencias requiere que ejecute la construcción maven para cada cambio en dependencias y configuraciones (por ejemplo, paquetes de importaciones/exportaciones, cambios de DS). Pero una vez hecho esto, puede ejecutar/depurar las pruebas de Eclipse.

He puesto el proyecto en un archivo comprimido here

HTH =)

+0

¡Eso es genial! Muchas gracias por su respuesta detallada y paciencia. –

+0

Gracias @earcam por la respuesta muy detallada, mucho mejor que los documentos de pax-exam, me ayudó mucho. Pero tengo algo de confusión, esto está funcionando con JAVA 6 pero no con JAVA 8, probé mucho con diferentes versiones de dependencia de PAX pero no pude resolver el conflicto con Java 8. ¿Puede proporcionar el flujo de dependencia de versión si es posible? –

0

ServiceFactory permite que su código proporcione el objeto de servicio personalizado para diferentes paquetes. Tenga en cuenta que con ServiceFactory, los clientes de su servicio aún no controlan cuando se crea una nueva instancia, buscan el servicio por su interfaz (ServiceB) como de costumbre. Entonces, para ellos, no hay diferencia si su servicio está registrado como ServiceFactory o no.

Con servicios declarativos, no debe implementar ServiceFactory usted mismo. Simplemente agregue el atributo servicefactory="true" al elemento <service> (ya lo hizo) y se crearán (activarán) diferentes instancias de su clase de componente automáticamente para diferentes paquetes solicitantes. Debe especificar ServiceBImpl como una clase de implementación del componente.

+0

¿Y cómo sabe la plataforma OSGi qué clase de fábrica usar? –

+0

Deseo proporcionar mi propia implementación de fábrica para permitir una mejor comprobabilidad de mi código. Vea esta discusión: http://stackoverflow.com/questions/7004165/unit-testing-osgi-components/7009717#7009717 Encontré en http://www.knopflerfish.org/osgi_service_tutorial.html un ejemplo de cómo hacer esto, pero tengo que registrar la fábrica de servicio en el activador. ¿No hay forma de hacer esto dentro de la declaración del componente para que DS lo haga por mí? –

+0

Creará su propia fábrica que básicamente hará algo similar a getService() {Object comp = clazz.newInstance(); activateComponent (comp); rendimiento de devolución; } –

1

En realidad, es bastante simple ... DS crea una instancia para cada paquete, así que con DS no se implementa de servicio de fábrica , DS hace todo el trabajo duro. Por ejemplo:

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

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

Cada vez que otro paquete recibe este XyzService, DS creará una nueva instancia. Puede usar el ComponentContext (opcionalmente pasado en el método de activación) para obtener el paquete que lo está utilizando.

+0

el enlace github ahora está roto –