3

I've been diving into ServiceMix 5.4.0 and OSGi, and have run across a rather weird behavior with OpenJPA.

I have a data source defined like so:

<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="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver"/>
        <property name="url" value="jdbc:postgresql://localhost:5432/test"/>
        <property name="username" value="test"/>
        <property name="password" value="test"/>
    </bean>

    <service interface="javax.sql.DataSource" ref="dataSource">
      <service-properties>
        <entry key="osgi.jndi.service.name" value="jdbc/test"/>
      </service-properties>
    </service>
</blueprint>

Using the jndi:names command, I can verify that the data source is visible:

karaf@root> jndi:names
JNDI Name            Class Name                                                  
osgi:service/jndi    org.apache.karaf.jndi.internal.JndiServiceImpl              
osgi:service/jdbc/test org.apache.commons.dbcp.BasicDataSource                     
karaf@root> 

My persistence.xml:

<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="test" transaction-type="JTA">               
        <jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/test)</jta-data-source>

        <class>com.example.persistence.security.User</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>
            <property name="openjpa.jdbc.DBDictionary" value="postgres"/>                       
            <property name="openjpa.Log" value="slf4j"/>
        </properties>
    </persistence-unit>
</persistence>

I then inject the persistence unit into a DAO class via Blueprint:

<?xml version="1.0" encoding="UTF-8"?>

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

    <bean id="securityDAO" class="com.example.security.dao.SecurityDAOImpl" init-method="init">
        <tx:transaction method="*" value="Required" />
        <jpa:context property="entityManager" unitname="test" />
    </bean>

    <service ref="securityDAO" interface="com.example.security.dao.SecurityDAO">
    </service>


</blueprint>

The persistence unit is successfully injected, which I verify in the init-method of the DAO:

public void init() {
    if (em==null) {
        log.error("Entity manager not found. Check JPA configuration.");
        throw new RuntimeException("No EntityManager found");
    }

    log.info("Started SecurityDAO");
}

After all my diligent work, ServiceMix rewards me with the following cryptic exception when I call my DAO's method from another bean:

....
public void setSecurityDAO (SecurityDAO dao) {
    this.dao = dao;
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String userName = req.getParameter("userName");
    String password = req.getParameter("password");

    // Invocation of injected DAO results in exception
    User u = dao.authenticateUser(userName, password);          

This results in the following:

Caused by: java.lang.RuntimeException: The DataSource osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/test) required by bundle persistence/0.0.1.SNAPSHOT could not be found.
    at org.apache.aries.jpa.container.unit.impl.JndiDataSource.getDs(JndiDataSource.java:87)
    at org.apache.aries.jpa.container.unit.impl.DelayedLookupDataSource.getConnection(DelayedLookupDataSource.java:36)
    at org.apache.openjpa.lib.jdbc.DelegatingDataSource.getConnection(DelegatingDataSource.java:116)
    at org.apache.openjpa.lib.jdbc.DecoratingDataSource.getConnection(DecoratingDataSource.java:93)
    at org.apache.openjpa.jdbc.schema.DataSourceFactory.installDBDictionary(DataSourceFactory.java:233)
    ... 54 more
Caused by: javax.naming.NoInitialContextException: Unable to find the InitialContextFactory org.eclipse.jetty.jndi.InitialContextFactory.
    at org.apache.aries.jndi.ContextHelper.getInitialContext(ContextHelper.java:148)
    at org.apache.aries.jndi.OSGiInitialContextFactoryBuilder.getInitialContext(OSGiInitialContextFactoryBuilder.java:49)
    at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
    at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
    at javax.naming.InitialContext.init(InitialContext.java:244)
    at javax.naming.InitialContext.<init>(InitialContext.java:216)
    at org.apache.aries.jpa.container.unit.impl.JndiDataSource.getDs(JndiDataSource.java:64)
    ... 58 more

Somehow the OSGi-exported data source is not finding its way into the persistence bundle. The strange part is that when I added the following code to the init-method to see if I could execute a test query, not only does OpenJPA not throw an exception in the init method, the invocation of the DAO that was triggering the exception now works as well:

public void init() {
    if (em==null) {
        log.error("Entity manager not found. Check JPA configuration.");
        throw new RuntimeException("No EntityManager found");
    }

    try {
        Query q = em.createNativeQuery("SELECT 1=1");           
        q.getFirstResult();         
    } catch (Exception ex) {
        log.error("Unable to execute test query against database", ex);
        throw new RuntimeException(ex);
    }

    log.info("Started SecurityDAO");
}

So, to summarize: If I call a method from a different bundle than my DAO, OpenJPA throws an exception indicating that it can't find the InitialNamingContext, and does not show any indication in the log that it has started. If I execute a query inside my DAO before an external component calls into it, somehow OpenJPA is able to find the InitialNamingContext, OpenJPA shows up in the log, and subsequent invocations from outside the DAO bundle begin to work.

