Code27 mei 2022· 4 min lezen

Java 8, JAXB, Spring 'veenlijk' upgraden

Een bijzonder oud Asterisk-hulpje in Java 1.8 met JAXB moderniseren, zodat de Cisco-telefoonmenu's blijven werken.

Display van een Cisco IP Phone SPA514G met het Setup-menu: Network, ProductInfo, Status en Reboot
Display van een Cisco IP Phone SPA514G met het Setup-menu: Network, ProductInfo, Status en Reboot

Ik probeer een bijzonder oud stukje software te moderniseren: een Asterisk hulpje gebouwd in Java 1.8 met JAXB zodat de Cisco telefoon Menus werken. gebouwd rondom 2010 met populaire technieken van toen. Zou ik eea nog naar 'het heden' kunnen upgraden?

pom.xml van de phoneservice in de IDE, met Spring 5.2.22, Jackson 1.9.13 en JDK 11 als properties

Laten we dat proberen te upgraden: JAXB is een handige manier om van XML notatie naar Java objecten om te zetten (serialiseren) en omgekeerd. Helaas is JAXB sinds Java 11 uit Het Java standaardframework gehaald, in Java 9 werd het verouderd verklaard (deprecate).

Dit alles was een eeuwigheid geleden, maar is er anno nu een vervanger om JAXB te gebruiken? Misschien een mooie nieuwere alternative implementatie buiten het standaard java framework?

https://www.jesperdj.com/2018/09/30/jaxb-on-java-9-10-11-and-beyond/
goed uitgelegd

In het bovenstaand artikel wordt goed uitgelegd hoe je toch JAXB kan gebruiken zonder dat deze in het standaard java framework zit. Gelukkig is er dus ergens op het internet iemand die bedacht heeft de verouderde JAXB functionaliteit aan te bieden met een library

https://eclipse-ee4j.github.io/jaxb-ri/

<!-- JAXB API v3.0.1 -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>3.0.1</version>
</dependency>
 
<!-- JAXB v3.0.2 reference implementation (curiously with com.sun coordinates) -->
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>3.0.2</version>
    <scope>runtime</scope>
</dependency>

Replace in Files-venster in de IDE dat javax.xml door jakarta.xml vervangt

Spring framework

Het Spring framework dat ik voor de structuur, test en deployments gebruik is van een verouderde V3 vaar v5 geupdate, dit geeft geen probleem.!Hier is alleen een update van de laatste stabiele versie nodig. Kudos voor de dames en heren Spring voor deze 10 jaar aan backwards compatibility!

Spring MVC request flow: van DispatcherServlet naar Handler Mapping, Controller, Model en View Resolver

Vervolgens is het nodig om een WAR builder aan te zetten op het project

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.3.1</version>
</plugin>

Google App Engine?

Voeg met de gcloud SDK de app engine ondersteuning toe op de mac

gcloud components install app-engine-java

Run/Debug Configurations met de Google AppEngine Dev Server 1.9.96 als application server

gebruik de volgende path:  
/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/platform/google_appengine/google/appengine/tools/java

Daarna voeg je een bestandje toe genaamd: appengine-web.xml in de WEB-INF folder

Projectboom van WEB-INF met appengine-web.xml, applicationContext.xml en de dispatcher-servlet-configuraties

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <threadsafe>true</threadsafe>
  <runtime>java17</runtime>
</appengine-web-app>

Start de app engine omgeving en krijg de volgende fout

java.lang.RuntimeException: Unable to create a DevAppServer
	at com.google.appengine.tools.development.DevAppServerFactory.doCreateDevAppServer(DevAppServerFactory.java:378)
	at com.google.appengine.tools.development.DevAppServerFactory.createDevAppServer(DevAppServerFactory.java:310)
	at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:384)
	at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:58)
	at com.google.appengine.tools.development.DevAppServerMain.run(DevAppServerMain.java:258)
	at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:249)
Caused by: java.lang.ExceptionInInitializerError
	at com.google.appengine.tools.development.DevAppServerImpl.<init>(DevAppServerImpl.java:135)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.google.appengine.tools.development.DevAppServerFactory.doCreateDevAppServer(DevAppServerFactory.java:363)
	... 5 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make static java.net.URLStreamHandler java.net.URL.getURLStreamHandler(java.lang.String) accessible: module java.base does not "opens java.net" to unnamed module @18e8568
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
	at com.google.appengine.tools.development.StreamHandlerFactory.<clinit>(StreamHandlerFactory.java:52)
	... 12 more
Disconnected from server

Process finished with exit code 1

Oorzaak: Java17, hoewel Java17 al een tijd stabiel is lijkt de combinatie van mijn (stok)oude code en App Engine dev server geen goede.

https://www.spigotmc.org/threads/inaccessibleobjectexception-unable-to-make-protected-void-java-net-urlclassloader-addurl.514803/

Bovenstaand is een oplossing voor het classloader probleem dat zich sinds java16 kan voordoen.

Als alternatief gaan we terug naar Java11, eens kijken of dat wat veranderd.

De Franzen PBX-webpagina met een leeg overzicht: Total Items 0

Change of Plans!

Omdat de App Engine omgeving me niet zoveel gaf, voornamelijk exceptions en problemen dacht ik "Het is toch een klein projectje, waarom niet van Spring naar Spring Boot overstappen?"

Het voordeel van Spring Boot is verregaande standardisatie, en de app draait nu 'zonder hulpmiddelen' dus met eigen (tomcat) server etc. Dit is ideal om het dan te laten draaien in een container en vervolgens in een GCP dienst als Cloud Run.

We gaan migreren! van full spring naar spring boot met behulp van deze guide

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.1</version>
</parent>

daarna alle spring framework referenties in de pom.xml verwijderen en vervangen voor een spring-boot web referentie

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Meerdere Servlets in 1 service.

Nu kom ik er achter dat ik waarschijnlijk iets gedaan heb wat qua design niet zo netjes is: ik heb 2 keer de dispatcher servlet in de oplossing gezet terwijl die er (schijnt) maar 1 keer in mag zitten

<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
	<display-name>Franzenonline PBX web application</display-name>
 
	<servlet>
		<servlet-name>phoneservice-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>phoneservice-dispatcher</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>
 
	<servlet>
		<servlet-name>cisco-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>cisco-dispatcher</servlet-name>
		<url-pattern>/cisco/*</url-pattern>
	</servlet-mapping>
 
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/applicationContext.xml</param-value>
	</context-param>
 
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
 
</web-app>

Omdat we de hele handel naar Spring Boot over aan het zetten zijn lijkt met dit een mooi moment om afscheid te nemen van de hele WEB-INF folder en alle config in de Application class te zetten! Nu eerst een mooie oplossing bedenken voor deze 2 gescheiden delen van de API: /cisco/* vs /rest/*

Schema van de Spring MVC-flow: van inkomend verzoek via DispatcherServlet en Handler Mapping naar Controller, Model en View Resolver

Na wat onderzoeken bleek dat de XML messaging kant voor Cisco telefoons

Uiteindelijk

Volgende stap: beveiliging inbouwen en deployen een cloudomgeving