Accueil > DynaResume, JPA, JPA/EclipseLink, JPA/Hibernate, OSGi, Spring DM, Spring ORM > Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step19]

Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step19]


Dans le billet précédant [step18] nous avons mis en place JPA avec LocalContainerEntityManagerFactoryBean en utilisant JPA/Hibernate et JPA/EclipseLink avec les 2 bases de données H2 et Derby dans un contexte NON OSGi. Dans ce billet nous allons mettre en place JPA dans un contexte OSGi avec LocalContainerEntityManagerFactoryBean en utilisant JPA/Hibernate et JPA/EclipseLink avec les 2 bases de données H2 et Derby.

Notre classe UserServiceImpl du bundle org.dynaresume.services.impl utilisera une interface DAO UserDAO qui dans notre cas est implémenté en JPA. L’implémentation de la DAO UserDAO est renseigné à la classe UserServiceImpl via le mécanisme d’injection de Dépendances en utilisant le registre de services OSGi :

Pré-requis – step19-tp

Avant de démarrer ce billet vous devez :

Base de données

Installer les bases de données H2 et Derby :

Téléchargez le zip spring-target-platform-dao.zip qui contient les librairies JARs JPA, JPA/Hibernate,… récupérés via Maven dans le billet [step14] puis importez le projet spring-target-platform-dao dans votre workspace.

REMARQUE : nous avons créé la base de données via des scripts mais JPA est capable de générer la base de données en utilisant les informations de mapping.

Workspace Eclipse

Préparer votre workspace : pour cela vous devez récupérer les bundles OSGi (Services, Domain..) du billet [step13] et le projet qui contient les bundles OSGi Jpa du billet [step14] et ajoutez les JARs Jpa à la Target Platform. Vous pouvez télécharger le projet org.dynaresume_step19-tp.zip qui contient les projets expliqués dans cette section.

Téléchargez les projets et importez les dans votre worksapce :

