gwt-test-utils unit fail when run with jacoco - sonarqube

We are attempting to generate coverage reports for our GWT application from a set of unit tests written using gwt-test-utils. The project is a multi-module maven project. We are using the sonar-plugin on jenkins in order to generate and collate our coverage and violation information.
When the build jobs run all the GWT unit tests pass as part of the normal build, however when the Sonar plugin attempts to rerun the tests they all fail with the following error:
initializationError(uk.co.card.gwt.retailpost.client.dialog.productmodify.CurrencyEditDialogTest) Time elapsed: 0 sec <<< ERROR!
com.googlecode.gwt.test.exceptions.GwtTestException: Error while generating gwt-test-utils prerequisites
at com.googlecode.gwt.test.internal.GwtFactory.(GwtFactory.java:113)
at com.googlecode.gwt.test.internal.GwtFactory.initializeIfNeeded(GwtFactory.java:45)
at com.googlecode.gwt.test.internal.junit.AbstractGwtRunner.(AbstractGwtRunner.java:30)
at com.googlecode.gwt.test.GwtRunner.(GwtRunner.java:19)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:250)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
at com.google.gwt.dev.cfg.ModuleDef.checkForSeedTypes(ModuleDef.java:559)
at com.google.gwt.dev.cfg.ModuleDef.getCompilationState(ModuleDef.java:363)
at com.google.gwt.dev.cfg.ModuleDef.getCompilationState(ModuleDef.java:354)
at com.googlecode.gwt.test.internal.GwtFactory.createCompilationState(GwtFactory.java:151)
at com.googlecode.gwt.test.internal.GwtFactory.(GwtFactory.java:106)
... 25 more
Looking through the rest of the console output from jenkins, and the workspace directories, I can't locate the log file that the "com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)" refers to.
Has anyone encountered a similar problem and knows how to get Sonar to run the gwt-test-utils successfully, or at least will have an idea when to look to find the previous log entries mentioned in the exception.
EDIT: After further experimentation the issue appears to be being caused by jacoco. trying to run just the unit tests instrumented with jacoco (and with no sonar involved) results in the same error
**EDIT:
sample from pom.xml
<build>
pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.6.2.201302030002</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<excludedGroups combine.self="override" />
<reuseForks>true</reuseForks>
<argLine>-Xmx1024m -XX:MaxPermSize=256m ${jacoco.agent.argLine}</argLine>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<propertyName>jacoco.agent.argLine</propertyName>
<destFile>${sonar.jacoco.itReportPath}</destFile>
<append>true</append>
<excludes>
<exclude>*.osgi.*</exclude>
<exclude>*.apache.*</exclude>
<exclude>*.sourceforge.*</exclude>
<exclude>*.junit.*</exclude>
<!-- Test support code does not need to be covered -->
<exclude>uk.co.card.retailpost.clientintegration.utilities.*</exclude>
</excludes>
<classDumpDir>temp/classes</classDumpDir>
</configuration>
<executions>
<execution>
<id>agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

