Search code examples
osgiapache-karafblueprint-osgi

ClassCastException DTO OSGI


I have three blueprint bundles (distinct jars):

  • ap-data: contains only a class named Game.java, supposed to be used as a DTO. It's an entity, annotated with @Entity.
  • ap-dao: a persistence bundle that contains persistence.xml, Meta-Persistence header in OSGI manifest and imports Game.java from ap-data.
  • ap-service: also imports Game.java from ap-data and an interface exposed by ap-dao, IGameDAO.java. It exposes IGameService.java also.

Realized that these bundles form layers of a module Game. Scenario:

  • All bundles are deployed correct in Karaf 3.0.5 (all are 'ACTIVE').
  • ap-dao bundle is correctly loaded as a persistence bundle, can access a configured datasource and can access EntityManager without problems. It can execute queries and I can see the results. All is fine.
  • ap-service can inject an instance of IGameDAO with no problem. All is fine until here.
  • The class Game.java (DTO) is only contained in the ap-data bundle. There is no duplicates (I checked).

Problem:

The Game typed in GameServiceImpl is loaded by the ap-service bundle classloader and the instances returned by the IGameDAO service are of another type since they were loaded by the ap-dao bundle classloader, therefore generating ClassCastException. Realize that the class is the same (app.Game.java) , supposed to be a DTO. DTOs are basic objects that I must be able to use and pass them through bundles (by services). How DTOs are treated in OSGI? How can I solve this problem?

// code in GameServiceImpl, from ap-service bundle
public void prettyPrintGames() {
    List<Game> games = gameDao().findAll();

    /* ClassCastException in the for-loop, since the type Game, in ap-service
     * is not the same of the instances of varible 'games', 
     * returned by the DAO service instance. This is the reason
     * of the classloader issue.
     */
    for(Game g : games){
        System.out.println(g.toString()); 
    }
}

Structure:

ap-data

+---ap-data
|   |   pom.xml
|   |       
|   +---src
|   |   \---main
|   |       +---java
|   |       |   \---br
|   |       |       \---com
|   |       |           \---company
|   |       |               \---game
|   |       |                   \---entity
|   |       |                           Game.java

Bundle manifest:

Manifest-Version: 1.0
Bnd-LastModified: 1445945039034
Build-Jdk: 1.7.0_07
Built-By: user
Bundle-Description: ap-data
Bundle-ManifestVersion: 2
Bundle-Name: ap-data
Bundle-SymbolicName: ap-data
Bundle-Version: 1.0.0
Created-By: Apache Maven Bundle Plugin
Export-Package: br.com.company.game.entity;version="1.0.0";uses:="javax.
 persistence"
Import-Package: javax.persistence;version="[2.1,3)"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
Tool: Bnd-3.0.0.201509101326

ap-dao

+---ap-dao
|   |   pom.xml
|   +---src
|   |   \---main
|   |       +---java
|   |       |   \---br
|   |       |       \---com
|   |       |           \---company
|   |       |               \---game
|   |       |                   \---dao
|   |       |                       +---api
|   |       |                       |       IGameDAO.java
|   |       |                       |       
|   |       |                       \---impl
|   |       |                               GameDAOImpl.java
|   |       |                               
|   |       \---resources
|   |           +---META-INF
|   |           |       persistence.xml
|   |           |       
|   |           \---OSGI-INF
|   |               \---blueprint
|   |                       blueprint.xml
|   |                       

Bundle Manifest

Manifest-Version: 1.0
Bnd-LastModified: 1445945039855
Build-Jdk: 1.7.0_07
Built-By: user
Bundle-Blueprint: OSGI-INF/blueprint/blueprint.xml
Bundle-Description: game-module
Bundle-ManifestVersion: 2
Bundle-Name: ap-dao
Bundle-SymbolicName: ap-dao
Bundle-Version: 1.0.0
Created-By: Apache Maven Bundle Plugin
Export-Package: br.com.company.game.dao.api;version="1.0.0";uses:="br.co
 m.company.game.entity"
Export-Service: br.com.company.game.dao.api.IGameDAO
Import-Package: br.com.company.game.dao.api;version="[1.0,2)",br.com.com
 pany.game.entity;version="[1.0,2)",javax.persistence;version="[2.1,3)",
 org.osgi.service.blueprint;version="[1.0.0,2.0.0)"
Meta-Persistence: META-INF/persistence.xml
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
Tool: Bnd-3.0.0.201509101326

ap-service

+---ap-service
|   |   pom.xml
|   |   
|   +---src
|   |   \---main
|   |       +---java
|   |       |   \---br
|   |       |       \---com
|   |       |           \---company
|   |       |               \---game
|   |       |                   \---service
|   |       |                       +---api
|   |       |                       |       IGameService.java
|   |       |                       |       
|   |       |                       \---impl
|   |       |                               GameServiceImpl.java
|   |       |                               
|   |       \---resources
|   |           \---OSGI-INF
|   |               \---blueprint
|   |                       service.xml
|   |                       

Bundle manifest

Manifest-Version: 1.0
Bnd-LastModified: 1445945040469
Build-Jdk: 1.7.0_07
Built-By: user
Bundle-Blueprint: OSGI-INF/blueprint/service.xml
Bundle-Description: game-module
Bundle-ManifestVersion: 2
Bundle-Name: ap-service
Bundle-SymbolicName: ap-service
Bundle-Version: 1.0.0
Created-By: Apache Maven Bundle Plugin
Export-Package: br.com.company.game.service.api;version="1.0.0"
Export-Service: br.com.company.game.service.api.IGameService
Import-Package: br.com.company.game.dao.api;version="[1.0,2)",br.com.com
 pany.game.entity;version="[1.0,2)",br.com.company.game.service.api;vers
 ion="[1.0,2)",org.osgi.service.blueprint;version="[1.0.0,2.0.0)"
Import-Service: br.com.company.game.dao.api.IGameDAO;multiple:=false
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
Tool: Bnd-3.0.0.201509101326

Analysis:

I made some tests here in ap-dao and just realized that if I return the entities without passing through EntityManager, creating the objects hardcoded, for instance, everything works. Once I use entityManager to query the entities, the problem come back again and I get ClassCastException.

private EntityManager entityManager;

    @SuppressWarnings("unchecked")
    @Override
    public List<Game> findAll() {
        return _findAllHardCoded(); // this work just fine in service layer, no ClassCastException

        return entityManager.createQuery("SELECT s FROM Game s").getResultList(); // if I use this, ClassCastException is generated in the service layer

    }

    protected List<Game> _findAllHardCoded() {
        List<Game> games = new ArrayList<Game>();
        games.add(new Game(Short.valueOf("1"), "Game1"));
        games.add(new Game(Short.valueOf("2"), "Game2"));
        return games;
    }

Solution

  • If your setup is correct then ap-data should contain the class Game and have an Export-Package for the package Game is in. The other two bundles should have an Import-Package statement for this package. In this case the class Game should only be loaded by the classloader of ap-data.

    Also take care how you setup the maven bundle plugin. If you for example tell it to export a package that is contained in another bundle then it will embed it in your own bundle. So probably the easiest way is to leave it at defaults.

    Things you can look for are. No package should be contained in more then one bundle. You should use Aries JPA to do the JPA access. As Balazs commented the persistence providers often do black magic inside. In combination with Aries JPA it should work in OSGi but if you use plain JPA there may be problems.

    I checked the project on github. I think the problem is the location of the persistence.xml. This file should always be located in the project that contains the entity classes. Can you try to move it?