HOAB

History of a bug

Jacoco ne génère pas de rapport via Sonar

Rédigé par gorki Aucun commentaire

Le problème :

Configuration du job Jenkins pour Sonar avec les arguments Jacoco :

clean org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true

Et pourtant pas de couverture de code à la fin de l'exécution du build sonar.

Solution :

Simple quand on connait la réponse, conflit entre  :

  • les arguments dans le pom.xml du plugin Surefire
  • les arguements en ligne de commande du plugin Jacoco.

Du coup, les <argLine> du plugin Surefire dans le pom.xml doivent référencer la variable ${argLine}

 <argLine>-server -ea -XX:MaxPermSize=256m -Xmx4g -XX:-UseSplitVerifier ${argLine}</argLine> 

Extrait de stackoverflow

 

Spring, Autowire, Proxy Class

Rédigé par gorki Aucun commentaire

Le problème :

A l'origine, suite à une réorganisation des classes et un nettoyage des configurations Spring, une des classes (appelons-là ClasseA) n'est plus "Autowired" sur ClasseB.

Alors que :

- les dépendances entre configurations Spring sont OK

- le component scan est OK

- les traces de Spring montrent bien une instanciation de la classe ClasseA et une tentative d'"autowiring" sur la classe ClasseB.

Puis, plus rien, pas de trace d'erreur si ce n'est que l'autowiring indique qu'il n'a pas trouvé de candidat.

 

Solution :

Ce n'est pas très simple.

Première étape :

- le tag qui a fait marcher le test :

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />

Je ne voyais pas le lien entre les transactions spring et ma classe qui n'avait aucun rapport avec les transactions.

En réalité c'est le proxy-target-class="true" qui a fait marcher le test. Grâce à ce lien, il est indiqué que cet attribut s'applique partout !

 

Deuxième étape deux nouveaux tests :

- suppression du proxy-target-class:  le test est KO (revenu à l'état premier)

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

- ajout d'un tag général dédié aux proxies : le test est KO, il manque cette classe org.aspectj.util.PartialOrder$PartialComparable.

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<aop:config proxy-target-class="true" />

 

Troisième étape, en ajoutant le jar aspectjweaver (moins lourd que le aspecttools), ça a marché.

Donc c'était bien l'instanciation de ma classe via un JDK Proxy plutôt que via un CGLIB qui a changé la donne.

 

Pour aller plus avant :

- ma classe hérite de org.apache.commons.configuration.CompositeConfiguration

- j'ai essayé d'en extraire une interface pour utiliser les JDK Proxy plutôt que les CGI : échec sur les premiers tests. J'ai laissé filé.

- un mystère que je n'ai pas creusé non plus : pourquoi le fait d'utiliser le proxy-target-class sur le tag aop:config demande plus de fonctionnalités (PartialOrder manquant) que sur tx:annotation-driven ?

- les JDK proxy sont conseillés car ils implique une programmation par interface (pour ceux qui aiment...)

 

Moralité ( il est mort alité...) :

- d'où l'importance de ne pas laisser faire la configuration Spring à n'importe qui, de bien comprendre les tags et leur portées.

- contrôler la portée de vos component-scan, ça aide d'avoir des tests rapides...

 

Mise à jour :

Le problème a aussi une autre conséquence qui bloque le comportement Autowired:

- Si une classe CLASSE_REELLE (@Component ) implémente une interface INTERFACE_GENIALE

- Une classe B (de test par exemple) essaye d'injecter A directement

- aop:config n'est pas configuré pour empêcher l'utilisation des proxy Java ! Spring décide par défaut d'utiliser les proxy dès qu'une interface est présente. (cf ce lien :  JDK- and CGLIB-based proxies)

Conséquence : Spring ne qu'injecter des variables de types INTERFACE_GENIALE et pas de type CLASSE_REELLE. Seules les interfaces sont injectables.

 

Spring Boot et CXF

Rédigé par gorki Aucun commentaire

Le problème :

SpringBoot c'est assez pratique, le REST est géré de base avec Spring Boot MVC, mais parfois on a aussi besoin d'un serveur SOAP et là souvent CXF est utilisé plutôt que Spring MVC...)