As I mentioned in comments libraries are loaded in different order for jacoco with maven-surefire-plugin. To solve this problem write your own runner (extends com.googlecode.gwt.test.GwtRunner) and change classloader for thread contextClassLoader.
import com.googlecode.gwt.test.GwtRunner;
public class MyGwtRunner extends GwtRunner {
static {
URLClassLoader classLoader = (URLClassLoader) MyGwtRunner.class.getClassLoader();
try {
URL[] urls = getClassPath();
ClassLoader cl = URLClassLoader.newInstance(urls, classLoader);
Thread.currentThread().setContextClassLoader(cl);
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
public MyGwtRunner(Class<?> clazz) throws Throwable {
super(clazz);
}
private static URL[] getClassPath() throws MalformedURLException {
String classPath = System.getProperty("java.class.path");
String pathSeparator = System.getProperty("path.separator");
String[] array = classPath.split(pathSeparator);
List<URL> files = new ArrayList<URL>();
for (String a : array) {
files.add(new File(a).toURI().toURL());
}
return files.toArray(new URL[files.size()]);
}
}
In your tests override GwtRunner by MyGwtRunner
#GwtModule("com.my.module.GwtTestUtils")
#RunWith(MyGwtRunner.class)
public abstract class AbstractGwtJunit extends GwtTest {
....
}

Related

OpenAPI Codegen "oneOf": Java classes not generated correctly

TLDR:
OpenAPI offers 'oneOf' property. The resulting java classes created do not seem to allow one of the possible instances.
Details:
I am creating the spring/java server side rest api code using OpenAPI maven plugin.
The request class is not such that the passed object is not parsed correctly. The following error is printed in the console.
JSON parse error: Could not resolve subtype of [simple type, class com.model.Issuer]: missing type id property 'type' (for POJO property 'issuer'); nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class com.Issuer]: missing type id property 'type' (for POJO property 'issuer')\n at [Source: (PushbackInputStream); line: 3, column: 15] (through reference chain: com.IssueCredentialRequest[\"credential\"]->com.Credential[\"issuer\"])
The reason seem to be that the generated class does not have subtypes list in the annotation:
package com.sphereon.vdp.vc.service.model;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
})
public interface OneOfIssuer {
}
There is another class which is generated correctly. The reason that this one is generated correctly is probably that this one deals with non-primitive types.
package com.sphereon.vdp.vc.service.model;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = VerifyPresentationRequest.class, name = "VerifyPresentationRequest"),
#JsonSubTypes.Type(value = ProoflessVerifyPresentationRequest.class, name = "ProoflessVerifyPresentationRequest")
})
public interface OneOfpresentationsVerifyBody {
}
Can someone point to how to fix the code generation for primitive types?
<plugin>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.33</version>
<dependencies>
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>4.3.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>vc-rest-api-issuer-source-generation</id>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<inputSpec>${pom.basedir}/specifications/issuer.yml</inputSpec>
<language>spring</language>
<apiPackage>com.company.vdp.vc.service.api</apiPackage>
<modelPackage>com.company.vdp.vc.service.model</modelPackage>
<artifactVersion>${project.version}</artifactVersion>
<generateModels>true</generateModels>
<generateApis>true</generateApis>
<generateModelDocumentation>true</generateModelDocumentation>
<generateSupportingFiles>true</generateSupportingFiles>
<verbose>${openapi-codegen-verbose}</verbose>
<output>${project.basedir}/target/generated-sources/java/api</output>
<ignoreFileOverride>${project.basedir}/target/generated-sources/java/api/.swagger-codegen-ignore</ignoreFileOverride>
<configOptions>
<delegatePattern>true</delegatePattern>
<dateLibrary>java8</dateLibrary>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
<execution>
<id>vc-rest-api-verifier-source-generation</id>
<goals>
<goal>generate</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<inputSpec>${pom.basedir}/specifications/verifier.yml</inputSpec>
<language>spring</language>
<apiPackage>com.company.vdp.vc.service.api</apiPackage>
<modelPackage>com.company.vdp.vc.service.model</modelPackage>
<artifactVersion>${project.version}</artifactVersion>
<generateModels>true</generateModels>
<generateApis>true</generateApis>
<generateModelDocumentation>true</generateModelDocumentation>
<generateSupportingFiles>true</generateSupportingFiles>
<verbose>${openapi-codegen-verbose}</verbose>
<output>${project.basedir}/target/generated-sources/java/api</output>
<ignoreFileOverride>${project.basedir}/target/generated-sources/java/api/.swagger-codegen-ignore</ignoreFileOverride>
<configOptions>
<delegatePattern>true</delegatePattern>
<dateLibrary>java8</dateLibrary>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Following is the spec file that I am using without editing.
https://github.com/w3c-ccg/vc-api/blob/main/components/Issuer.yml
https://github.com/w3c-ccg/vc-api/blob/main/verifier.yml

tests fat jar with maven shade-plugin

