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?
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?
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>
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!
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
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
<?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.
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.
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/*
Na wat onderzoeken bleek dat de XML messaging kant voor Cisco telefoons
Uiteindelijk
Volgende stap: beveiliging inbouwen en deployen een cloudomgeving