Petit pense bête car les solutions trouvées sur le net me semble :

- compliquée : en utilisant Spring integration

- incomplète : déclaration simple

Solution :

En trois étapes :

  1. - Utiliser un fichier spring externe à Spring Boot
  2. - Déclarer la servlet CXF
  3. - Configurer Spring et CXF

Point 1 et 2 : dans l'application Spring Boot

Attention :
- dans notre exemple, CXF va gérer les URLs commençant par /soap/
- ne pas oublier de démarrer la servlet CXF

package com.test.springboot.cxf

import javax.annotation.PreDestroy;

import org.apache.cxf.transport.servlet.CXFServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

/**
 *
 */
@Configuration
@ImportResource("classpath:spring-cxf.xml")
@ComponentScan
@EnableAutoConfiguration()
public class SpringBootCxf {
    private static final Logger LOGGER = LoggerFactory.getLogger(SpringBootCxf.class);

    public static void main(final String[] args) throws Exception {
        SpringApplication.run(SpringBootCxf.class, args);
    }

    @Bean
    public ServletRegistrationBean cxfServlet() {
        ServletRegistrationBean servletDef = new ServletRegistrationBean(new CXFServlet(), "/soap/*");
        servletDef.setLoadOnStartup(1);
        return servletDef;
    }

    @PreDestroy
    public void exit() {
        LOGGER.info("Exiting");
    }
}

Point 3 : Le fichier de configuration Spring - CXF

Attention :
- le point important ici est le lien entre le fichier XML et le composant qui implémente le service :

implementor="#eventServiceSoap"

Fichier XML complet :

<?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:jaxws="http://cxf.apache.org/jaxws"
	xmlns:soap="http://cxf.apache.org/bindings/soap"
	xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<!-- these are included in the dependency jar -->
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<!-- event -->
	<jaxws:endpoint id="eventServiceEndpoint" implementor="#eventServiceSoap"
		address="/event">
		<jaxws:properties>
			<entry key="schema-validation-enabled" value="false" />
		</jaxws:properties>
		<jaxws:binding>
			<soap:soapBinding version="1.2" mtomEnabled="true" />
		</jaxws:binding>
	</jaxws:endpoint>	
</beans>

Ensuite on peut utiliser un composant "Autowired" by Spring :
- EventManagerBean est le résultat de la génération WSDL2Java

@Component("eventServiceSoap")
public class EventServiceImpl implements EventManagerBean {

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(EventServiceImpl.class);

    @Autowired
    EventRepository eventRepository;

    ...

 

 

 

 

Jinit Jinitiator Java7

Rédigé par gorki Aucun commentaire

Le problème :

Avec les vieilles versions d'Oracle Forms (10g2 - 10.1.2.0.2), un système existait pour remplacer le Java web start (JWS).. le Jinitiator (*fear !*).

Sauf que ce vieux système n'est plus maintenu. Donc rien ne se lance. Comme toujours une recherche donne des indices dont :

- réduire la version de Java

- Modifier Oracle Forms pour ne pas utiliser Jinitiator, c'est possible avec la 10g2 je crois

Mais comme par hasard, tout ça ne marche pas chez moi.

Solution :

Ce n'est pas simple... (testée avec la JVM 32bits 1.7.0_55-b13)

0) Activer la console Java pour tous les lancements, vérifier que vous avez l'erreur

ClassNotFoundException : oracle.forms.engine.Main

1) repérer où l'application Java est téléchargée... (Panneau de configuration -> Java -> Général -> PAramètres des fichiers temporaires -> Empalcement, exemple :

C:\Users\<user>\AppData\LocalLow\Sun\Java\Deployment\cache

2) Recherche l'archive de lancement dans les sous répertoires - un fichier de  1,151 Mo). C'est le fichier frmall_jinit.jar indiqué dans la source de la page de l'applet.

3) Surprise c'est un jar dans un jar ! Voilà pourquoi mon java ne pouvais pas lancer l'appli, une astuce Jinit...

3) Extraire le jar : forms.jar

