HOAB

History of a bug

SpringBoot OSGI and templated OSGI modules with external constrant Jetty

Rédigé par gorki Aucun commentaire

Problem :

A very specific and particular problem.

I want to deploy  a SpringBoot application, in an OSGI environment, in a external Jetty server…

So I found solution do deploy Springboot in OSGI : here or here. Very good by the wat.

And also to deploy Springboot as war for an external Tomcat…

Too easy, my external Jetty server was rejecting Servlet 3.0 application startup… (hard coding for : 

context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);

Yes, it's better, starting a 1,25 days of try and success !

Solution :

My first web application was deployed with Spring Dispatcher Servlet with a BundleActivator

My second web application was deployed with Spring DispatcherServlet AND a SpringBootApplication intialized with the ressource loader of the BundleActivator (as in the OSGI example) : component-scan is working ! 

My third web application was switching to an initialization Servlet 3.0 with an extension of ServletContainerInitializer, thanks to harded jetty configuration, it's not working

My third and a half remove all web.xml content to keep only a context listener as my Jetty was doing nothing : 

<?xml version="1.0" encoding="ISO-8859-1"?>
<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">

    <!-- ServletContainerInitializer can not be used here -->
    <!-- XXXXXXX is putting in hard : context.setAttribute("org.eclipse.jetty.containerInitializers with only Jasper -->

    <!-- At the end, it's possible to load SpringBoot with this context listener -->
    <listener>
        <listener-class>
            com.hexagon.introscope.extension.MyContextLoaderListener
        </listener-class>
    </listener>

</web-app>

My fourth web application was trying to load the Springboot application file as a YAML file (why so simple !), resolved with a “hack” :

  • The ServletContext has a way to store the configuration directory, during the initialization, I saved it somewhere : HpaContextLoaderListener.getConfigDirectory() 

The listener loader : 

package com.xxxxx.extension;

import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import java.io.File;
import java.nio.file.Path;

public class MyContextLoaderListener implements ServletContextListener {

    private static File configDirectory;

    public static File getConfigDirectory() {
        return configDirectory;
    }

    public void contextInitialized(ServletContextEvent event) {
        ServletContext servletContext = event.getServletContext();
        MySpringBootHexagonActivator app = new MySpringBootHexagonActivator();

        try {
            configDirectory = servletContext.getAttribute("introscope.em"));
            
            InstantiateHelper.getValueByMethod(servletContext, "setExtendedListenerTypes", new Class[]{boolean.class}, true);

            app.onStartup(event.getServletContext());
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
    }

    public void contextDestroyed(ServletContextEvent event) {
    }
}

 

@SpringBootApplication
public class MySpringBootHexagonActivator extends SpringBootServletInitializer {

    protected SpringApplicationBuilder createSpringApplicationBuilder() {
        SpringApplicationBuilder builder = super.createSpringApplicationBuilder();
        Map<String, Object> properties = new HashMap<>();
        Path propertyFile = Path.of(MyContextLoaderListener.getConfigDirectory().getPath(), "application-extension.yml");
        properties.put("spring.config.location", propertyFile.toString());
        builder.properties(properties);
        builder.resourceLoader(HpaHexagonActivator.getResourceResolver());

        return builder;
    }
}

The HpaHexagonActivator.getResourceResolver() is the trick from SpringBoot OSGI example to load classes from Bundle classpath. I store the ressources resolver in a static variable that I can access from everywhere.

A word on that one : 

InstantiateHelper.getValueByMethod(servletContext, "setExtendedListenerTypes", new Class[]{boolean.class}, true);

I'm in OSGI, Jetty controls the listener class, and Springboot is unknown. You can authorized additional listeners but in my case, Jetty is not exported by the other OSGI package, not able to call it simply so… reflection : InstantiateHelper is basically a helper class to call methods by reflection.

 

At the end : 

  1. Springboot starts
  2. Springboot scans package offered by BundleActivator ressource locator
  3. Springboot can use an external configuration file, in YAML
  4. Springboot starts REST & Database
  5. It was hard.

Of course, lot of tries between these big steps !

The goal is to extend a 3rd party product without having source code.

Springboot & listening address

Rédigé par gorki Aucun commentaire

Problème :

J'ai une configuration un peu particulière :

  • je développe et teste majoritairement sous Linux, Ubuntu
  • pour certains cas, je lance une VM Windows 10 sous virtualbox pour tester IE/Edge

Je lance comme d'habitude mon application, j'essaie de m'y connecter depuis la VM impossible...

Configuration de la VM :
- en NAT
- en virtual network

Solution :

Evidemment je pense au paramètre `server.address` de Spring boot car j'ai un log qui m'affiche : Listening on 127.0.0.1:8080
Mais ça ne marche pas...

Je tente X configurations différentes : dans le fichier de configuration, en paramètre avec le -Dserver.address

Je tente avec le port forwarding NAT de Virtualbox. Nada.

La connexion SSH fonctionne : je peux me connecter depuis la VM

Je vérifie le firewall, pas activé.

Jé vérifie IPTables, ça me semble OK

Le netstat a du mal à sortir les LISTEN, j'utilise : lsof -i -P -n | grep LISTEN

Et là :

1) Springboot écoute bien par défaut sur toutes les IPv4, comme décrit a plein d'endroit, le server.address fonctionne très bien
2) le log affiché était applicatif et codé en dur : argh :(
3) c'était le iptable qui bloquait...  il fallait regarder le FORWARD et pas le INPUT... pas sur d'avoir compris pourquoi, a priori on ne change pas de réseau/machine. A creuser. Commande iptable pour ouvrir le port.

 

 

 

