Développement OSGi pour serveur Karaf – Part 3

image_pdfimage_print

Dans la partie 2, nous avons commencer à mettre en place une application de vente/échange de spiritueux et de vins qui va nous servir de support le la suite des opérations. Cette partie va concerner le module persistance. En effet, pour le moment, notre sauvegarde de données se fait dans une ArrayList ; niveau persistance on a déjà vu mieux! C’est pourquoi nous allons, dans cette partie, mettre en place une solution de type base de données. Personnellement en ce moment, j’aime beaucoup les base de données de type document , mais pour les besoin de l’exemple je vais utiliser une base de données “classique” du genre Oracle, mySQL, postgres, derby, hsql et consorts.

La source de données

Lorsque dans un serveur J2EE classique nous utiliserions un connecteur JNDI pour se coupler à la DataSource, dans un serveur OSGI nous allons utiliser un service OSGI … normal ! La création de notre DateSource va être simplifiée par l’utilisation de blueprint encore une fois. La définition du DataSource va se faire par la création d’un bean implémentant l’interface “javax.sql.DataSource “corespondant à la base de données que vous souhaitez attaquer. Puis une fois ce bean créer, nous exposerons un service répondant à l’interface “javax.sql.DataSource” et référençant notre bean et lui donnant un p’tit nom à la JNDI. Dans l’exemple nous allons utiliser une base full Java nommé H2.

Dans karaf, il ne faut pas oublier que les applications n’emportent pas leurs dépendances – et cela vaut aussi pour le drivers de base de données – et qu’il faudra donc installer le drivers dans karaf. Afin de vous laisser le choix des armes voici une liste des dépendances utilisables pour quelques bases couramment rencontrées :

Base de données Installation des dépendances dans Karaf
DB2 mvn install:install-file -DgroupId=com.ibm.db2.jdbc -DartifactId=db2jcc -Dversion=10.1 -Dpackaging=jar -Dfile=”C:\Program Files (x86)\IBM\SQLLIB\java\db2jcc.jar”
DERBY install -s mvn:org.apache.derby/derby/10.10.1.1
H2 install -s mvn:com.h2database/h2/1.3.170
HSQL install -s mvn:org.hsqldb/hsqldb/2.2.9
MYSQL install -s mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.stax-api-1.2/2.2.0install -s mvn:mysql/mysql-connector-java/5.1.24
ORACLE mvn install:install-file -Dfile=”C:\oraclexe\app\oracle\product\11.2.0\server\jdbc\lib\ojdbc6.jar” -DgroupId=ojdbc -DartifactId=ojdbc -Dversion=11.2.0.2.0 -Dpackaging=jar
POSTGRES install -s wrap:mvn:postgresql/postgresql/9.1-901-1.jdbc4

Plus tard, nous utiliserons les facilités offertes par le fichier features.xml qui permettera de faciliter la “mise en production”.

L’installation de la dépendance pour H2 se fera donc de la façon suivante :

osgi:install -s mvn:com.h2database/h2/1.3.170

Une fois le driver de base de données installé, il nous faut créer notre service permettant d’interagir avec la base. Comme nous l’avons vue dans la partie précédente créer un service n’est pas chose compliquée et pour preuve voici le code pour notre service de base de données :

<!--?xml version="1.0" encoding="UTF-8"?-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

  <bean id="h2DataSource" class="org.h2.jdbcx.JdbcDataSource">
      <property name="URL" value="jdbc:h2:~/cellar_bd"/>
      <property name="user" value="john"/>
      <property name="password" value="doe"/>
  </bean>

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

Une fois ce fichier XML défini nous avons en notre possession une data source définie par les critères suivant :

  • Base de données : H2
  • Nom de base : cellar_bd
  • User : john
  • Password : doe
  • Nom d’exposition du service d’accès : jdbc/cellar_bd

Maintenant la question est simple: comment l’utiliser ? JDBC ou JPA ? Avec quel framework OpenJPA, Hibernate ou autres ? Les possibilités sont assez variées, certaines très simples d’autres beaucoup plus complexes !

Allez on se connecte !

Nous allons commencer par la base, c’est à dire la mise en place  d’une interaction avec la base utilisant JDBC.
Dans la partie 2, nous avions défini une classe SpiritServiceImpl dans le bundle persistence. Cette classe possédait une ArrayList en guise d’élément de persistance ; Le but du jeu va être de remplacer cette liste par une vraie base de données.

Dans le but de faire à peu près correctement les choses, nous allons remplacer les opérations sur la liste de spirit par un objet de type DAO que j’appellerai sans surprise … SpiritService.