4) Remplacer le fichier du cache par le fichier extrait forms.jar => Dans mon cas j'ai du remplacer ce fichier (conserver le nom du cache évidemment) :

C:\Users\<user>\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\0\2d74c040-2640a8d0

5) Actualiser la page et hop ! ça affiche quelque chose, mais ça plante encore... Eh oui, il y a une vérification de la version de Java dans la classe HTTPConnexion

oracle.forms.net.HTTPConnexion

6) décompilation, suppression du test, mise à jour du HTTPConnexion.class dans l'archive, recopie dans le cache, relance... easy ou presque.

 

A noter que cette astuce ne devrait pas servir souvent  :

  1. soit vous êtes admin et dans ce cas, modifiez votre configuration Oracle Forms pour utiliser JWS
  2. soit vous êtes un utilisateur courant et vous faites remonter le problème à votre admin
  3. sinon, comme moi, vous êtes utilisateur ponctuel et vous n'avez pas envie de réinstaller une VM XP Java 2 juste pour ça...

 

Cependant ça peut servir pour toutes les applets Java hein, un truc vous embête, modification, remplacement du cache et hop ça roule. C'est déjà utilisé couramment pour modifier les scores des jeux ^^ (java, flash ou autre)...

Personne ne trouve bizarre qu'un mec finisse un niveau en 0.1 seconde ou qu'un score grimpe à 999 999 999 ?!! :)

 

 

