3

I have a core library that has an interface that I want to expose as an OSGI service in Fuse ESB (Apache ServiceMix and Karaf). The goal is to allow other bundles to use it. The service uses JPA (OpenJPA) and Spring. The following is the interface:

public interface PatientService {
    public Patient find(Integer id);
}

and the class:

@Repository
public class PatientServiceJpaImpl implements PatientService {
    @PersistenceContext(unitName="psu")
    private EntityManager entityManager;

    @Override
    public Patient find(Integer id) {
        return entityManager.find(Patient.class, id);
    }
}

The following is an abbreviated META-INF/spring/beans.xml:

<beans xmlns="http://www.springframework.org/schema/beans" ...>
    <context:annotation-config />
    <context:component-scan base-package="..." />

    <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf" />
    </bean>

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="psu" />
        <property name="jpaVendorAdapter" ref="jpaAdapter" />
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${database.driver}" />
        <property name="url" value="${database.url}" />
        <property name="username" value="${database.username}" />
        <property name="password" value="${database.password}" />
    </bean>

    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>

And the META-INF/persistence.xml (also abbreviated):

<persistence xmlns="http://java.sun.com/xml/ns/persistence" ...>
    <persistence-unit name="psu" transaction-type="RESOURCE_LOCAL">
        <class>...</class>
</persistence>

In a non-OSGi environment, everything works great. It uses the felix maven-bundle-plugin, so to create the OSGi service, I added the following OSGI-INF/blueprint/osgi-context.xml:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
    http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <bean id="patientService" class="com.test.service.PatientServiceJpaImpl" />
    <service id="osgiPatientService" ref="patientService" interface="com.test.service.PatientService" />

</blueprint>

The bundle is deployed successfully and the service is registered. The problem is that when the PatientService is referenced from another bundle, the entity manager has not been injected, thus throwing a NullPointerException in the find(Integer id) method. The following is a snippet of the consumer's META-INF/spring/consumer-context.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://cxf.apache.org/jaxws
    http://cxf.apache.org/schemas/jaxws.xsd
    http://www.springframework.org/schema/osgi
    http://www.springframework.org/schema/osgi/spring-osgi.xsd">

    <bean id="patientServiceImpl" class="com.test.ws.PatientWebServiceImpl" >
        <property name="patientService">
            <osgi:reference interface="com.test.service.PatientService"/>
        </property>
    </bean>

    ...

</beans>

Just to be clear, the PatientService is injected in the consumer bundle, but the entity manager is not injected in the provider bundle. Also, it does not appear to be an issue with the persistence unit due to the following log output when starting the original service:

125  psu  TRACE  [SpringOsgiExtenderThread-14] openjpa.Runtime - org.apache.openjpa.persistence.PersistenceProviderImpl@24a5031d creating container org.apache.openjpa.persistence.EntityManagerFactoryImpl@4d6f77b6 for PU psu.

To get an idea what is going on, I logged the the object memory reference and stack trace in the constructor of the PatientServiceJpaImpl class. The constructor was called twice (creating two different objects):

  1. The first output appears to originate from the osgi container starting from org.apache.felix and more or less ending in org.apache.aries.blueprint.

  2. The second output appears to originate from the spring framework starting from org.springframework.osgi and more or less ending in org.springframework.beans.BeanUtils.

When the consumer service is called, the reference it has is to the blueprint instantiated object, which does not have an injected entity manager. Also from the logs, the persistence unit is instantiated after the blueprint instantiation of the PatientServiceJpaImpl object.

I have searched and tinkered with this issue for quite a while and I've run out of ideas. The irony is that it actually worked at one point, but I had made so many changes to get it to work that it was a rats nest that I was not able to back out of successfully.

Why is the persistence context not injected in the blueprint managed object? Any ideas will be appreciated. Thanks.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283

2 Answers2

2

I'm not sure if this is going to work, cause you are mixing spring with blueprint. I have a working application based only on blueprint and I'm quite happy. For your use-case I'd suggest using blueprint at least for your JPA part. You still can use spring-dm for using the jpa classes as services.

<blueprint default-activation="eager"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.0.0" xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.0.0">

<bean id="patientService" class="com.test.service.PatientServiceJpaImpl" >
    <jpa:context property="em" unitname="dn1" />
<tx:transaction method="*" value="Required" />
</bean>
<service id="osgiPatientService" ref="patientService" interface="com.test.service.PatientService" />
</blueprint>

Your PatientServiceJPAImpl I would change to not contain any annotations.

public class PatientServiceJpaImpl implements PatientService {
    protected EntityManager em;

    @Override
    public Patient find(Integer id) {
        return em.find(Patient.class, id);
    }
}
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
Achim Nierbeck
  • 5,265
  • 2
  • 14
  • 22
  • Thanks for the response. This library will also be used by other developers in a non-OSGI environment. Is there a way to maintain both solutions? In other words, what effect (if any) will keeping the 'PatientServiceJpaImpl' annotations in a blueprint environment have? – wiliano vanderkeskov Dec 04 '12 at 14:52
  • In that case make sure you're not exporting your service via blueprint but with spring-dm. A mixture of both won't work if you have to use spring stick to spring-dm, and reference your services via blueprint. – Achim Nierbeck Dec 04 '12 at 15:38
  • I tried that originally by defining the osgi service in the spring context xml file. However, after building the bundle with the felix maven-bundle-plugin, I did not see the Export-Service declaration in the MANIFEST-MF, and deploying the bundle did not register the service. I decided not to followup since Spring DM appears to have been discontinued. Is it possible to use spring-dm in karaf (I am using the Fuse ESB distribution)? And if so, does it make sense to do so down the road? Thanks. – wiliano vanderkeskov Dec 04 '12 at 16:27
  • Karaf does support Spring-dm in version 1.2.1, so yes this could be your way to go. I wouldn't rely on the maven-bundle-plugin to actually interpret the spring xml correctly for the export-service declaration. I would just give it a try and use it. – Achim Nierbeck Dec 04 '12 at 21:39
  • I was too smart for my own good by assuming that if there was no export-service declaration the service would not be available. I got it to work by using spring dm (actually, the fuse distribution comes with a spring-dm 1.2.1 bundle installed). Thanks again. – wiliano vanderkeskov Dec 05 '12 at 18:23
0

A good way to deal with this would be to use Gemini JPA. With Gemini JPA, your JPA bundle would automatically expose an EntityManagerFactory OSGi service, based on the configurations in your persistence.xml. Thus, in the JPA client bundle, the blueprint just needs to import this service with a filter condition specifying the persistence unit name.

Here's a small implementation on those lines: mvc-osgi

If the EMF service doesn't work for you, you can use the EntityManagerFactoryBuilder service exposed by the Gemini JPA bundle to manually create the EMF. This would require calling the "createEntityManagerFactory".

Aritra
  • 1,234
  • 12
  • 20