Ajoutez à la Target Platform spring-target-platform/DynaResume Target Platform.target tous les répertoires (lib/*) du projet spring-target-platform-dao comme expliqué ici. Ouvrez le fichier Target Platform spring-target-platform/DynaResume Target Platform.target et cliquez sur Set As Target Platform.

ATTENTION, assurez vous que dans tous les launch, la case à cocher “Add new workspace bundles to this launch configuration automaticly” est dé-sélectionné. Dans le cas contraire tous les bundles/fragments que nous allons créés seront sont sélectionnés. Veuillez aussi cliquer sur l’option “Clear the configuration area before launching” dans l’onglet “Settings” du launch pour ne pas avoir d’erreur bizarres de bundles non trouvés.

step19-javadao2bundledao

Dans cette section nous allons reprendre le projet org.dynaresume.test.jpa_lcfb_4 du billet [step18] et découper ce dernier en plusieurs bundles OSGi : un bundle OSGi par fichier XML Spring applicationContext*.xml. Nous allons reprendre tel quel les fichiers XML Spring concernant EclipseLink et verrons les problèmes que cela posera dans un contexte OSGi.

Vous pouvez télécharger le projet org.dynaresume_step19-javadao2bundledao.zip qui contient les projets expliqués dans cette section.

Concernant JPA/EclipseLink nous allons désactiver le LoadTimeWeaver car :

  1. nous n’en avons pas besoin (nous n’avons pas de mode lazy dans notre Pojo User)
  2. l’Agent Java Spring que nous avons utilisés dans le billet précédant dans la section JPA/EclipseLink – Run-time bytecode instrumentation ne peut pas être utilisé dans un contexte OSGi. La gestion du LoadTimeWeaver dans un contexte OSGi (Equinox plus particulièrement) peut s’effectuer apparemment (je n’ai pas encore réussi à le faire marcher) avec Equinox Aspects. Je vous conseille de lire le billet Load-Time Weaving for Spring-DM.

Bundle org.dynaresume.domain

Ici nous allons modifier la classe org.dynaresume.domain.User du bundle Domain org.dynaresume.domain pour rendre persistent cette classe via des annotations JPA . Pour utiliser les annotations JPA dans le bundle org.dynaresume.domain, importez le package javax.persistence.

Import-Package: javax.persistence;version="2.0.0",
 org.osgi.framework;version="1.3.0"

Comme vous pouvez le constater, PDE ajoute l’attribut version= »2.0.0″ pour le package javax.persistence. Nous verrons que ceci posera problème lorsque nous mettrons en place JPA/Hibernate. Modifiez la classe User avec les annotations JPA comme suit :

package org.dynaresume.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "T_USER")
public class User implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "USR_ID_N")
	private long id;

	@Column(name = "USR_LOGIN_C")
	private String login;

	@Column(name = "USR_PASSWORD_C")
	private String password;

	public User() {
	}

	public User(String login, String password) {
		setLogin(login);
		setPassword(password);
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Vous pouvez remarquer que la classe User implémente java.io.Serializable pour pouvoir être utilisée dans un contexte Client/Serveur.

Bundle org.dynaresume.dao

Créez le Bundle org.dynaresume.dao avec les paramètres suivants :

  • champs ID: org.dynaresume.dao
  • champs Version: 1.0.0.qualifier.
  • champs Name: DynaResume API DAO
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

Créez l’interface org.dynaresume.dao.UserDAO comme suit :

package org.dynaresume.dao;

import java.util.Collection;

import org.dynaresume.domain.User;

public interface UserDAO {

	Collection findAllUsers();
	
	User saveUser(User user);
}

Pour que le bundle compile, importez les packages :

  • Importez le package org.dynaresume.domain.

Exportez le package org.dynaresume.dao pour rendre accessible l’interface DAO aux autres bundles.

Bundle org.dynaresume.services

Modifiez l’interface UserService comme suit :

package org.dynaresume.services;

import java.util.Collection;

import org.dynaresume.domain.User;

public interface UserService {

	Collection<User> findAllUsers();

	User createUser(String login, String password);
}

Bundle org.dynaresume.services.impl

Modifiez la classe UserServiceImpl pour utiliser l’interface UserDAO récupérée via Injection de dépendances comme suit :

package org.dynaresume.services.impl;

import java.util.Collection;

import org.dynaresume.dao.UserDAO;
import org.dynaresume.domain.User;
import org.dynaresume.services.UserService;
import org.springframework.transaction.annotation.Transactional;

@Transactional(readOnly=true)
public class UserServiceImpl implements UserService {

	private UserDAO userDAO;

	public void setUserDAO(UserDAO userDAO) {
		this.userDAO = userDAO;
	}
	
	public Collection<User> findAllUsers() {
		return userDAO.findAllUsers();
	}

	@Transactional
	public User createUser(String login, String password) {
		User user = new User(login, password);
		return userDAO.saveUser(user);
	}
}

Pour que le bundle compile, importez les packages :

  • org.dynaresume.dao
  • org.springframework.transaction.annotation

module-osgi-context.xml

Modifiez le fichier module-osgi-context.xml pour déclarer le bean « userDAO » qui est l’implémentation de la DAO UserDAO récupérée dans le registre de services OSGi :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:reference id="userDAO" interface="org.dynaresume.dao.UserDAO" />
	 
	<osgi:service ref="userService" interface="org.dynaresume.services.UserService" />

</beans>

module-context.xml

Modifiez le fichier XML Spring module-context.xml du bundle pour renseigner au service UserServiceImpl, l’implémentation de la DAO UserDAO récupérée du registre de services OSGi :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl">
		<property name="userDAO" ref="userDAO" />
	</bean>
</beans>

Bundle org.dynaresume.dao.jpa

Ici nous allons déclarer les beans Spring définit dans le fichier XML Spring applicationContext-dao-jpa.xml du billet précédant en créant le bundle OSGi org.dynaresume.dao.jpa. Ce bundle implémente la DAO UserDAO avec JPA et publiera dans le registre de services OSGi cette instance pour etre ensuite consommé dans la classe UserServiceImpl. Ce bundle est indépendant de l’implémentation JPA (EclipseLink/Hibernate).

Créez le Bundle org.dynaresume.dao.jpa avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.jpa
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Jpa DAO
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

Créez l’implémentation JPA org.dynaresume.dao.jpa.UserDAOJpa comme suit :

package org.dynaresume.dao.jpa;

import java.util.Collection;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.dynaresume.dao.UserDAO;
import org.dynaresume.domain.User;

public class UserDAOJpa implements UserDAO {

	@PersistenceContext
	private EntityManager entityManager;

	public Collection<User> findAllUsers() {
		Query query = entityManager.createQuery("select u from "
				+ User.class.getSimpleName() + " u");
		return query.getResultList();
	}

	public User saveUser(User user) {
		entityManager.persist(user);
		return user;
	}

}

Pour que le bundle compile, importez les packages :

  • org.dynaresume.domain
  • org.dynaresume.dao
  • javax.persistence
Import-Package: javax.persistence;version="2.0.0",
 org.dynaresume.dao,
 org.dynaresume.domain

PDE ajoute l’attribut version= »2.0.0″ pour le package javax.persistence. Nous verrons que ceci posera posera problème lorsque nous mettrons en place JPA/Hibernate. Créez le fichier persistence.xml dans le répertoire META-INF comme suit :

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	version="1.0">

	<persistence-unit name="dynaresume" transaction-type="RESOURCE_LOCAL">

		<class>org.dynaresume.domain.User</class>

	</persistence-unit>

</persistence>

module-context.xml

Créez le fichier module-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	
	<bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa"></bean>
	
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="dynaresume" />
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
		<property name="jpaProperties" ref="jpaProperties" />
	</bean>

	<bean
		class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

	<tx:annotation-driven transaction-manager="txManager" />

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

</beans>

La déclaration des beans Spring utilise des classes Spring (ex : org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor) ou il faut importer les packages pour éviter d’avoir des ClassNotFoundException. Importez les packages :

  • org.springframework.orm.jpa
  • org.springframework.orm.jpa.support

Le bean entityManagerFactory est paramétré via les beans dataSource, jpaVendorAdapter, jpaProperties qui sont des beans récupérés dans le registre de services OSGi.

module-osgi-context.xml

Créez le fichier module-osgi–context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:service ref="userDAO" interface="org.dynaresume.dao.UserDAO" />

	<osgi:reference id="dataSource" interface="javax.sql.DataSource"  />
	<osgi:reference id="jpaProperties" interface="java.util.Properties" />
	<osgi:reference id="jpaVendorAdapter" interface="org.springframework.orm.jpa.JpaVendorAdapter"  />
	
</beans>

Ici nous déclarons <osgi:reference id=dataSource, jpaVendorAdapter, jpaProperties pour récupérer dans le registre de services OSGi ces beans. L’implémentation de la DAO UserDAO en JPA est enregistrée dans le registre de services OSGi pour ensuite être consommée par le service UserServiceImpl.

Vous pouvez remarquer que le bean jpaProperties déclare interface= »java.util.Properties » qui n’est pas une interface et verrons le problème (CGLIB) que ceci posera.

Bundle org.dynaresume.dao.jpa.vendor.eclipselink

Ici nous allons déclarer les beans Spring définis dans le fichier XML Spring applicationContext-dao-jpa-eclipselink.xml du billet précédant en créant le bundle OSGi org.dynaresume.dao.jpa.vendor.eclipselink. Ce dernier s’occupe de configurer JPA/EclipseLink, autrement dit fournir via des services OSGi, les 2 bean Spring :

  • jpaProperties : propriétés de JPA/EclipseLnk qui consisté à désactiver le LoadTimeWeaver.
  • jpaVendorAdapter : implémentation JPA/Eclipselink de l’interface org.springframework.orm.jpa.JpaVendorAdapter qui est utilisé par le bean entityManagerFactoryBean du bundle implémentation Jpa/DAO org.dynaresume.dao.jpa

Créez le Bundle org.dynaresume.dao.jpa.vendor.eclipselink avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.jpa.vendor.eclipselink
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Jpa/EclipseLink DAO
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

Créez le fichier module-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<!-- JPA/EclipseLink properties -->
	<bean id="jpaProperties"
		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="properties">
			<props>
				<prop key="eclipselink.weaving">false</prop>
			</props>
		</property>
	</bean>

	<!-- JPA/EclipseLink Vendor -->
	<bean id="jpaVendorAdapter"
		class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
		<property name="database" ref="database" />
		<property name="generateDdl" value="false" />
		<property name="showSql" value="true" />
	</bean>

</beans>

Ici nous désactivons le weaving (LoadTimeWeaver de Spring) de EclipseLink via la propriété weaving.

Créez le fichier module-osgi-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:reference id="database" interface="java.lang.String" />
	
	<osgi:service ref="jpaProperties" interface="java.util.Properties" />	
	<osgi:service ref="jpaVendorAdapter" interface="org.springframework.orm.jpa.JpaVendorAdapter" />
		
</beans>

Nous verrons dans la suite que la déclaration <osgi:reference database et <osgi:service jpaProperties poseront problème. En effet "java.lang.String" et "java.util.Properties" ne sont pas des interfaces! Concernant "java.util.properties", j'ai fait comme ceci au début car la factory Spring org.springframework.beans.factory.config.PropertiesFactoryBean construit une instance java.util.Properties.

Import package

Importez les 3 packages utilisés dans la déclaration des bean Spring :

On peut remarquer que le package java.util* n’a pas besoin d’être importé.

Bundle org.dynaresume.dao.datasource

Ici nous allons déclarer les beans Spring définit dans le fichier XML Spring applicationContext-datasource.xml du billet précédant en créant le bundle OSGi org.dynaresume.dao.datasource. Ce dernier s’occupe de définir une datasource générique, autrement dit fournir via un service OSGi, le bean Spring :

  • dataSource : qui définit la datasource javax.sql.DataSource.

Créez le Bundle org.dynaresume.dao.datasource avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.datasource
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Datasource DAO
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

module-context.xml

Créez le fichier module-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<!-- DataSource -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource"
		p:driverClassName="${database.driverClassName}" p:url="${database.url}"
		p:username="${database.username}" p:password="${database.password}">
	</bean>

</beans>

module-osgi-context.xml

Créez le fichier module-osgi-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:service ref="dataSource" interface="javax.sql.DataSource"  />

</beans>

Ici nous exposons la dataSource en services OSGi qui est utilisé dans l’entityManagerFactory.

Import package

Importez les packages utilisés dans la déclaration des bean Spring :

  • org.springframework.jdbc.datasource : utilisé dans le fichier Spring module-context.xml.

Fragment org.dynaresume.config.dao.datasource.derby

Le bundle de la datasource générique org.dynaresume.dao.datasource attend les paramètres de configuration d’une base de données (ex : p:driverClassName= »${database.driverClassName} »). Ceci peut s’effectuer dans un fichier de propriétés comme ce que nous avons fait dans le billet précédant avec applicationContext-datasource-derby.xml. Pour rappel, en OSGi, une bonne pratique est de déléguer la configuration des fichiers de propriétés à des Fragments OSGi, ce qui évite de modifier le bundle OSGi qui requiert la configuration d’un fichier de propriétés.

Ici nous allons créer le fragment OSGi org.dynaresume.config.dao.datasource.derby qui configure la base Derby, lié au bundle OSGi org.dynaresume.dao.datasource. Créez le fragment org.dynaresume.config.dao.datasource.derby avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.datasource.derby
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Datasource Derby configuration
  • Plug-in ID: org.dynaresume.dao.datasource

derby.properties

Créez le fichier derby.properties dans le package org.dynaresume.config.dao.datasource.derby comme suit :

database.driverClassName=org.apache.derby.jdbc.EmbeddedDriver
database.url=jdbc:derby:C:/db/derby/dynaresume
database.username=
database.password=

module-context.xml

Ici nous allons charger les propriétés de derby.properties à l’aide de org.springframework.beans.factory.config.PropertyPlaceholderConfigurer. Créez le fichier module-context.xml dans le répertoire META-INF/spring du fragment comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>classpath:org/dynaresume/config/dao/datasource/derby/derby.properties</value>
		</property>
	</bean>

</beans>

La déclaration du chargement du fichier de propriété peut s’écrire plus simplement à l’aide de <context:property-placeholder :

<?xml version="1.0" encoding="UTF-8"?>
<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:util="http://www.springframework.org/schema/util"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
			http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

    <context:property-placeholder location="classpath:org/dynaresume/config/dao/datasource/derby/derby.properties"  />
 
</beans>

Import package

Lorsque le bundle org.dynaresume.dao.datasource va instancier le driver JDBC (déterminé dans la propriété database.driverClassName), celui ci doit importer le package du driver JDBC. Dans le cas de Derby, le driver est org.apache.derby.jdbc.EmbeddedDriver, ce qui signifie que le fragment doit importer le package org.apache.derby.jdbc. Nous ne le faisons pas tout de suite pour constater par la suite l’erreur.

Fragment org.dynaresume.config.dao.datasource.h2

Ici nous allons créer le fragment OSGi org.dynaresume.config.dao.datasource.h2 qui configure la base H2, lié au bundle OSGi org.dynaresume.dao.datasource. Ceci peut s’effectuer dans un fichier de propriétés comme ce que nous avons fait dans le billet précédant avec applicationContext-datasource-h2.xml.

Créez le fragment org.dynaresume.config.dao.datasource.h2 avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.datasource.h2
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Datasource H2 configuration
  • Plug-in ID: org.dynaresume.dao.datasource

h2.properties

Créez le fichier h2.properties dans le package org.dynaresume.config.dao.datasource.h2 comme suit :

database.driverClassName=org.h2.Driver
database.url=jdbc:h2:C:/db/h2/dynaresume
database.username=sa
database.password=

module-context.xml

Ici nous allons charger les propriétés de h2.properties à l’aide de <context:property-placeholder :

<?xml version="1.0" encoding="UTF-8"?>
<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:util="http://www.springframework.org/schema/util"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
			http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

    <context:property-placeholder location="classpath:org/dynaresume/config/dao/datasource/h2/h2properties"  />
 
</beans>

Import package

Lorsque le bundle org.dynaresume.dao.datasource va instancier le driver JDBC (déterminé dans la propriété database.driverClassName), celui ci doit importer le package du driver JDBC. Dans le cas de H2, le driver est org.h2.Driver, ce qui signifie que le fragment doit importer le package org.h2. Nous ne le faisons pas tout de suite pour constater par la suite l’erreur.

Bundle org.dynaresume.dao.jpa.database.derby

Ici nous allons créer le bundle OSGi org.dynaresume.dao.jpa.database.derby qui s’occupe de retourner le type de la base de donnée Derby qui est utilisé ensuite dans le dialect JPA. Ce bundle expose en tant que service OSGi la valeur DERBY avec l’id database. Mais nous verrons que ce procédé ne fonctionne pas car java.lang.String n’est pas une interface. Mais dans un premier temps nous allons effectuer la même chose que ce que nous avons fait dans un contexte non OSGi avec applicationContext-dao-jpa-database-derby.xml.

Créez le Bundle org.dynaresume.dao.datasource avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.jpa.database.derby
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Jpa/DAO Database Derby
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

module-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<bean id="database" class="java.lang.String">
		<constructor-arg>
			<value>DERBY</value>
		</constructor-arg>
	</bean>
	
</beans>

module-osgi-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:service ref="database" interface="java.lang.String"  />

</beans>

Bundle org.dynaresume.dao.jpa.database.h2

Ici nous allons créer le bundle OSGi org.dynaresume.dao.jpa.database.h2 qui s’occupe de retourner le type de la base de donnée H2 qui est utilisé ensuite dans le dialect JPA. Ce bundle expose en tant que service OSGi la valeur H2 avec l’id database. Mais nous verrons que ce procédé ne fonctionne pas car java.lang.String n’est pas une interface. Mais dans un premier temps nous allons effectuer la même chose que ce que nous avons fait dans un contexte non OSGi avec applicationContext-dao-jpa-database-h2.xml.

Créez le Bundle org.dynaresume.dao.datasource avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.jpa.database.h2
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Jpa/DAO Database H2
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in’s life cycle non cochée.

module-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<bean id="database" class="java.lang.String">
		<constructor-arg>
			<value>H2</value>
		</constructor-arg>
	</bean>
	
</beans>

module-osgi-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:service ref="database" interface="java.lang.String"  />

</beans>

Dans cette section nous avons transformé les fichiers XMl Spring applicationContext*xml (dans un contexte non OSGi) du billet précédant en bundle OSGi. Nous allons voir dans la suite tous les problèmes que ceci va poser dans un contexte OSGi.

Launch OSGi DynaResume – Full Client – EclipseLink-Derby

Dans cette section nous allons mettre en place JPA/EclipseLink avec la bases de données Derby et appeler le service UserService via le bundle client org.dynaresume.simpleosgiclient.

Vous pouvez télécharger le projet org.dynaresume_step19-eclipselink-osgi.zip qui contient les projets expliqués dans cette section.

Nous allons nous occuper dans un premier temps de la base Derby. Dupliquez le launch OSGi DynaResume – Full Client et nommez le OSGi DynaResume – Full Client – EclipseLink-Derby.

Launch OSGi EclipseLink-Derby

Ajoutez au launch les bundles JPA/DAO « générique » :

  • org.dynaresume.dao
  • org.dynaresume.dao.datasource
  • org.dynaresume.dao.jpa
  • com.springsource.javax.persistence (2.0.0)

Ajoutez au launch la configuration de la base de donnée Derby :

  • org.dynaresume.config.dao.datasource.derby
  • org.dynaresume.dao.jpa.database.derby

Ajoutez au launch les bundles EclipseLink :

  • org.dynaresume.dao.jpa.vendor.eclipselink
  • org.eclipse.persistence.antlr
  • org.eclipse.persistence.asm
  • org.eclipse.persistence.core
  • org.eclipse.persistence.jpa

Ajoutez au launch le support Spring ORM/Jpa :

  • org.springframework.orm
  • org.springframework.jdbc
  • org.springframework.transaction

Veuillez vous assurer que :

com.springsource.sfl4j.api version A.5.6 est coché (et pas l’autre version).

Cliquez sur le bouton Validate Bundles pour vous assurer que tous les bundles requis ont été sélectionnés.

Erreur « Could not load JDBC driver class »

Lancez le launch et vous aurrez l’erreur :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource'
...
org.springframework.beans.MethodInvocationException: Property 'driverClassName' threw exception; nested exception is java.lang.IllegalStateException: Could not load JDBC driver class [org.apache.derby.jdbc.EmbeddedDriver]828 ...

Le driver JDBC n’est pas trouvé dans le ClassLoader, pour régler ce problème, importez le package org.apache.derby.jdbc dans le fragment org.dynaresume.config.dao.datasource.derby. Effectuez la même chose pour la base H2, autrement dit importez le package org.h2 dans le fragment org.dynaresume.config.dao.datasource.h2.

Avant de relancer le launch, cliquez quer le bouton « Validate Bundles », et vous verrez que le bundle Derby manque (du à l’import package que nous venons d’ajouter). Cochez le bundle com.springsource.org.apache.derby.

Erreur ClassNotFoundException: javax.persistence.spi.PersistenceProvider

Relancez le launch et vous aurrez l’erreur :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaVendorAdapter'
...
Caused by: java.lang.NoClassDefFoundError: javax/persistence/spi/PersistenceProvider

J’ai passé un temps four sur ce problème. La classe javax.persistence.spi.PersistenceProvider se trouve bien dans le JAR com.springsource.javax.persistence-2.0.0.jar (bundle com.springsource.javax.persistence qui fait partie de la Target Platform)

Le problème vient du MANIFEST.MF du JAR org.springframework.orm-2.5.6.A.jar :

javax.persistence;version="[1.0.0, 2.0.0)";resolution:=optional,
 javax.persistence.spi;version="[1.0.0, 2.0.0)";resolution:=optional,

La déclaration :

javax.persistence.spi;version="[1.0.0, 2.0.0)";resolution:=optional

signifie que l’import package javax.persistence.spi s’effectue pour une version du package compris entre 1.0.0 et 2.0.0 (non inclus). Hors notre JAR com.springsource.javax.persistence-2.0.0.jar fournit un package de version 2.0.0.

Lorsque nous avons cliqué sur le bouton « Validate Bundle » pour valider les versions des bundles, PDE ne nous a pas levé d’erreur car cette contrainte est mis en optional (resolution:=optional). Dans le cas d’EclipseLink la version JPA 2.0.0 est requise. Pour résoudre le problème il faut que cette contrainte dans le bundle Spring ORM de version ne filtre pas notre version 2.0.0.

Ce problème se retrouve aussi pour d’autre classes de EclipseLink du à cette déclaration dans le MANIFEST.MF de Spring ORM :

...
org.eclipse.persistence.expressions;version="[1.0.0, 2.0.0)"
 ;resolution:=optional,org.eclipse.persistence.internal.databaseaccess
 ;version="[1.0.0, 2.0.0)";resolution:=optional,org.eclipse.persistenc
 e.internal.sessions;version="[1.0.0, 2.0.0)";resolution:=optional,org
 .eclipse.persistence.jpa;version="[1.0.0, 2.0.0)";resolution:=optiona
 l,org.eclipse.persistence.sessions;version="[1.0.0, 2.0.0)";resolutio
 n:=optional
...

Etant donné que Spring ORM fonctionnait avec EclipseLink dans un contexte NON OSGi, je souhaitais garder la version de Spring ORM que j'ai modifié (voir JAR org.springframework.orm-2.5.6.A-patch.jar du zip org.dynaresume_step19-eclipselink-osgi.zip) en remplaçant les déclarations "2.0.0)" en "2.0.1]" dans le JAR (j'ai essayé 2.0.0] mais ca ne marchait pas???).

Veuillez remplacez les déclarations "2.0.0)" en "2.0.1]" dans le JAR de Spring ORM, ou récupérez le dans le zip. Faites bien attention à supprimer physiquement le JAR org.springframework.orm-2.5.6.A.jar du projet spring-target-platform-dao.

Rafraichissez votre Target Platform (Window Preferences -> Plug-In Developments -> target Platform + selection de la Target Plaform + clic sur le bouton Reload)

Erreur CGLIB avec java.lang.String

Relancez le launch et vous aurrez l'erreur:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaVendorAdapter' defined in URL [bundleentry://15.fwk29293232/META-INF/spring/module-context.xml]: Cannot resolve reference to bean 'database' while setting bean property 'database'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'database': FactoryBean threw exception on object creation; nested exception is org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces....

Cette erreur s'explique par le fait que le bean database est enregistré dans le registre de service OSGi avec interface="java.lang.String" (qui n'est pas une interface) :

<osgi:service ref="database" interface="java.lang.String"  />

L'attribut interface comme son l'indique doit être une interface! J'ai tenté de mettre les interfaces java.io.Serializable et java.lang.CharSequence (que j'ai mis aussi dans le osgi:reference)

<osgi:reference id="database" interface="java.lang.CharSequence" />

mais j'ai une erreur de conversion de java.lang.CharSequence en Enum org.springframework.orm.jpa.vendor.Database :

Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type [$Proxy3 implementing java.lang.CharSequence,org.springframework.aop.IntroductionInfo,java.io.Serializable,org.springframework.osgi.service.importer.ImportedOsgiServiceProxy,org.springframework.core.InfrastructureProxy,org.springframework.aop.SpringProxy] to required type [org.springframework.orm.jpa.vendor.Database] for property 'database'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy3 implementing java.lang.CharSequence,org.springframework.aop.IntroductionInfo,java.io.Serializable,org.springframework.osgi.service.importer.ImportedOsgiServiceProxy,org.springframework.core.InfrastructureProxy,org.springframework.aop.SpringProxy] to required type [org.springframework.orm.jpa.vendor.Database] for property 'database': no matching editors or conversion strategy found

Dans un contexte OSGi nous ne pouvons donc pas publier/consommer un service java.lang.String. Pour corriger le problème nous allons créer un fragment OSGi attaché au bundle org.dynaresume.dao.datasource qui contient la valeur "DERBY" dans une propriété. Ce procédé fonctionne mais implique que nous devons crééer un fragment par implémentation JPA (2) * base de données (2) = 4 fragments.

Spring 3.0 - SEL

Je pense qu'avec Spring 3.0 on peut gérer ce cas-ci d'une manière beaucoup plus élégante avec une interface IDatabaseProvider (ce qui évite de crééer 4 fragments et de pouvoir arrêter/lancer le service pour changer de dialect de base de données au runtime):

public interface IDatabaseProvider  {
  String getDatabase();
}

Le bundle org.dynaresume.dao.jpa.database.derby pourrait fournir une implémentation comme ceci :

public class DerbyDatabaseProvider implements IDatabaseProvider  {
  public String getDatabase() {
    return "DERBY";
  }
}

et enregistrer une instance de DerbyDatabaseProvider en tant que service OSGi :

<osgi:service ref="databaseProvider" interface="IDatabaseProvider"  />

Le bundle org.dynaresume.dao.jpa.vendor.eclipselink pourrait consommer ce service :

<osgi:reference id="databaseProvider" interface="IDatabaseProvider"  />

et utiliser ce bean avec une Expression Spring :

<bean id="jpaVendorAdapter"
		class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
		<property name="database" value="#{databaseProvider.database}" />
...

Fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.derby

Comme nous n'avons pas Spring 3.0, nous allons créer un fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.derby qui va exposer la propriété "DERBY" pour le bundle hôte org.dynaresume.dao.jpa.vendor.eclipselink.

Créez le fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.derby avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.jpa.vendor.eclipselink.database.derby
  • champs Version: 1.0.0.qualifier.
  • champs Name: Fragment DERBY Database for EclipseLink
  • Plug-in ID: org.dynaresume.dao.jpa.vendor.eclipselink

Créer le fichier META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<!-- Database Derby -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="properties">
			<props>
				<prop key="database">DERBY</prop>
			</props>
		</property>
	</bean>
	
</beans>

database devient une propriété (et plus un bean). Pour cela commentez ou supprimez la déclaration du bean database dans le fichier org.dynaresume.dao.jpa.vendor.eclipselink/META-INF/spring/module-osgi-context.xml :

<!--<osgi:reference id="database" interface="java.lang.String" />
-->

et modifiez la propriété database dans le fichier org.dynaresume.dao.jpa.vendor.eclipselink/META-INF/spring/module-osgi-context :

<property name="database" ref="database" />

par

<property name="database" value="${database}" />

Vous pouvez supprimer le bundle org.dynaresume.dao.jpa.database.derby puis sélectionner dans le launch le fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.derby.

Erreur CGLIB avec java.util.Properties

Relancez le launch. Si vous avez l'erreur :

Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type [java.lang.String] to required type [org.springframework.orm.jpa.vendor.Database] for property 'database';...

Ceci signifie que la valeur de database ne contient pas un nom de database connu (ex : DERBY, H2...).

Après avoir lancé le launch, l'erreur suivante s'affiche :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDAO': Injection of persistence fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in URL [bundleentry://13.fwk21722195/META-INF/spring/module-context.xml]: Cannot resolve reference to bean 'jpaProperties' while setting bean property 'jpaProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaProperties': FactoryBean threw exception on object creation; nested exception is org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces.

Cette erreur est la même que celle de CGLIB java.lang.String. En effet java.util.Properties n'est pas une interface. Nous pouvons cependant écrire le bean jpaProperties avec une interface java.util.Map. Pour cela :

  1. remplacez dans le fichier org.dynaresume.dao.jpa.vendor.eclipselink/META-INF/spring/module-osgi-context.xml la déclaration jpaProperties comme suit :
    <osgi:service ref="jpaProperties" interface="java.util.Map" />
  2. remplacez dans le fichier org.dynaresume.dao.jpa/META-INF/spring/module-osgi-context.xml la déclaration jpaProperties comme suit :
    <osgi:reference id="jpaProperties" interface="java.util.Map" />

Erreur ClassNotFoundException javax.transaction.*

Relancez et vous aurez l'erreur :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDAO': Injection of persistence fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory': Post-processing of the FactoryBean's object failed; nested exception is java.lang.NoClassDefFoundError: javax/persistence/spi/PersistenceUnitInfo
...

Pour résoudre ce problème il faut importer le package javax.persistence.spi dans le bundle org.dynaresume.dao.jpa. Si vous relancez vous aurez d'autres erreurs de ClassNotFoundException sur des classes appartenant aux packages javax.persistence.criteria et javax.persistence.metamodel. Pour evitez toutes ces erreurs, importez les 3 packages dans le bundle org.dynaresume.dao.jpa:

javax.persistence.criteria;version="2.0.0",
 javax.persistence.metamodel;version="2.0.0",
 javax.persistence.spi;version="2.0.0"

Erreur java.sql.SQLException: No suitable driver found for...

Relancez et vous aurrez l'erreur :

Internal Exception: java.sql.SQLException: No suitable driver found for jdbc:derby:C:/db/derby/dynaresume
Error Code: 0
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:309)
..

Je n'ai pas bien approfondi le problème, mais de ce que j'ai pu comprendre, la classe org.springframework.jdbc.datasource.DriverManagerDataSource ne marche pas bien dans un contexte OSGi. Pour résoudre le problème j'ai remplacé org.springframework.jdbc.datasource.DriverManagerDataSource par org.apache.commons.dbcp.BasicDataSource du projet Commons Apache- DBCP qui est d'ailleurs conseillé lorsque l'application est mise en production.

Vous pouvez trouver les bundles DBCP dans le répertoire step19-eclipselink-osgi/spring-target-platform-dao/lib/dbcp.

Dans le bundle org.dynaresume.dao.datasource, ajoutez le package :

  • org.apache.commons.dbcp;version="1.2.2.osgi"
  • et supprimez le package :
    org.springframework.jdbc.datasource;version="2.5.6.A"

Modifiez le fichier org.dynaresume.dao.datasource/META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<!-- DataSource -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		p:driverClassName="${database.driverClassName}" p:url="${database.url}"
		p:username="${database.username}" p:password="${database.password}"
		init-method="createDataSource" destroy-method="close">
	</bean>

</beans>

Sélectionnez les bundles DBCP au launch :

  • com.springsource.org.apache.commons.dbcp
  • com.springsource.org.apache.commons.pool

Relancez et vous devez voir apparaître la liste des Users provenant de la base Derby dans la console :

User [login=angelo (Derby), password=]
User [login=djo (Derby), password=]
User [login=keulkeul (Derby), password=]
User [login=pascal (Derby), password=]

Launch OSGi DynaResume – Full Client – EclipseLink-H2

Dans cette section nous allons mettre en place JPA/EclipseLink avec la bases de données H2 et appeler le service UserService via le bundle client org.dynaresume.simpleosgiclient.

Vous pouvez télécharger le projet org.dynaresume_step19-eclipselink-osgi.zip qui contient les projets expliqués dans cette section.

Fragment org.dynaresume.config.dao.datasource.h2

Si vous ne l'avez pas déja fait, importez le package org.h2 dans le fragment org.dynaresume.config.dao.datasource.h2.

Fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.h2

Nous allons créer un fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.h2 qui va exposer la propriété "H2" pour le bundle hôte org.dynaresume.dao.jpa.vendor.eclipselink.

Créez le fragment org.dynaresume.config.dao.jpa.vendor.eclipselink.database.h2 avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.jpa.vendor.eclipselink.database.h2
  • champs Version: 1.0.0.qualifier.
  • champs Name: Fragment H2 Database for EclipseLink
  • Plug-in ID: org.dynaresume.dao.jpa.vendor.eclipselink

Créer le fichier META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<!-- Database H2 -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="properties">
			<props>
				<prop key="database">H2</prop>
			</props>
		</property>
	</bean>
	
</beans>

Launch OSGi EclipseLink-H2

Dupliquez le launch OSGi DynaResume – Full Client – EclipseLink-Derby et nommez le en OSGi DynaResume – Full Client – EclipseLink-H2. Déselectionnez tous les bundles liés à derby puis sélectionnez tous les bundles lié à H2 soit :

  • org.dynaresume.config.dao.datasource.h2
  • org.dynaresume.config.dao.jpa.vendor.eclipselink.database.h2
  • com.springsource.org.h2

Relancez et vous devez voir apparaître la liste des Users provenant de la base H2 dans la console :

User [login=angelo (H2), password=]
User [login=djo (H2), password=]
User [login=keulkeul (H2), password=]
User [login=pascal (H2), password=]

Launch OSGi DynaResume – Full Client – Hibernate-Derby

Dans cette section nous allons mettre en place JPA/Hibernate avec la base de données Derby et appeler le service UserService via le bundle client org.dynaresume.simpleosgiclient.

Vous pouvez télécharger le projet org.dynaresume_step19-all-osgi.zip qui contient les projets expliqués dans cette section.

Bundle org.dynaresume.dao.jpa.vendor.hibernate

Ici nous allons déclarer les beans Spring définit dans le fichier XML Spring applicationContext-dao-jpa-hibernate.xml du billet précédant en créant le bundle OSGi org.dynaresume.dao.jpa.vendor.hibernate. Ce dernier s'occupe de configurer JPA/Hibernate, autrement dit fournir via des services OSGi, les 2 bean Spring :

  • jpaProperties : propriétés de JPA/Hibernate qui sont vides.
  • jpaVendorAdapter : implémentation JPA/Eclipselink de l'interface org.springframework.orm.jpa.JpaVendorAdapter qui est utilisé par le bean entityManagerFactoryBean du bundle implémentation Jpa/DAO org.dynaresume.dao.jpa

Créez le Bundle org.dynaresume.dao.jpa.vendor.hibernate avec les paramètres suivants :

  • champs ID: org.dynaresume.dao.jpa.vendor.hibernate
  • champs Version: 1.0.0.qualifier.
  • champs Name: Dynaresume Jpa/Hibernate DAO
  • champs Execution Environment: J2SE-1.5.
  • option generate an activator, a Java class that controls the plug-in's life cycle non cochée.

module-context.xml

Créez le fichier module-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<!-- JPA/Hibernate properties -->
	<bean id="jpaProperties"
		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
	</bean>

	<!-- JPA/Hibernate Vendor -->
	<bean id="jpaVendorAdapter"
		class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
		<property name="database" value="${database}" />
		<property name="generateDdl" value="false" />
		<property name="showSql" value="true" />
	</bean>

</beans>

module-osgi-context.xml

Créez le fichier module-osgi-context.xml dans le répertoire META-INF/spring du bundle comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  

http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<osgi:service ref="jpaProperties" interface="java.util.Map" />
	<osgi:service ref="jpaVendorAdapter"
		interface="org.springframework.orm.jpa.JpaVendorAdapter" />

</beans>

Import package

Importez les 3 packages utilisés dans la déclaration des bean Spring :

On peut remarquer que le package java.util* n'a pas besoin d'être importé.

Fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.derby

Nous allons créer un fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.derby qui va exposer la propriété "DERBY" pour le bundle hôte org.dynaresume.dao.jpa.vendor.hibernate.

Créez le fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.derby avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.jpa.vendor.hibernate.database.derby
  • champs Version: 1.0.0.qualifier.
  • champs Name: Fragment DERBY Database for Hibernate
  • Plug-in ID: org.dynaresume.dao.jpa.vendor.hibernate

Créer le fichier META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<!-- Database Derby -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="properties">
			<props>
				<prop key="database">DERBY</prop>
			</props>
		</property>
	</bean>
	
</beans>

Launch OSGi Hibernate-Derby

Dupliquez le launch OSGi DynaResume – Full Client – EclipseLink-Derby et nommez le OSGi DynaResume – Full Client – Hibernate-Derby. Déselectionnez tous les bundles liés à EclipseLink puis sélectionnez tous les bundles lié à Hibernate soit :

  • org.dynaresume.config.dao.jpa.vendor.hibernate.database.derby
  • org.dynaresume.dao.jpa.vendor.hibernate

javax.persistence version=1.99.0

Si vous relancez vous aurrez l'erreur :

java.lang.ClassNotFoundException: org.hibernate.ejb.HibernatePersistence

Ce qui est normal car les bundles d'implémentation JPA/Hibernate n'ont pas été sélectionné. Sélectionnez dans le launch les bundles :

  • com.springsource.antlr
  • com.springsource.javassist
  • com.springsource.net.sf.cglib (version 2.2.0)
  • com.springsource.org.dom4j
  • com.springsource.javax.xml.stream
  • com.springsource.org.apache.commons.collections
  • com.springsource.org.objectweb.asm
  • com.springsource.sfl4j.api (version 1.5.6)
  • com.springsource.org.hibernate
  • com.springsource.org.hibernate.annotations
  • com.springsource.org.hibernate.annotations.common
  • com.springsource.org.hibernate.ejb

Si vous cliquez sur le bouton "Validate Bundles", vous pouvez constater qu'il y a un problème de version sur javax.persistence :

En effet notre bundle javax.persistence vaut 2.0.0 et Hibernate ne supporte pas la version 2.0.0 de JPA. La version javax.persistence 1.99 peut convenir. Pour télécharger la version 1.99 de JPA, ajoutez au fichier spring-target-platform-dao/pom.xml la dépendance :

<dependency>
  <groupId>javax.persistence</groupId>  
  <artifactId>com.springsource.javax.persistence</artifactId>  
  <version>1.99.0</version> 
</dependency>

Puis lancez la target Copy-dependencies Dynaresume Target Platform DAOs pour télécharger com.springsource.javax.persistence-1.99.0.jar. Copiez le JAR dans la Target Platform spring-target-platform-dao\lib\javax puis rafraichissez Rafraichissez votre Target Platform (Window Preferences -> Plug-In Developments -> target Platform + selection de la Target Plaform + clic sur le bouton Reload)

Sélectionnez dans le launch :

  • com.springsource.jacvaxpersistence de version 1.99

Cliquez sur le bouton "Validate Bundle" et vous pouvez constater l'apparition de 2 nouvelles erreurs :

Les 2 erreurs se trouvent dans les 2 bundles :

  • org.dynaresume.domain. Pour résoudre ce problème, supprimez la version 2.0.0" du MANIFEST.MF du bundle org.dynaresume.domain :
    Import-Package: javax.persistence,
    ...
  • org.dynaresume.dao.jpa. Pour résoudre ce problème, modifiez le MANIFEST.MF du bundle org.dynaresume.dao.jpa comme suit :
    Import-Package: javax.persistence,
     javax.persistence.criteria;version="2.0.0";resolution:=optional,
     javax.persistence.metamodel;version="2.0.0";resolution:=optional,
     javax.persistence.spi,
    ...

    Concernant les packages javax.persistence et javax.persistence.spi, la version a été enlevée car dans les 2 implémentation JPA (Hibernate et EclipseLink) ces packages sont requis. Les packages javax.persistence.criteria et javax.persistence.metamodel sont utilisés uniquement en JPA 2 Eclipselink. Ils deviennent optionnel (resolution:=optional). Une autre solution aurrait été de créer un
    fragment OSGi qui ajoute ces dépendances (mais je trouve dommage de créer un nouveau fragment pour gérer ceci).

Fragment org.dynaresume.domain.hibernate.fragment

Relancez le launch et vous aurrez erreur :

2391 [SpringOsgiExtenderThread-8] ERROR org.hibernate.proxy.pojo.BasicLazyInitializer - Javassist Enhancement failed: org.dynaresume.domain.User
java.lang.RuntimeException: by java.lang.NoClassDefFoundError: org/hibernate/proxy/HibernateProxy
...

Pour résoudre le problème, il faut importer le package org.hibernate.proxy dans le bundle Domain org.dynaresume.domain, ce qui couple très fortement le bundle Domain à Hibernate (dan sle cas d'EclipseLink, Hibernate n'est pas requis). Une solution pour régler ce problème serait d'utiliser resolution="optionnal" mais lorsqu'une nouvelle implémentation JPA serait géré, le bundle Domain devra être aussi mis à jour avec de nouvelles classes JPA de type Proxy. La solution la plus propre que j'ai trouvé est de créer un fragment OSGi attaché au bundle Domain qui enrichit les import packages.

Créez le fragment org.dynaresume.doman.hibernate avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.datasource.derby
  • champs Version: 1.0.0.qualifier.
  • champs Name: Hibernate Fragment for Domain
  • Plug-in ID: org.dynaresume.domain

Importez les packages :

  • org.hibernate.proxy
  • javassist.util.proxy

java.lang.NoClassDefFoundError: javax/transaction/SystemException

Selectionnez le fragment org.dynaresume.domain.hibernate.fragment puis relancez, l'errreur suivant s'affiche :

java.lang.NoClassDefFoundError: javax/transaction/SystemException
at org.hibernate.ejb.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:39)
...

Cochez dans le launch :

  • com.springsource.javax.transaction

Frgament org.dynaresume.services.impl.hibernate.fragment

Relancez et vous aurrez l'erreur :

java.lang.IllegalArgumentException: org.hibernate.QueryException: ClassNotFoundException: org.hibernate.hql.ast.HqlToken [select u from org.dynaresume.domain.User u]
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:601)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:96)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...

Pour résoudre ce problème, il faut que le bundle d'implémentation de services org.dynaresume.services.impl importe le package org.hibernate.hql.ast. Pour éviter de "polluer" le bundle org.dynaresume.services.impl, nous allons créer le fragment org.dynaresume.services.impl.hibernate.fragment qui effectue cela.

Créez le fragment org.dynaresume.services.impl.hibernate.fragment avec les paramètres suivants :

  • champs ID: org.dynaresume.services.impl.hibernate.fragment
  • champs Version: 1.0.0.qualifier.
  • champs Name: Hibernate Fragment for Services Implementation
  • Plug-in ID: org.dynaresume.services.impl

Importez les packages :

  • org.hibernate.hql.ast

Sélectionnez le fragment dans le launch puis relancez et vous devez voir apparaître la liste des Users provenant de la base Derby dans la console :

User [login=angelo (Derby), password=]
User [login=djo (Derby), password=]
User [login=keulkeul (Derby), password=]
User [login=pascal (Derby), password=]

Launch OSGi DynaResume – Full Client – Hibernate-H2

Dans cette section nous allons mettre en place JPA/Hibernate avec la base de données H2 et appeler le service UserService via le bundle client org.dynaresume.simpleosgiclient.

Vous pouvez télécharger le projet org.dynaresume_step19-all-osgi.zip qui contient les projets expliqués dans cette section.

Fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.h2

Nous allons créer un fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.h2 qui va exposer la propriété "H2" pour le bundle hôte org.dynaresume.dao.jpa.vendor.hibernate.

Créez le fragment org.dynaresume.config.dao.jpa.vendor.hibernate.database.h2 avec les paramètres suivants :

  • champs ID: org.dynaresume.config.dao.jpa.vendor.hibernate.database.h2
  • champs Version: 1.0.0.qualifier.
  • champs Name: Fragment H2 Database for Hibernate
  • Plug-in ID: org.dynaresume.dao.jpa.vendor.hibernate

Créer le fichier META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

	<!-- Database H2 -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="properties">
			<props>
				<prop key="database">H2</prop>
			</props>
		</property>
	</bean>
	
</beans>

Launch OSGi Hibernate-H2

Dupliquez le launch OSGi DynaResume – Full Client – Hibernate-Derby et nommez le en OSGi DynaResume – Full Client – Hibernate-H2. Déselectionnez tous les bundles liés à derby puis sélectionnez tous les bundles lié à H2 soit :

  • org.dynaresume.config.dao.datasource.h2
  • org.dynaresume.config.dao.jpa.vendor.hibernate.database.h2
  • com.springsource.org.h2

Relancez et vous devez voir apparaître la liste des Users provenant de la base H2 dans la console :

User [login=angelo (H2), password=]
User [login=djo (H2), password=]
User [login=keulkeul (H2), password=]
User [login=pascal (H2), password=]

Launch OSGi DynaResume - Server Jetty 6.1.9/Tomcat 5.5 - EclipseLink-(Derby/H2)

Pour tester l'architecture Client/Serveur, Vous pouvez trouvez dans le zip org.dynaresume_step19-all-osgi.zip les launch coté serveur (Jetty 6.1.9/Tomcat 5.5) qui utilise Jetty avec EclipseLink/Hibernate et Derby/H2.
Je n'explique pas dans ce billet comment ces launch sont configurés, car ils suivent la même configuration que ce que nous avons fait jusqu'à maintenant.

On peut juste remarquer que :

  • dans le cas de Jetty, CGLIb version 2.1.3 est sélectionné, sinon l'erreur suivante apparaît :
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'warDeployer'
    ...
    org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces.
  • dans le cas d'Hibernate, CGLIb version 2.2.0 est requis. Dans le launch Jetty+Tomcat les 2 versiosn de CGLIB sont utilisées et peuvent cohabiter ensemble.
  • dans le cas d'Hibernate, les fragments (qui permettent de résoudre les "Proxy" Hibernate) org.dynaresume.services.impl.hibernate.fragment et org.dynaresume.domain.hibernate.fragment doivent être dans le launch.
  • le bundle com.springsource.javax.transaction doit être ajouté (au moins dans le cas d'Hibernate).

Launch OSGi DynaResume - Client (Server)

Si vous cliquez sur le bouton "Validate Bundle" du launch Client (dans une architecture Clent/Serveur), vous aurrez une erreur qui dit que l'import package "javax.persistence" ne peux pas êter résolu. Dans le cas d'un Client (dans une architecture Clent/Serveur), les annotations JPA ne sont pas utilisées. Pour éviter de devoir ajouter le bundle javax.persistence dans le launch, il suffit de modifier dans le MANIFEST.MF du bundle org.dynaresume.domain le mode de résolution de l'import du package en optionnal. Pour cela modifiez le MANIFEST.MF du bundle org.dynaresume.domain comme suit :

javax.persistence;resolution:=optional,

Avec Hibernate je m'attendais a avoir des problèmes de Proxy coté Client (dans une architecture Clent/Serveur), mais je n'en ai pas eu. Il serait intéressant de voir en mode lazy ce qui se passe avec les Proxy Javassist et CGLIB.

OSGi DynaResume - RCP Client (Server)

Si vous avez effectué la modification ci dessous, le launch n'a pas besoin d'être modifié.

OSGi DynaResume - RCP Full Client - EclipseLink-(Derby/H2)

Les launch OSGi sont en Default-Auto Start à "true", ce qui permet de lancer les bundles OSGi automatiquement. Les launch RCP sont en Default-Auto Start à "false" pour des raisons de performances et éviter de lancer tous les bundles OSGi de la Target Platform. Pour rendre opérationnel les launch OSGi DynaResume - RCP Full Client - EclipseLink-(Derby/H2), il faut forcer l'Auto-Start à true des bundles :

  • org.dynaresume.dao.jpa, qui est l'implémentation DAO en Jpa.
  • org.dynaresume.dao.datasource, qui est le pooling de connection.
  • org.dynaresume.dao.jpa.vendor.eclipselink, qui est l'implémentation JPA en EclipseLink.

OSGi DynaResume - RCP Full Client - Hibernate-(Derby/H2)

Les launch OSGi sont en Default-Auto Start à "true", ce qui permet de lancer les bundles OSGi automatiquement. Les launch RCP sont en Default-Auto Start à "false" pour des raisons de performances et éviter de lancer tous les bundles OSGi de la Target Platform. Pour rendre opérationnel les launch OSGi DynaResume - RCP Full Client - Hibernate-(Derby/H2), il faut forcer l'Auto-Start à true des bundles :

  • org.dynaresume.dao.jpa, qui est l'implémentation DAO en Jpa.
  • org.dynaresume.dao.datasource, qui est le pooling de connection.
  • org.dynaresume.dao.jpa.vendor.hibernate, qui est l'implémentation JPA en Hibernate.

Probleme bundle exporter & timeout

Lors de certains lancements des launch coté serveur (généralement avec la base H2), le bundle qui exporte les services est appelé AVANT le service qui expose l'implémentation du service (du à un temps de latence je pense du démarrage de la base de donnée). Voici l'erreur que j'avais de temps en temps :

2234 [Timer-2] ERROR org.springframework.web.servlet.DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name '/UserService' defined in URL [bundleentry://34.fwk30758157/META-INF/spring/module-context.xml]: Cannot resolve reference to bean 'userService' while setting bean property 'service'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService': FactoryBean threw exception on object creation; nested exception is org.springframework.osgi.service.ServiceUnavailableException: service matching filter=[(objectClass=org.dynaresume.services.UserService)] unavailable
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:275)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:104)

Ce problème vient du fait que le bundle qui exporte les services org.dynaresume.remoting.exporter.http récupère l'implémentation du services comme ceci :

<osgi:reference id="userService" interface="org.dynaresume.services.UserService"
		timeout="1000"/>

et parfois le timeout ne suffit pas, autrement dit le service implémentation est initialisé APRES que ce bundle exporte le service, d'ou l'erreur ci dessous. Pour résoudre le problème on pourrait augmenter le timeout, mais le problème peut revenir un jour ou l'autre. Une solution propre que j'ai trouvé est de mettre cardinality="0..1"comme ceci :

<osgi:reference id="userService" interface="org.dynaresume.services.UserService"
		cardinality="0..1"/>

Cette déclaration cardinality="0..1" indique que le service n'est pas obligatoire. Dans le cas ou le service implémentation est initialisé APRES que le bundle org.dynaresume.remoting.exporter.http exporte le service, ceci fontionne bien.

Create User

A ce stade, la liste des Users s'affiche correctement mais la creation d'un User ne s'effectue pas correctement (le commit ne s'effectue pas). Pour mettre en evidence ce problème nous allons creer un bouton "Create User" (à coté du bouton "Refresh Users") dans l'application RCP qui creera un User via le service UserService.

Vous pouvez télécharger le projet org.dynaresume_step19-final.zip qui contient les projets expliqués dans cette section.

RCP - Bouton "Create User"

Modifiez la classe org.dynaresume.simplercpclient.View de l'application RCP org.dynaresume.simplercpclient pour ajouter le bouton "Create User" comme suit :

// Create User Button
		Button createUserButton = new Button(parent, SWT.NONE);
		createUserButton.setText("Create User");
		createUserButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				String login = "Login_" + System.currentTimeMillis();
				String password = "";
				userService.createUser(login, password);
				refresh();
			}
		});

Relancez l'application RCP et le bouton "Create user" apparaît :

Cliquez sur le bouton "Create User", et vous pouvez constatez que la liste de User n'est pas rafraîchit avec le User qui devrait être créé.

@Transactional & tx:annotation-driven

Nous avons vu dans un billet précédant que si le commit ne s'effectue pas sur la l'entityManger JPA, il n'y avait aucune erreur soulévée. C'est ce qui se passe dans notre cas. Nous avons utilisé @Transactional de Spring :

@Transactional(readOnly=false)
public User createUser(String login, String password) {
	User user = new User(login, password);
	return userDAO.saveUser(user);
}

Mais cette annotation n'est pas prise en compte. L'activation de ces annotations s'effectuent via tx:annotation-driven :

<tx:annotation-driven transaction-manager="txManager" />

qui à ce stade est déclaré dans le bundle JPA org.dynaresume.dao.jpa et pas dans le bundle Implémentation Service qui utilise @Transactionnal. Pour résoudre le problème de commit, il faut :

  1. publier en tant que service OSGi le bean "txManager" du bundle JPA org.dynaresume.dao.jpa.
  2. pour qu'il soit ensuite consomé dans le bundle Implémentation Service org.dynaresume.services.impl.

Modification bundle JPA org.dynaresume.dao.jpa

Publiez le bean txManager en ajoutant cette déclaration dans le fichier org.dynaresume.dao.jpa/META-INF/spring/module-osgi-context.xml :

<osgi:service ref="txManager" interface="org.springframework.transaction.PlatformTransactionManager" />

org.springframework.transaction.PlatformTransactionManager est l'interface des gestionnaire de transaction. Importez ensuite le package :

org.springframework.transaction;version="2.5.6.A"

Modification bundle Impl Services org.dynaresume.services.impl

Consommez le servive txManager en ajoutant la déclaration dans le fichier org.dynaresume.services.impl/META-INF/spring\module-osgi-context.xml:

<osgi:reference id="txManager" interface="org.springframework.transaction.PlatformTransactionManager" />

Activez @Transactional à l'aide de <tx:annotation-driven en modifiant le fichier org.dynaresume.services.impl/META-INF/spring/module-context.xml comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"	
	xsi:schemaLocation="http://www.springframework.org/schema/beans	

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	
	<bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl">
		<property name="userDAO" ref="userDAO" />
	</bean>
	
	<tx:annotation-driven transaction-manager="txManager" />
	
</beans>

Vous pouvez remarquer qu'un nouveau namespace xmlns:tx="http://www.springframework.org/schema/tx" a été ajouté au fichier XML Spring pour pouvoir utiliser <tx:annotation-driven.

Importez ensuite les packages pour éviter les erreurs de ClassNotFoundException :

 org.aopalliance.aop;version="1.0.0",
 org.springframework.aop;version="2.5.6.A",
 org.springframework.aop.framework;version="2.5.6.A",
 org.springframework.transaction;version="2.5.6.A"

Relancez et si vous cliquez sur le bouton "Create User", le User doit apparaitre dans la liste :

Conclusion

La mise en place des DAO avec JPA dans un contexte OSGi a été la tâche ou j'en ai le plu sué. A mon sens le plus difficile est de trouver les bon import package à insérer dans les bon bundles. Je ne sais pas si c'est parce que je ne sais pas bien utiliser PDE, mais pour importer les bons packages ceci ne peut s'effectuer qu'au lancement de la console OSGi (on démarre la console OSGi, on a une erreur de ClassNotFoundException, on importe le package dans le bon bundle). Grossièrement, il faut importer un package :

  • pour compiler une classe Java du Bundle qui utilise une auter classe d'un autre Bundle.
  • pour utiliser un package des classes déclarés dans les fichiers XML Spring.s
  • proxy hibernate dans le cas du bundle services (implémentation) et HqlToken dans le cas du bundle Domain.
  • pour utiliser driver JDBC dans le bundle datasource.

Une autre règle importante (suryout quand on passe d'un contexte NON OSGi en OSGi), est que les beans qui sont publiés/consommés doivent implémenter une interface et l'attribut interface de (<osgi:service et <osgi:reference) doit être une interface.

Une grosse difficulté sur laquelle j'ai passé beaucoup de temps est le fait que Spring ORM ne support pas JPA 2.0, mais ceci se fait silencieusement, du à son MANIFEST.MF :

javax.persistence;version="[1.0.0, 2.0.0)";resolution:=optional...

Spring ORM gère plusieurs implémentation JPA et c'est pour cela que resolution:=optional à été utilisé, mais à auncun moment PDE peut nous dire que la dépendance n'est pas respectée (avec EclipseLink par exemple). Peut être serait il mieux que Spring ORM soit découpé en plusieurs bundles (ORM Core, ORM EclipseLink...)?

Pour terminer EclipseLink a été utilisé dans ce billet en désactivant le weaving. Si vous souhaitez utiliser EclipseLink dans un contexte OSGi avec Spring en activant le Weaving (pour gérer le mode lazy), Springweaver 1.0.2 pourra peut être vous aider.

About these ads
  1. Yoann
    juin 25, 2010 à 12:18

    Merci vraiment pour toute cette série d’articles.

    A partir de tes sources, j’ai tenté de faire une écriture en m’appuyant sur un « createUser » a partir du service « userService »
    Aucun message d’erreur n’est lancé pourtant la base de donnée n’est pas modifiée.
    As tu une idée de ce qui pourrait se passer ?

    • juin 25, 2010 à 12:27

      Bonjour,

      > Merci vraiment pour toute cette série d’articles.
      Merci a toi de tes encouragements.

      >A partir de tes sources, j’ai tenté de faire une écriture en m’appuyant sur un « createUser » a partir du service « userService »
      >Aucun message d’erreur n’est lancé pourtant la base de donnée n’est pas modifiée.
      >As tu une idée de ce qui pourrait se passer ?

      Je ne sais plus dans quel billet j’en avais parle, mais d’apres mes souvenir si le commit n’est pas effectue en JPA, il n’y a aucuen erreur qui est lancé. Je pense qu’il doit y avoir un problème avec le transaction manager. As tu mis @Transactional sur ton service?

      Bon courage!

      Angelo

  2. Yoann
    juin 25, 2010 à 1:41

    En effet, j’avais bien noté que JPA ne faisait pas de commit.

    Le truc, c’est que j’utilise tom implémentation du service telle quelle est fournie dans le fichier Zip que tu as mis à disposition (« org.dynaresume_step19-all-osgi.zip »).
    Et il y a bien l’annotation @Transactional de mentionnée

    En fait, je nai fait que modifier le client pour appeler la methode « createUser » du service

  3. juin 25, 2010 à 2:31

    En effet je viens de faire le test et ca ne commite pas? Si tu trouves d’ou vient le problème je suis preneur. Merci!

    Angelo

  4. Yoann
    juin 28, 2010 à 9:01

    Après plusieurs jours de recherche, j’ai trouvé la cause.

    En fait cela vient de la gestion des transactions dans un contexte OSGi.
    A priori, l’intercepteur AOP ne peut intercepter les appels de methodes @Transactional que dans le bundle ou le contexte d’application a été défini.

    Comme le service est défini dans un bundle différent de celui du JPA, l’annotation n’est pas traitée, et du coup les modifications dans la base ne sont pas commitées.

    Pour résoudre cela il faut :
    1) Exporter le TransactionManager en tant que service OSGi en rajoutant cette ligne dans le fichier « module-osgi-context.xml » du bundle « org.dynaresume.dao.jpa »:

    2) Importer le service « txManager » dans le bundle ou l’annotation @Transactional se situe en rajoutant cette ligne dans le fichier « module-osgi-context.xml » du bundle « org.dynaresume.services.impl »:

    3) Rajouter dans le fichier MANIFEST.MF la déclaration du package « org.springframework.transaction » afin de ne pas avoir un ClassNotFoundException.

    En espérant que cela puisse aider d’autres personnes qui, comme moi, débutent en OSGi…

    • Yoann
      juin 28, 2010 à 9:13

      Avec les lignes :
      1)

      2)

  5. juin 29, 2010 à 12:05

    Bonsoir Yoann,

    Merci pour toutes ces precisions! Je viens de mettre a jour le billet avec une nouvelle section http://angelozerr.wordpress.com/2010/04/27/rcp_springdm_step19/#CreateUser qui explique comment régler le problème que tu as aussi expliqué.

    J’ai aussi changé la conclusion, car j’ai réussi a gérer le Weaving (utilise pour le mode lazy) de EclipseLink dans un contexte OSGi+Spring avec Springweavr de Martin Lippert (que j’ai modifié). Pour plus d’info voir http://angelozerr.wordpress.com/about/springweaver/.

  6. celeraman+
    septembre 7, 2010 à 3:23

    Merci!
    That was awesome! Congratulations!

    I tried to replace Embedded Derby Driver (org.apache.derby.jdbc.EmbeddedDriver) by Network Client (org.apache.derby.jdbc.ClientDriver).

    First of all, I downloaded com.springsource.org.apache.derby.client bundle from SpringSource’s repository and dropped it into target platform.

    So, I replaced org.apache.derby.jdbc package by org.apache.derby.client package in org.dynaresume.config.dao.datasource.derby bundle’s Imported Packages.

    Finally, I replaced the connection url by ‘jdbc:derby://localhost:1527/dynaresume;create=true’ and I started the Derby Network Server. Voilá, I got « Cannot load JDBC driver class ‘org.apache.derby.jdbc.ClientDriver' ».

    If I put com.springsource.org.apache.derby.client bundle in Required Plug-ins of org.dynaresume.dao.datasource all work Ok!

    OMG! I cant’t figure out which packages should be in org.dynaresume.config.dao.datasource.derby bundle’s Imported Packages.
    Any help would be most welcome!

    More one time, thank you for the articles. That was really awesome!

    • septembre 7, 2010 à 7:45

      Thank you for your congratulation. I’m sorry I have nor time to try to resolve your problem. It seems that your modification is OK. If you resolve your problem, could you send your comment in this blog. Thanks!

      Regards Angelo

  1. avril 27, 2010 à 4:12

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 169 autres abonnés

%d blogueurs aiment cette page :