Un projet Web avec Hibernate tout simple (avec Maven, déjà ce n'est plus simple...)

Rédigé par gorki Aucun commentaire

Le problème :

Pour faire des tests, il peut être utile d'avoir un projet Hibernate minimaliste, sans fioriture pour tester simplement une fonctionnalité particulière du framework.

Et aussi si ça pouvait être une application WEB, ça serait cool.

Maven c'est pour éviter d'avoir à télécharger les librairies quand on a déjà Eclipse/Maven sur son poste.

On a bien quelques liens sur google, mais je ne trouvais pas ça super simple pour quelqu'un qui connaissait un peu hibernate : ici, google.

Solution :

Librement inspirée de ce site.

Prérequis :

  • savoir ce qu'est Hibernate, Maven, Eclipse, Java, JPA. Je n'explique rien ici, mais le web est là pour ça.
  • avoir un Eclipse récent qui supporte Maven en natif (à partir de la version Kepler je crois)
  • savoir créer un projet sous Eclipse et savoir où se placent les fichiers dans une arbo Maven (Sinon télécharger l'archive en fin de page, je sais GitHub existe...)
  • savoir piocher les bouts qui vont bien pour votre propre besoin, c'est un peu le mode bloc note ici.

.

  1. Créer le pom qui va bien :
    • Servlet pour la partie annotation Web
    • HSQLDB pour la base
    • SLF4J et Logback pour les logs
    • Hibernate pour Hibernate
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mygroup</groupId>
	<artifactId>testApp</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>

	<name>Webapp</name>

	<dependencies>
		<!-- Servlet -->

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>compile</scope>
		</dependency>

		<!-- BDD -->

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.3.2</version>
		</dependency>

		<!-- Logger -->

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

		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.1.1</version>
		</dependency>

		<!-- ORM -->

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.3.4.Final</version>
		</dependency>
		


	</dependencies>

	<build>

		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>
  1. Un fichier web.xml tout petit :
    • Attention ! à la version des servlets : 3.0 - pour pouvoir les déployer avec des annotations @WebServlet
    • Un servlet de startup pour initialiser la base mémoire
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">


	<display-name>Test Application</display-name>


	<servlet>
		<servlet-name>StartupGenerique</servlet-name>
		<servlet-class>com.mygroup.servlet.StartupServlet</servlet-class>
		<load-on-startup>0</load-on-startup>
	</servlet>

</web-app>
  1. Une servlet qui répond à toutes les requêtes (on fait dans le super simple)
    • La servlet répond au motif HTTP : "/". Donc toutes les requêtes....
    • Algorithme de haut-niveau :
      • Créer un objet DOG
      • Le sauvegarde
      • Recherche de l'objet DOG sauvé
      • Retourne une réponse
package com.mygroup.servlet;

// Import required java libraries
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mygroup.dao.DogDAO;
import com.mygroup.model.Dog;

/**
 * The Class TestServlet1.
 */
@WebServlet("/")
public class TestServlet extends HttpServlet {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The Constant LOGGER. */
	private static final Logger LOGGER = LoggerFactory.getLogger(TestServlet.class);

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		DogDAO dogDAO = new DogDAO();

		dogDAO.startConnection();

		String responseString = null;

		try {
			/*
			 * Save Dog
			 */
			Dog dog = new Dog();
			dog.setName("Beethoven " + System.currentTimeMillis());
			dog.setWeight(45);
			dogDAO.save(dog);

			/*
			 * Find dog
			 */
			dog = dogDAO.find(dog.getId());
			LOGGER.debug("Dog saved : {}", dog.getName());

			/*
			 * Response
			 */
			responseString = "OK, Dog saved : " + dog.getName();
		} catch (Exception e) {
			LOGGER.error("Erreur générale", e);
			responseString = "Erreur, voir log";
		} finally {
			dogDAO.commitConnection();
			dogDAO.closeConnection();
		}

		// Set response content type
		response.setContentType("text/html");
		// Actual logic goes here.
		PrintWriter out = response.getWriter();
		out.println(responseString);
	}
}
  1. Le DAO qui permet de manipuler les objets DOG et d'ouvrir les connexions à la BDD (via DatabaseUtils ci-dessous)
package com.mygroup.dao;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import com.mygroup.model.Dog;

public class DogDAO {

	private Transaction newTransaction;
	private Session newSession;

	public void startConnection() {
		newSession = DatabaseUtils.getSessionFactory().openSession();
		newTransaction = newSession.beginTransaction();

	}

	public void closeConnection() {
		newSession.close();
	}

	public void commitConnection() {
		newTransaction.commit();
	}

	public void save(Dog dog) {
		newSession.persist(dog);
	}

	public void edit(Dog dog) {
		newSession.merge(dog);
	}

	public Dog find(int dogId) {
		return (Dog) newSession.createCriteria(Dog.class).add(Restrictions.idEq(dogId)).uniqueResult();
	}

	public void remove(Dog dog) {
		newSession.delete(dog);
	}

	public List listALL() {
		return newSession.createQuery("from " + Dog.class).list();
	}
}
  1. Le modèle DOG qui est persisté en base de données (et quand est-ce qu'on pourra se passer de ces getters/setters....)
package com.mygroup.model;

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

@Entity
@Table(name = "dog")
public class Dog {

	public static final String LIST_ALL = "listALL";

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int id;
	private String name;
	private double weight;

	// Getters and Setters
	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

}
  1. La création de connexion via Hibernate
package com.mygroup.dao;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

import com.mygroup.model.Dog;

public class DatabaseUtils {

	private static SessionFactory sessionFactory;

	static {
		Configuration cfg = new Configuration().addAnnotatedClass(Dog.class);
		ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(cfg.getProperties()).build();
		sessionFactory = cfg.buildSessionFactory(serviceRegistry);
	}

	public static SessionFactory getSessionFactory() {
		return sessionFactory;
	}

}
  1. Le fichier hibernate.properties :
hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.connection.url = jdbc:hsqldb:mem:.
hibernate.connection.username = sa
hibernate.connection.password = 
hibernate.hbm2ddl.auto = update
hibernate.c3p0.min_size=1
hibernate.c3p0.max_size=2
hibernate.c3p0.timeout=5
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.HSQLDialect
  1. Le fichier logback pour les logs :
<configuration>

	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
			by default -->
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
			</pattern>
		</encoder>
	</appender>


	<logger name="com.mygroup" level="DEBUG" />


	<root level="error">
		<appender-ref ref="STDOUT" />
	</root>
</configuration>

Et voilà.

Alors attention, ça ne part pas en production comme ça, on est d'accord ! :)

Le zip avec le tout ici : SimpleWebappHibernate.7z

 

 

Fil RSS des articles de cette catégorie