Springboot + Maven + Junit5

Rédigé par gorki Aucun commentaire

Problème :

Migration de springboot vers  la 2.1.1 et Junit5... et plus rien ne marche, tests introuvables.

Après de multiples tests, ça marchait dans Intellij Idea, mais pas sous maven (MockBean retournait null....)

Solution :

J'ai fait beaucoup d'essais :

  • ajouter junit-platform-commons comme vu ici
  • j'ai suivi les guides de Mkyong Springboot + Maven + Junit5
  • j'ai exclu Junit4 de surefire, ajouter plateform-commons à surefire...

Rien n'y faisait :

  • soit pas de tests
  • soit NullPointerException sur les MockBeans
  • soit "Caused by: java.lang.ClassNotFoundException: org.junit.platform.engine.support.discovery.SelectorResolver"

Au final :

  1. Faire migrer les tests junit vers junit5
    1. Corriger les API
      import org.junit.jupiter.api.Assertions;
      import org.junit.jupiter.api.Test;
    2. Remplacer le SpringRunner
      @ExtendWith({SpringExtension.class})
  2. Simplifier le POM (vu ici : https://github.com/junit-team/junit5/issues/1773)
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<version>${test.junit.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>${test.junit.version}</version>
			<scope>test</scope>
		</dependency>
<!--
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${test.junit.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.platform</groupId>
			<artifactId>junit-platform-commons</artifactId>
			<version>${test.junit.platform}</version>
			<scope>test</scope>
		</dependency>-->

Et bien sur :

		<!-- TEST -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>junit</groupId>
					<artifactId>junit</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

Et enfin :

			<!-- Testing -->
			<plugin>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>

 

Spring Boot ne sert pas les ressources statiques

Rédigé par gorki 1 commentaire

Le problème :

Avec une stack JHipster, spring-boot n'affiche pas les ressources statiques en mode développement.

Ddonc par exemple pas de index.html...

Pour autant si on lance la commande avec le war serverless (cf spring-boot-maven-plugin), ça fonctionne.

Je suis sous Intellij, à l'évidence quelque chose manque dans le lancement de la tâche Intellij spring-boot

L'architecture est mavenisé donc le root contexte est sous : src/main/webapp

Cependant spring-boot semble chercher ses ressources ailleurs : cf la documentation

Par quelle magie spring-boot doit-il trouver ses ressources dans src/main/webapp ??

Solution :

Beaucoup de recherches, de debug et autre (il y a un peu de spring-secu dans le lot). Au final les services jhipster classiques (health, dump, etc...) fonctionnent mais toujours pas de ressources statiques.

Vérification :

  • de la tâche de lancement Intellij, elle semble correcte.
  • des classpaths (on met src/main/webapp dans le classpath, on l'enlève) 
  • des paramètres de lancement (profil Spring et autre)

Au final la mise en debug m'a permis de trouver un log perdu parmi les autres :

[DEBUG] org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory - None of the document roots [src/main/webapp, public, static] point to a directory and will be ignored.

Tiens donc le Tomcat embarqué de spring-boot a ses propres chemins de recherches... (pas tout à fait décrit dans la doc Spring ci-dessus).

Breakpoint, watch, et paf, le chemin vers src/main/webapp en absolu n'est pas le bon.

Tout ça parce que le répertoire de travail de ma tâche spring-boot sous Intellij n'était pas fixé. Comment Intellij détermine cette valeur ? Mystère. En mettant donc le répertoire de travail égal au répertoire qui contient le src/main/webapp, tout roule (ici le répertoire de travail serait : /home/user/projets/monprojet/webportal.

On retrouve le log :

[DEBUG] org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory - Document root: /home/user/projets/monprojet/webportal/src/main/webapp

Eclairé aussi par ce lien

 

Fil RSS des articles de ce mot clé