public class SpiritDao {
	private DataSource dataSource;
	private static String TABLE_NAME = "cellar";
	private static String NAME = "name";
	private static String TYPE = "type";
	private static String PRICE = "price";

	public SpiritDao(final DataSource dataSource) {
		this.dataSource = dataSource;

		final String create = "create table " + TABLE_NAME + " (" + NAME
				+ " varchar(255), " + TYPE + " varchar(255), " + PRICE
				+ " real)";

		Connection connection = null;
		PreparedStatement statement = null;
		try {
			connection = dataSource.getConnection();
			ResultSet result = connection.getMetaData().getTables(
					connection.getCatalog(), "PUBLIC", "CELLAR",
					new String[] { "TABLE" });

			if (!result.isBeforeFirst()) {
				statement = connection.prepareStatement(create);
				statement.execute();

				init();
			}
		} catch (SQLException sqle) {
			sqle.printStackTrace();
		}  catch (Exception e) {
			e.printStackTrace();
		}finally {
			if (connection != null) {
				try {
					connection.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

	}

	public void insert(final Spirit spirit) throws Exception {
		final String insert = "INSERT INTO " + TABLE_NAME + " (" + NAME + ", " + TYPE
				+ ", " + PRICE + ") VALUES (?, ?, ?)";

		Connection connection = null;
		PreparedStatement statement = null;
		try {
			connection = dataSource.getConnection();
			statement = connection.prepareStatement(insert);

			statement.setString(1, spirit.getName());
			statement.setString(2, spirit.getType());
			statement.setFloat(3, spirit.getPrice());

			statement.executeUpdate();
		} catch (Exception e) {
			throw new Exception(e);
		} finally {
			try {
				if (statement != null)
					statement.close();
			} catch (SQLException sqle) {
				throw new Exception(sqle);
			}
			try {
				if (connection != null)
					connection.close();
			} catch (SQLException sqle) {
				throw new Exception(sqle);
			}
		}
	}

	public List<Spirit> findByType(String type) {
		final String select = "SELECT " + NAME + ", " + TYPE + ", " + PRICE + " FROM " + TABLE_NAME + " WHERE " + TYPE + " = ?" ;

		List<Spirit> spirits = new ArrayList<Spirit>();

		Connection connection = null;
		PreparedStatement statement = null;
		ResultSet resultSet = null;
		try {
			connection = dataSource.getConnection();
			statement = connection.prepareStatement(select);
			statement.setString(1, type);
			resultSet = statement.executeQuery();

			while (resultSet.next()) {
				String nameResult = resultSet.getString(1);
				String typeResult = resultSet.getString(2);
				Float priceResult = resultSet.getFloat(3);

				Spirit spirit = new Spirit();
				spirit.setName(nameResult);
				spirit.setType(typeResult);
				spirit.setPrice(priceResult);
				spirits.add(spirit);
			}

			return spirits;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (resultSet != null)
					resultSet.close();
			} catch (SQLException sqle) {
			}
			try {
				if (statement != null)
					statement.close();
			} catch (SQLException sqle) {
			}
			try {<?xml version="1.0" encoding="UTF-8"?>
<features name="spout- ${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
	<feature name='spout' version='${project.version}' description='Global spout install'>
		<feature>http-whiteboard</feature>
		<bundle dependency="true">mvn:com.h2database/h2/1.3.170</bundle>

		<bundle>mvn:fr.conceptit.tuto.spout/spout-model/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-persistence/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-ui/${project.version}</bundle>
	</feature>
</features>
				if (connection != null)
					connection.close();
			} catch (SQLException sqle) {
			}
		}
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	private void init()  throws Exception{
		insert(createSpirit("Kirsch", "simple", 12.20f));
		insert(createSpirit("Cognac", "simple", 12.20f));
		insert(createSpirit("Armagnac", "simple", 12.20f));
		insert(createSpirit("Vodka", "simple", 12.20f));
		insert(createSpirit("Whisky", "simple", 12.20f));

		insert(createSpirit("Pastis", "compose", 12.20f));
		insert(createSpirit("Pontarlier-Anis", "compose", 12.20f));
		insert(createSpirit("Anisette", "compose", 12.20f));
		insert(createSpirit("Ouzo", "simple", 12.20f));
		insert(createSpirit("Gin", "simple", 12.20f));
	}

	private Spirit createSpirit(final String name, final String type,
			final float price) {
		Spirit spirit = new Spirit();
		spirit.setName(name);
		spirit.setType(type);
		spirit.setPrice(price);

		return spirit;
	}
}
Remarque : Ce DAO vérifie à sa création si la table CELLAR existe, dans le cas contraire le DAO crée cette table. Au passage, on pousse quelques informations dedans afin de vérifier le bon fonctionnement de notre application. Si vous souhaitez vérifier que la base est bien créée et bien peuplée vous pouvez vous connecter à la base avec un logiciel tel que l’excellent SQuirreL.

L’utilisation de cette DAO à partir de notre service passera par une injection utilisant la capacité IOC de blueprint. Nous modifierons le fichier persistence.xml pour construire le DAO et le pousser vers le service. Au passage, un va injecter la DataSource dans le DAO :

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

	<!-- Definition de la DataSource -->
	<bean id="h2DataSource" class="org.h2.jdbcx.JdbcDataSource">
		<property name="URL" value="jdbc:h2:~/dev/db/cellar_bd" />
		<property name="user" value="john" />
		<property name="password" value="doe" />
	</bean>

	<!-- Exposition sous forme de service -->
	<service interface="javax.sql.DataSource" ref="h2DataSource">
		<service-properties>
			<entry key="osgi.jndi.service.name" value="jdbc/cellar_bd" />
		</service-properties>
	</service>

	<bean id="spiritDao" class="fr.conceptit.tuto.spout.dao.SpiritDao">
		<argument ref="h2DataSource"/>
	</bean>

	<!-- Création du service de gestion des spiritueux -->
	<bean id="spiritService" class="fr.conceptit.tuto.spout.service.SpiritServiceImpl">
		<property name="spiritDao" ref="spiritDao" />
	</bean>

	<!-- Exposition du service de gestion -->
	<service ref="spiritService" interface="fr.conceptit.tuto.spout.model.SpiritService" />
</blueprint>

Le Service s’en trouve simplifié :

public class SpiritServiceImpl implements SpiritService {
	private SpiritDao spiritDao;

	public void setSpiritDao(SpiritDao spiritDao) {
		this.spiritDao = spiritDao;
	}

	public boolean insert(Spirit spirit) {
		try {
			spiritDao.insert(spirit);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public List<Spirit> findByType(String type) {
		return spiritDao.findByType(type);
	}

}

Installation facile

Au début de ce post, nous avons vu que pour utiliser notre base de données il fallait installer le driver sur karaf. C’est rapide, facile mais le développeur peut encore faciliter la manipulation au client final. Pour ça on retourne sur notre fichier features.xml dans le projet spout-feature afin d’ajouter tout simplement le driver ou tout autre dépendance nécessaire.

<?xml version="1.0" encoding="UTF-8"?>
<features name="spout- ${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
	<feature name='spout' version='${project.version}' description='Global spout install'>
		<feature>http-whiteboard</feature>
		<bundle dependency="true">mvn:com.h2database/h2/1.3.170</bundle>

		<bundle>mvn:fr.conceptit.tuto.spout/spout-model/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-persistence/${project.version}</bundle>
		<bundle>mvn:fr.conceptit.tuto.spout/spout-ui/${project.version}</bundle>
	</feature>
</features>

Et voila c’est tout !
L’installation se ferra ainsi en une fois sans se poser de question sur les dépendances à installer à coté.

JDBC c’est bien mais après …

Il est toujours un peu fastidieux de travailler avec le JDBC surtout dans le cas de CRUD simpliste qui peuvent être facilités par tout ORM qui se respecte. C’est pourquoi la prochaine fois on ira voir du coté de JPA.

On se ferra aussi un petit module afin de tester l’insertion de donner et pour cela nous utiliserons une des facilitées liée à karaf … l’extension de shell.

4 comments

  1. […] Développement OSGi pour serveur Karaf – Part 3 […]

  2. eheb says:

    Est-ce que la partie 4 avec JPA est disponible ?
    allez-vous publier un maj pour karaf 3.0 car les commandes karaf ne sont pas compatibles.
    est-il possible de vous joindre par mail pour aller plus loin ?
    avez-vous des exemples avec 1 application kamel deployée sur karaf ?
    Grand merci pour vos travaux et votre reponse.

    • Pierre-Yves says:

      La partie 4 n’est pas encore disponible. Je vais essayer de la finaliser pour le mois prochain avec peut-être une partie dédiée à Hibernate (cela pourrait-il vous intéresser ?).

      Je n’avais pas prévu de faire une mise à jour pour karaf 3.0, mais si vous voulez vous pouvez m’exposer vos problèmes j’essaierai de vous aidez au mieux.

      Je n’ai pas d'”application tuto” à vous proposer concernant l’utilisation de Camel dans Karaf mais je suis à votre disposition pour vous aider à faire une telle application.

Leave a Reply

Your email address will not be published. Required fields are marked *