I need to build one jar for execution tests during CI process on server. This jar have to contains all test dependencies (like junit, mockito) and spring boot dependencies while my test will use spring. I wrote such maven configuration:
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<id>shade</id>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${project.artifactId}-fat</finalName>
<minimizeJar>false</minimizeJar>
<shadeTestJar>false</shadeTestJar>
<artifactSet>
<includes>
<include>*:*</include>
</includes>
</artifactSet>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>pl.klolo.demo.demo.smoke.SmokeTestAppKt</mainClass>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
Maven built one jar with all dependencies, but when I try to execute test I have strange error which I cannot understand:
2022-04-08 20:45:44.611 INFO 591618 --- [ main] p.k.d.demo.tests.DemoApplicationTests : No active profile set, falling back to 1 default profile: "default"
2022-04-08 20:45:45.018 ERROR 591618 --- [ main] o.s.boot.SpringApplication : Application run failed
java.lang.IllegalStateException: Unable to read meta-data for class
at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:237) ~[demo-fat.jar:0.0.1]
at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getOrder(AutoConfigurationSorter.java:208) ~[demo-fat.jar:0.0.1]
at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.access$000(AutoConfigurationSorter.java:154) ~[demo-fat.jar:0.0.1]
at org.springframework.boot.autoconfigure.AutoConfigurationSorter.lambda$getInPriorityOrder$0(AutoConfigurationSorter.java:64) ~[demo-fat.jar:0.0.1]
at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na]
at java.base/java.util.TimSort.sort(TimSort.java:220) ~[na:na]
at java.base/java.util.Arrays.sort(Arrays.java:1515) ~[na:na]
at java.base/java.util.ArrayList.sort(ArrayList.java:1750) ~[na:na]
at org.springframework.boot.autoconfigure.AutoConfigurationSorter.getInPriorityOrder(AutoConfigurationSorter.java:62) ~[demo-fat.jar:0.0.1]
Could you tell me what is wrong and how to fix it?
SmokeTestApp:
fun main(args: Array<String>) {
val launcher = LauncherFactory.create()
val listener = SummaryGeneratingListener()
launcher.registerTestExecutionListeners(listener)
launcher.execute(
LauncherDiscoveryRequestBuilder.request()
.selectors(selectPackage("pl.klolo"))
.build()
)
printSummary(listener.summary)
exitProcess(
if (listener.summary.failures.isEmpty()) 0
else -1
)
}
fun printSummary(summary: TestExecutionSummary) {
println("Passed tests: ${summary.testsSucceededCount}")
summary.failures.forEach {
println("###FAILED TEST ${it.testIdentifier.uniqueId}###")
it.exception.printStackTrace()
}
}
DemoApplicationTests:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class DemoApplicationTests(
#Value("\${test.applicationURI}") val applicationURI: String,
#Value("\${test.applicationPort}") val applicationPort: Int
) {
init {
RestAssured.baseURI = applicationURI
RestAssured.port = applicationPort
}
#Tag("smoke_test")
#Test
fun contextLoads() {
println("smoke test ===> 1")
}
I found reason of my problem. It was file spring.factories with entry:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
When I removed file jar works correctly

Setting descriptions for mojos and parameters in maven plugin

I am writing a maven plugin and I would like to add some documentation for the available goals and parameters.
When I run mvn help:describe -Dplugin=myplugin -Ddetail it prints out available goals and parameters. However it lists (no description) everywhere. From searching the internet I could not figure out where such a description is to be set.
For reference, my plugin is written in scala and looks roughly like this.
import org.apache.maven.plugins.annotations.{ Component, Parameter }
class MyMojo extends AbstractMojo {
#Parameter(defaultValue = "false", readonly = false)
private var skipFormatting: Boolean = _
}
So my question would be: where can set a description, such that it will show up with mvn help:describe -Dplugin=myplugin?
I strongly recommend to use the following:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>default-descriptor</id>
<phase>process-classes</phase>
</execution>
<execution>
<id>generate-helpmojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
which will generate the help part during the build process. And yes you have to add some javadoc like this:
#Mojo(name = "failure", defaultPhase = LifecyclePhase.NONE,
requiresDependencyResolution = ResolutionScope.NONE, threadSafe = true)
public class FailureMojo extends AbstractMojo {
I don't understand why you haven't have any annotations on your Mojo?
The documentation like this: https://maven.apache.org/plugins/maven-install-plugin/plugin-info.html will be generated from the javadoc on parameters etc. https://github.com/apache/maven-install-plugin/blob/master/src/main/java/org/apache/maven/plugins/install/InstallMojo.java#L69
Based on examples when running the help:describe on familiar plugins like the maven-jar-plugin, tt appears it's based on the Javadoc of the Mojo class.

How to precompile jsp in a spring boot application?

I'm using Spring boot and we were using Spring with Tomcat before that.
When we used Spring and Tomcat two years ago, we used a maven plugin to precompile the jsp.
It was really useful to avoid this compilation to be made for every first visits after a deployement.
However all maven plugin that we know dumps a web.xml file that list all jsp and associated generated servlets.
With Spring boot, it don't use web.xml anymore, so this file is ignored.
We still have the compilation and that's a security belt but there is a penalty for every first visit on each page.
Does anybody know if it's possible to precompile jsp in a Spring boot application ?
I got precompiling to work either at server start time (don't have to use JspC, so simpler build file) and at build time (much quicker server start time). I register the resulting servlets dynamically, so you don't have to manually change any files if you add/remove JSPs.
At server start time
Use ServletRegistration.Dynamic to register a JSP_SERVLET_CLASS Servlet for each JSP.
Use the initParameter jspFile to set the JSP filename (ref)
e.g. for SpringBoot in a ServletContextInitializer (ref):
#Bean
public ServletContextInitializer preCompileJspsAtStartup() {
return servletContext -> {
getDeepResourcePaths(servletContext, "/WEB-INF/jsp/").forEach(jspPath -> {
log.info("Registering JSP: {}", jspPath);
ServletRegistration.Dynamic reg = servletContext.addServlet(jspPath, Constants.JSP_SERVLET_CLASS);
reg.setInitParameter("jspFile", jspPath);
reg.setLoadOnStartup(99);
reg.addMapping(jspPath);
});
};
}
private static Stream<String> getDeepResourcePaths(ServletContext servletContext, String path) {
return (path.endsWith("/")) ? servletContext.getResourcePaths(path).stream().flatMap(p -> getDeepResourcePaths(servletContext, p))
: Stream.of(path);
}
At build time
Generate Java source files for each JSP and a web.xml with their servlet mappings using JspC (ref).
Then register these with the ServletContext (by parsing the web.xml with Tomcat's WebXmlParser, e.g. for SpringBoot:
#Value("classpath:precompiled-jsp-web.xml")
private Resource precompiledJspWebXml;
#Bean
public ServletContextInitializer registerPreCompiledJsps() {
return servletContext -> {
// Use Tomcat's web.xml parser (assume complete XML file and validate).
WebXmlParser parser = new WebXmlParser(false, true, true);
try (InputStream is = precompiledJspWebXml.getInputStream()) {
WebXml webXml = new WebXml();
boolean success = parser.parseWebXml(new InputSource(is), webXml, false);
if (!success) {
throw new RuntimeException("Error parsing Web XML " + precompiledJspWebXml);
}
for (ServletDef def : webXml.getServlets().values()) {
log.info("Registering precompiled JSP: {} = {} -> {}", def.getServletName(), def.getServletClass());
ServletRegistration.Dynamic reg = servletContext.addServlet(def.getServletName(), def.getServletClass());
reg.setLoadOnStartup(99);
}
for (Map.Entry<String, String> mapping : webXml.getServletMappings().entrySet()) {
log.info("Mapping servlet: {} -> {}", mapping.getValue(), mapping.getKey());
servletContext.getServletRegistration(mapping.getValue()).addMapping(mapping.getKey());
}
} catch (IOException e) {
throw new RuntimeException("Error registering precompiled JSPs", e);
}
};
}
Example Maven config to generate and compile the JSP classes, and generate the precompiled-jsp-web.xml:
<!-- Needed to get the jasper Ant task to work (putting it in the plugin's dependencies didn't work) -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina-ant</artifactId>
<version>8.0.32</version>
<scope>provided</scope>
</dependency>
<!-- ... -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>precompile-jsp-generate-java</id>
<!-- Can't be generate-sources because we need the compiled Henry taglib classes already! -->
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo message="Precompiling JSPs"/>
<property name="compile_classpath" refid="maven.compile.classpath"/>
<property name="target_dir" value="${project.basedir}/generated-sources/jspc" />
<path id="jspc_classpath">
<path path="${compile_classpath}"/>
</path>
<typedef resource="org/apache/catalina/ant/catalina.tasks" classpathref="jspc_classpath"/>
<mkdir dir="${target_dir}/java"/>
<mkdir dir="${target_dir}/resources"/>
<jasper
validateXml="false"
uriroot="${project.basedir}/src/main/webapp"
compilertargetvm="1.8"
compilersourcevm="1.8"
failonerror="true"
javaencoding="UTF-8"
webXml="${target_dir}/resources/precompiled-jsp-web.xml"
outputDir="${target_dir}/java/" >
</jasper>
<!-- Can't use Maven to compile the JSP classes because it has already compiled the app's classes
(needed to do that becuase JspC needs compiled app classes) -->
<javac srcdir="${target_dir}/java" destdir="${project.build.outputDirectory}" classpathref="jspc_classpath" fork="true"/>
<!-- Have to copy the web.xml because process-resources phase has already finished (before compile) -->
<copy todir="${project.build.outputDirectory}">
<fileset dir="${target_dir}/resources"/>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<!-- Not strictly necessary, because Ant does the compilation, but at least attempts to keep it in sync with Maven -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-precompiled-jsp-java-sources</id>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources>
<source>${project.basedir}/generated-sources/jspc/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-precompiled-jsp-resources</id>
<phase>generate-resources</phase>
<goals><goal>add-resource</goal></goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/generated-sources/jspc/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Based on the excellent answer of paulcm I came up with my own solution as the above solution didn't work for me and I couldn't track down the error. Maybe the answer above is outdated for tomcat9. Or it had some problem with multi-module setup. However: All credits belong to paulcm
This is only the compile time solution.
Add these two plugins to your pom.xml
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
<version>9.4.15.v20190215</version>
<executions>
<execution>
<id>jspc</id>
<goals>
<goal>jspc</goal>
</goals>
<configuration>
<mergeFragment>true</mergeFragment>
<sourceVersion>1.8</sourceVersion>
<targetVersion>1.8</targetVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webXml>${project.basedir}/target/web.xml</webXml>
</configuration>
</plugin>
Add an empty web.xml file
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<session-config>
<cookie-config>
</cookie-config>
</session-config>
</web-app>
Add a Registry
import org.apache.tomcat.util.descriptor.web.ServletDef;
import org.apache.tomcat.util.descriptor.web.WebXml;
import org.apache.tomcat.util.descriptor.web.WebXmlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.xml.sax.InputSource;
import javax.servlet.ServletRegistration;
import java.io.InputStream;
import java.util.Map;
#Configuration
public class PreCompileJspRegistry {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Bean
public ServletContextInitializer registerPreCompiledJsps() {
return servletContext -> {
InputStream inputStream = servletContext.getResourceAsStream("/WEB-INF/web.xml");
if (inputStream == null) {
logger.info("Could not read web.xml");
return;
}
try {
WebXmlParser parser = new WebXmlParser(false, false, true);
WebXml webXml = new WebXml();
boolean success = parser.parseWebXml(new InputSource(inputStream), webXml, false);
if (!success) {
logger.error("Error registering precompiled JSPs");
}
for (ServletDef def : webXml.getServlets().values()) {
logger.info("Registering precompiled JSP: {} = {} -> {}", def.getServletName(), def.getServletClass());
ServletRegistration.Dynamic reg = servletContext.addServlet(def.getServletName(), def.getServletClass());
reg.setLoadOnStartup(99);
}
for (Map.Entry<String, String> mapping : webXml.getServletMappings().entrySet()) {
logger.info("Mapping servlet: {} -> {}", mapping.getValue(), mapping.getKey());
servletContext.getServletRegistration(mapping.getValue()).addMapping(mapping.getKey());
}
} catch (Exception e) {
logger.error("Error registering precompiled JSPs", e);
}
};
}
}
A comment for "At server start time" outlined above: the servlet you create will by default be in development mode if the application is packaged in an executable jar, so you if you use it in production mode, you should also set development = false ++ to prevent the jsps from being compiled again:
reg.setInitParameter("genStringAsCharArray", "true");
reg.setInitParameter("trimSpaces", "true");
reg.setInitParameter("development", "false");

Generate DDL script at MAVEN build with Hibernate4 / JPA 2.1

It seems like the hibernate3-maven-plugin used to generate DDL create/drop scripts is not compatible any more with Hibernate 4.3 and newer versions (using JPA 2.1).
I use this plugin configuration :
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId>
<version>3.0</version>
<executions>
<execution>
<id>generate-sql-schema</id>
<phase>process-sources</phase>
<goals>
<goal>hbm2ddl</goal>
</goals>
<configuration>
<hibernatetool>
<jpaconfiguration persistenceunit="${persistenceUnitName}" />
<hbm2ddl update="true" create="true" export="false"
outputfilename="src/main/sql/schema.sql" format="true"
console="true" />
</hibernatetool>
</configuration>
</execution>
</executions>
</plugin>
But I get the following error :
[ERROR] Failed to execute goal org.codehaus.mojo:hibernate3-maven-plugin:3.0:hbm2ddl (generate-sql-schema) on project my-project: There was an error creating the AntRun task.
An Ant BuildException has occured: java.lang.NoClassDefFoundError: org/hibernate/util/ReflectHelper: org.hibernate.util.ReflectHelper -> [Help 1]
This class as migrated to a new package : org.hibernate.internal.util.ReflectHelper
However i found no clear way to keep generating DDL create scripts at MAVEN build.
There is no hibernate4-maven-plugin, or any other official way to do it.
So what ? Isn't it a main feature that should be supported ? How to do it ?
As Hibernate 4.3+ now implements JPA 2.1 the appropriate way to generate DDL scripts is to use following set of JPA 2.1 properties :
<property name="javax.persistence.schema-generation.scripts.action" value="create"/>
<property name="javax.persistence.schema-generation.create-source" value="metadata"/>
<property name="javax.persistence.schema-generation.scripts.create-target" value="target/jpa/sql/create-schema.sql"/>
A nice summary of others properties and context of schema generation in JPA 2.1 can be found here :
https://blogs.oracle.com/arungupta/entry/jpa_2_1_schema_generation
And official JPA 2.1 specifications here :
https://jcp.org/aboutJava/communityprocess/final/jsr338/index.html
As this will be generated at runtime, you may want to execute this DDL generation at build.
Here is the JPA 2.1 approach to generate this script programmatically :
import java.io.IOException;
import java.util.Properties;
import javax.persistence.Persistence;
import org.hibernate.jpa.AvailableSettings;
public class JpaSchemaExport {
public static void main(String[] args) throws IOException {
execute(args[0], args[1]);
System.exit(0);
}
public static void execute(String persistenceUnitName, String destination) {
System.out.println("Generating DDL create script to : " + destination);
final Properties persistenceProperties = new Properties();
// XXX force persistence properties : remove database target
persistenceProperties.setProperty(org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "");
persistenceProperties.setProperty(AvailableSettings.SCHEMA_GEN_DATABASE_ACTION, "none");
// XXX force persistence properties : define create script target from metadata to destination
// persistenceProperties.setProperty(AvailableSettings.SCHEMA_GEN_CREATE_SCHEMAS, "true");
persistenceProperties.setProperty(AvailableSettings.SCHEMA_GEN_SCRIPTS_ACTION, "create");
persistenceProperties.setProperty(AvailableSettings.SCHEMA_GEN_CREATE_SOURCE, "metadata");
persistenceProperties.setProperty(AvailableSettings.SCHEMA_GEN_SCRIPTS_CREATE_TARGET, destination);
Persistence.generateSchema(persistenceUnitName, persistenceProperties);
}
}
As you can see it's very simple !
Now you can use this in an AntTask, or MAVEN build like this (for MAVEN) :
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>generate-ddl-create</id>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- ANT Task definition -->
<java classname="com.orange.tools.jpa.JpaSchemaExport"
fork="true" failonerror="true">
<arg value="${persistenceUnitName}" />
<arg value="target/jpa/sql/schema-create.sql" />
<!-- reference to the passed-in classpath reference -->
<classpath refid="maven.compile.classpath" />
</java>
</target>
</configuration>
</execution>
</executions>
</plugin>
Note that the official hibernate-maven-plugin also may, or may not, do the trick in some way :
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-maven-plugin</artifactId>
<version>4.3.1.Final</version>
</dependency>
Enjoy ! :)

Resources