Obviously, I'm missing something basic here. Any help or thoughtful explanation of what's breaking, or what I'm doing wrong, will be greatly appreciated.

EDIT:

I hadn't noticed last night, but when I added in the test query, the following lines appear in the log. They are absent when I comment out that query:

... | Runtime | 220 - org.apache.openjpa - 2.3.0 | Starting OpenJPA 2.3.0
... | JDBC    | 220 - org.apache.openjpa - 2.3.0 | Using dictionary class "org.apache.openjpa.jdbc.sql.PostgresDictionary".
... | JDBC    | 220 - org.apache.openjpa - 2.3.0 | Connected to PostgreSQL version 9.9 using JDBC driver PostgreSQL Native Driver version PostgreSQL 9.3 JDBC4.1 (build 1102).

EDIT 2:

Tried it on plain vanilla Karaf 3.0.3, and got the same error. As a workaround, I created a separate bean in the bundle that executes the above-mentioned test query. Apparently, as long as a single bean in the bundle makes a call to OpenJPA before a bean outside the bundle tries to make a call, OpenJPA will be correctly initialized.

Since this is mentioned nowhere I can see in the OpenJPA/ServiceMix docs, I can only presume that I'm doing something wrong elsewhere in my configuration.

EDIT 3:

Per John Forth, here is the MANIFEST.MF

Manifest-Version: 1.0
Bnd-LastModified: 1430533396366
Build-Jdk: 1.8.0_45
Built-By: somedude
Bundle-Blueprint: OSGI-INF/blueprint/blueprint.xml
Bundle-Description: Database access layer for Peer Review product
Bundle-ManifestVersion: 2
Bundle-Name: Example :: Persistence
Bundle-SymbolicName: persistence-jpa
Bundle-Version: 0.0.1.SNAPSHOT
Created-By: Apache Maven Bundle Plugin
Export-Package: com.example.persistence.security;version="0.0.1.SNAPSHOT",co
 m.example.security.dao;version="0.0.1.SNAPSHOT";uses:="com.example.persistence.
 security,javax.persistence"
Export-Service: com.example.security.dao.SecurityDAO
Import-Package: javax.persistence;version="[1.1,2)",org.osgi.service.blu
 eprint;version="[1.0.0,2.0.0)",org.slf4j;version="[1.7,2)"
Meta-Persistence: META-INF/persistence.xml
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
Tool: Bnd-2.3.0.201405100607

And, since it may be related, the pom.xml of the JPA bundle:

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <parent>
        <artifactId>example</artifactId>
        <groupId>com.example</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>persistence-jpa</artifactId>
    <packaging>bundle</packaging>

    <name>Example :: Persistence</name>

    <dependencies>
        <dependency>
            <groupId>org.apache.geronimo.specs</groupId>
            <artifactId>geronimo-jpa_2.0_spec</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.7</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.5.3</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Meta-Persistence>META-INF/persistence.xml</Meta-Persistence>
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Version>${project.version}</Bundle-Version>                     
                        <Import-Package>*</Import-Package>
                        <Export-Package>com.example.persistence*,com.example.security.*;version=${project.version}</Export-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
рüффп
  • 5,172
  • 34
  • 67
  • 113
McCroskey
  • 1,091
  • 1
  • 11
  • 21

1 Answers1

0

If you are using OSGI the class visibility is defined in the MANIFEST.MF files.

Thus the persistence bundle can only see and load classes which are imported in its MANIFEST.MF.

A proper way to extend an existing bundle is to define a fragment which is attached to the existing bundle. This way you can provide classes (e.g. DAOs) and files (e.g. persistence.xml) and make the visible to the fragment-host.

The MANIFEST.MF then looks like

Bundle-ManifestVersion: 2
Bundle-Name: foo.bar.openjpa-fragment
Bundle-SymbolicName: foo.bar.openjpa-fragment;singleton:=true
Bundle-Version: 0.0.1.SNAPSHOT
Bundle-Vendor: foo bar
Fragment-Host: org.apache.openjpa-bundle
Bundle-ClassPath: .

Note that this is only an example.

OSGI means to provide proper visibility.

You can add more than one fragment to an existing bundle, e.g. to keep the configuration in a separate bundle, wich makes it easier to switch the configuration.

bebbo
  • 2,830
  • 1
  • 32
  • 37
  • Not clear how this translates for me...I am defining a bundle with some entities, a DAO or three, and a persistence.xml, which is then referenced by a CXF service in another bundle. Should I be structuring my JPA project so that the entities and DAO are fragments attaching to the OpenJPA bundle? – McCroskey May 02 '15 at 02:30
  • Exactly. The DAOs must be a fragment attached to the OpenJPA bundle, otherwise the OpenJPA bundle can't see and load those classes. – bebbo May 02 '15 at 11:06