JPA/EclipseLink: Understanding ClassLoader issues - java

EDIT: Although a (good) answer was given and awarded, this only covers a rather unimportant part of my question. The main parts of this question are still open.
I use EclipseLink (2.6.2) in a cloud project. The project is a web application packaged as a WAR file and deployed on Apache Tomcat 8. The persistence context is set up using Java code, where I specify the entities to use using entityManagerFactoryBean.setPackagesToScan(packagesToScan). This configuration normally works as expected, where exactly the entity classes in the specified packages are found.
I now fail to understand when to use which classloader, especially when considering Tomcat, running tests, and using different connection pool implementations.
When running on Apache Tomcat including Tomcat's connection pool, the DataSource instance is created using the spring-cloud-connector plugin (spring-cloud-spring-service-connector).
In this setting everything works as expected, as long as I don't change the classloader as described below (otherwise I face ClassNotFoundExceptions for the entity classes).
When running unit tests with the help of JUnit and spring-test, the DataSource instance is created using the in-memory database H2 (using EmbeddedDatabaseBuilder from spring-jdbc). In this setting I have to specify JPA to use the classloader used for the DataSource instance (key eclipselink.classloader in the JPA properties map), otherwise I get "Object ... is not a known Entity type".
When running tests in an embedded Apache Tomcat 8, I don't see any message indicating the connection pool in use. In this setting I also have to set the classloader as for the unit tests.
If I add commons-dbcp (2.1.1) to my project and explicitly configure the spring-cloud-connector plugin to use it instead of Tomcat's connection pool, I can run the application on Tomcat without configuring the classloader, but it also works with the classloader specification described above.
For the tests the commons-dbcp does not change anything compared to the scenarios outlined above (as the corresponding configuration is not used).
Summary:
Tomcat (Tomcat CP): only using the unmodified classloader for JPA
Tomcat (DBCP): both variants
Tests: only using DataSource's classloader for JPA
Could you help me understand the differences here, and suggest a simple solution suitable for all cases? I assume that DBCP and Spring use a different classloader than Tomcat (and Tomcat's connection pool).
If you need further information, I will happily add it.
EDIT: I added an example project with a big README on how to reproduce.
https://github.com/C-Otto/classloaderexample

"I get another error when I start using mvn tomcat7:run, use Tomcat's
connection pool (CloudDatabaseConfig) and do not re-configure the
classloder (JpaConfig):"
You must configure the PostreSQL dependency on the maven plugin. You are getting ClassNot found because the PostgreSQL JAR is not in the path:
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
<!-- For any extra dependencies needed when running embedded Tomcat (not WAR dependencies) add them below -->
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1206-jdbc41</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</plugin>
Also have a look at https://tomcat.apache.org/maven-plugin-2.2/run-mojo-features.html
I forgot to include the class loader change on (JpaConfigWithDatasourceClassloader.getJPAProperties):
properties.put(CLASSLOADER, new java.net.URLClassLoader(
((java.net.URLClassLoader)classLoader).getURLs(), JpaConfigWithDatasourceClassloader.class.getClassLoader() )
) ;
With this you can run all variants using tomcat7:run

Related

Change CodeStar Spring MVC project to Spring Boot

I have a Spring Boot project that works perfectly when run in IDE. I would like to run this via AWS CodeStar. Unfortunately, the default Spring template created by CodeStar uses Spring MVC.
I cannot just overwrite the default Spring MVC project with my Spring Boot project (it doesn't work). I can copy some of my resources to the MVC project, for example index.html and that works. But then features like Thymeleaf don't work. For this and other reasons, I would like to change the provided Spring MVC into the Spring Boot structure I already have.
I followed the instructions here: https://www.baeldung.com/spring-boot-migration
Unfortunately, this doesn't help. I can create Application Entry Point and add Spring Boot dependencies without the app breaking. But when I remove the default dependencies or the configuration associated with the MVC, the app breaks. When trying to reach the URL, I get a 404 error with description:
The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
Debugging this error message (e.g. https://www.codejava.net/java-ee/servlet/solved-tomcat-error-http-status-404-not-found) didn't help.
The message seems like it's connected to the web resource. I have my web resources in folder resources as well as webapp/resources. And Spring Boot doesn't need any location configuration, right? It uses this location by default.
Can somebody tell me what things to remove and what to add to be able to use my existing Spring Boot project?
EDIT:
This is a link to a default template for AWS CodeStar Spring web application: https://github.com/JanHorcicka/AWS-codestar-template
And this is my Spring Boot project structure:
I realize that you indicated that previously you tried to use your Spring Boot project with some modifications without success, but I think it could be actually a possibility to successfully deploy your application on AWS CodeStar, and it will be my advice.
I also realized that in your screenshot you included several of the required artifacts and classes, but please, double check that you followed these steps when you deployed your application to AWS CodeStar.
Let's start with a pristine version of your Spring Boot project running locally, without any modification, and then, perform the following changes.
First, as indicated in the GitHub link you shared, be sure that you include the following files in your project. They are required for the deployment infrastructure of AWS:
appspec.yml
buildspec.yml
template.yml
template-configuration.json
The whole scripts directory
Please, adapt any necessary configuration to your specific needs, especially, template-configuration.json.
Then, perform the following modifications in your pom.xml. Some of them are required for Spring Boot to work as a traditional deployment and others are required by the deployment in AWS CodeStar.
Be sure that you indicate packaging as war:
<packaging>war</packaging>
To ensure that the embedded servlet container does not interfere with the Tomcat to which the war file is deployed, either mark the Tomcat dependency as being provided as suggested in the above-mentioned documentation:
<dependencies>
<!-- … -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- … -->
</dependencies>
Or exclude the Tomcat dependency in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
If necessary, apply this exclusion using some kind of profile that allows you to boot Spring Boot locally and in an external servlet container at the same time.
Next, parameterize the maven war plugin to conform to the AWS CodeStar deployment needs:
<build>
<pluginManagement>
<plugins>
<!-- ... -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<warName>ROOT</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!-- ... -->
<plugins>
</pluginManagement>
</build>
I do not consider it necessary, but just to avoid any kind of problem, adjust the name of your final build:
<finalName>ROOT</finalName>
Lastly, as also indicated in the Spring documentation, be sure that your MyProjectApplication - I assume this class is your main entry point subclass SpringBootServletInitializer and override the configure accordingly, something like:
#SpringBootApplication
public class MyProjectApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyProjectApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyProjectApplication.class, args);
}
}
Please, feel free to adapt the class to your specific use case.
With this setup, try to deploy your application and see if it works: perhaps you can find some kind of library dependencies problem, but I think for the most part it should work fine.
At a first step, you can try to deploy locally the version of the application you will later deploy to AWS CodeStar following the instructions you provided in your project template, basically, once configured with the necessary changes described in the answer, by running:
mvn clean package
And deploying the generated war on your local tomcat environment. Please, be aware that probably the ROOT application already exists in a standard tomcat installation (you can verify it by inspecting the webapps folder): you can override that war file.
For local testing you can even choose a different application name (configuring build.finalName and the warName in your pom.xml file): the important thing is verify if locally the application runs successfully.
If you prefer to, you can choose to deploy the app directly to AWS CodeStar and inspect the logs later it necessary.
In any case, please, pay attention on two things: on one hand, if you have any absolute path configured in your application, it can be the cause of the 404 issue you mention in the comments. Be aware that your application will be deployed in Tomcat with context root '/'.
On the other hand, review how you configured your database access.
Probably you used application.properties and it is fine, but please, be aware that when employing the application the database must be reachable: perhaps Spring is unable to create the necessary datasources, and the persistence manager or related stuff associated with and, as a consequence, the application is not starting. Again, it may be the reason of the 404 error code.
To simplify database connectivity, for testing, at first glance, I recommend you to use simple properties for configuring your datasource, namely the driver class, connection string, username and password. If that setup works properly, you can later enable JNDI or what deemed necessary.
Remember that if you need to change your context name and/or define a datasource pool in Tomcat you can place a context.xml file under a META-INF directory in your web app root path.
This context.xml should look like something similar to:
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/">
<Resource name="jdbc/myDS"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
url="jdbc:mysql://localhost:3306/myds"
driverClassName="com.mysql.jdbc.Driver"
username="root"
password="secret"
/>
</Context>

Spring boot environment specific properties for dependencies

I am curious what is the best practice for environment (or even server) specific properties for dependencies.
This is not for properties that are managed by the applicationContext, as I am aware of the {env}-application.properties convention that Spring supports.
I will give a small example to elaborate:
My project, is project A. In A, we depend on Project B
A's pom.xml
<dependency>
<groupId>com.B</groupId>
<artifactId>B</artifactId>
</dependency>
Artifact B has a dependency on B.properties, which it does not provide and it must be on the classpath. I am unable to refactor Artifact B.
Contents of B.properties are:
b.someproperty=${property.placeholder}/b/dir
Contents of application.properties:
property.placeholder=default
Contents of dev-application.properties:
property.placeholder=dev
So when I run my spring boot app with -Dspring.profiles.active=dev, b.someproperty must resolve to dev/b/dir
What I have tried so far (that worked):
1) Externalized properties that sit on the server, with the placeholders already resolve and simply adding them to the classpath at runtime (So each server would have it's own B.properties file with no placeholders, and this would not be part of the build/deploy process)
2) Class within the spring bootable jar which takes the properties in b.properties, resolves all the placeholders, and writes it out to a file on the server and then adds this file to the classpath. So running the app with -Dspring.profiles.active=dev would generate b.properties on the server with no placeholders, and everything is contained within the jar.
Neither of these solutions are very clean, or good (imo). Is it possible to resolve the placeholders as the properties are being used, even if they are not being managed by the application context?
Any insight or criticism of my current solutions appreciated.

Spring Boot application + Jolokia - exception during startup

I'm using Spring Boot 1.5.3.RELEASE and Jolokia 1.3.6 (also happens in later versions).
The Jolokia is integrated by adding a dependency:
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
One of our microservices that all share the same architecture fails to start and I see the exception with the following root-cause during the startup:
Caused by: java.io.FileNotFoundException: JAR entry BOOT-INF/lib/jolokia-core-1.3.7.jar!/META-INF/simplifiers-default not found in <MY_JAR_NAME_GOES_HERE>.jar
at sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:142)
at sun.net.www.protocol.jar.JarURLConnection.getInputStream (JarURLConnection.java:150)
at java.net.URL.openStream(URL.java:1045)
at org.jolokia.util.ServiceObjectFactory.readServiceDefinitionFromUrl(ServiceObjectFactory.java:90)
This exception doesn't happen when I start the application from the IDE, only when I start with java -jar <MY_JAR>.
I looked at the line that produces exception inside the code of Jolokia, and it looks like this:
reader = new LineNumberReader(new InputStreamReader(new URL(pUrl).openStream(),"UTF8"));
So I conclude (after debugging) that new URL(pUrl).openStream() fails to find a jar entry as specified in the aforementioned exception stack trace. I also understand that in IDE it doesn't happen because it works with different classloaders (Spring Boot application uses LaunchedURLClassLoader).
However, I don't see a bug here in the source code: we have a lot of microservices, all are running with the same configurations and it works as expected, in addition, as far as I know this is the documented way for Jolokia integration.
So I suspect some race condition here or something, but I can't really point out exactly what happens here.
Did anyone encounter such a problem? Is there a workaround?
I was getting exactly the same exception. The problem in my case was that the filename had a + (I'm using reckon Gradle plugin to generate the project version). The solution was to rename the file before running it with java -jar.
I'm facing the same problem, with Spring Boot 1.5.22 and default version of jolokia.
I have another app (same version of SpringBoot, jolokia) that did not have the problem... I did not find any differences between the 2 apps...
But I have use that workaround : instruct Spring Boot to extract jolokia jar in order to skip Spring boot nested jar url process for jolokia jar only.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<requiresUnpack>
<dependency>
<artifactId>jolokia-core</artifactId>
<groupId>org.jolokia</groupId>
</dependency>
</requiresUnpack>
</configuration>
</plugin>
see https://docs.spring.io/spring-boot/docs/1.5.x/reference/htmlsingle/#howto-extract-specific-libraries-when-an-executable-jar-runs
With this workaround, jolokia is happy, the /jolokia endpoint is available and Spring Boot Admin jmx tab is active.

Issues with WAS Liberty classloader

I have an issue with the WAS Liberty classloader, nothing I do seem to fix it. The issue seems to be with log4j2, which I am using.
I'm running 16.0.0.4 (just upgraded from 8.5.5.9 where this issue also exist). I'm trying to create a webapp using Primefaces 6.0 which connects to Elasticsearch 5.1.1.
I have added the following dependency to maven:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${library.elasticsearch.version}</version>
</dependency>
Somewhere along the road I need to do the following:
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(HOST), PORT));
Debugging this line hits the following method call in Elasticsearch (class: org.elasticsearch.threadpool.ThreadPool line 203):
logger.debug("created thread pool: {}", entry.getValue().formatInfo(executorHolder.info));
This throws an java.lang.NoSuchMethodError:
org/apache/logging/log4j/Logger.debug(java/lang/String;java/lang/Object;)
Normally we use log4j 2.2, but I have included log4j2 2.7 as described here (Elasticsearch v5.0 uses 2.6.2, v5.1 uses 2.7): https://discuss.elastic.co/t/issue-with-elastic-search-5-0-0-noclassdeffounderror-org-apache-logging-log4j-logger/64262/2
I have also tried to make it "provided". I'm currently building a war file, but I also tried to do it as an ear, same result.
I came accoss this issue on the Elasticsearch 5-alpha: https://github.com/elastic/elasticsearch/issues/19415 Here they note that they wanted to create a server and therefore did not see log4j2 as a consern, but they suggest that you use the REST API instead (although, at the time of writing, this was not usable for Java developers).
So the question is, what do I do? Should I use the REST API (e.g. Jest (https://github.com/searchbox-io/Jest/tree/master/jest)) or ?...
The code I have works fine when running standalone outside Liberty.
UPDATE:
It seems like parts of Liberty does contain log4j v2.2:
class load: org.apache.logging.log4j.core.appender.routing.Route from: file:/C:/deploy/liberty/workarea/org.eclipse.osgi/60/data/cache/com.ibm.ws.app.manager_0/.cache/lib/log4j-core-2.2.jar
...
It loads a lot of classes from this jar, but not the one that I'm having trouble with - this is loaded from an app we have. I tried to bump the version inside our own app, but same issue.

Set the JAXB context factory initialization class to be used

I have updated our projects (Java EE based running on Websphere 8.5) to use a new release of a company internal framework (and Ejb 3.x deployment descriptors rather than the 2.x ones). Since then my integration Tests fail with the following exception:
[java.lang.ClassNotFoundException: com.ibm.xml.xlxp2.jaxb.JAXBContextFactory]
I can build the application with the previous framework release and everything works fine.
While debugging i noticed that within the ContextFinder (javax.xml.bind) there are two different behaviours:
Previous Version (Everything works just fine): None of the different places brings up a factory class so the default factory class gets loaded which is com.sun.xml.internal.bind.v2.ContextFactory (defined as String constant within the class).
Upgraded Version (ClassNotFound): There is a resource "META-INF/services/javax.xml.bind.JAXBContext" beeing loaded successfully and the first line read makes the ContextFinder attempt to load "com.ibm.xml.xlxp2.jaxb.JAXBContextFactory" which causes the error.
I now have two questions:
What sort is that resource? Because inside our EAR there is two WARs and none of those two contains a folder services in its
META-INF directory.
Where could that value be from otherwise? Because a filediff showed me no new or changed properties files.
No need to say i am going to read all about the JAXB configuration possibilities but if you have first insights on what could have gone wrong or help me out with that resource (is it a real file i have to look for?) id appreciate a lot. Many Thanks!
EDIT (according to comments Input/Questions):
Out of curiosity, does your framework include JAXB JARs? Did the old version of your framework include jaxb.properties?
Indeed (i am a bit surprised) the framework has a customized eclipselink-2.4.1-.jar inside the EAR that includes both a JAXB implementation and a jaxb.properties file that shows the following entry in both versions (the one that finds the factory as well as in the one that throws the exception):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
I think this is has nothing to do with the current issue since the jar stayed exactly the same in both EARs (the one that runs/ the one with the expection)
It's also not clear to me why the old version of the framework was ever selecting the com.sun implementation
There is a class javax.xml.bind.ContextFinder which is responsible for initializing the JAXBContextFactory. This class searches various placess for the existance of a jaxb.properties file or a "javax.xml.bind.JAXBContext" resource. If ALL of those places dont show up which Context Factory to use there is a deault factory loaded which is hardcoded in the class itself:
private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
Now back to my problem:
Building with the previous version of the framework (and EJB 2.x deployment descriptors) everything works fine). While debugging i can see that there is no configuration found and thatfore above mentioned default factory is loaded.
Building with the new version of the framework (and EJB 3.x deployment descriptors so i can deploy) ONLY A TESTCASE fails but the rest of the functionality works (like i can send requests to our webservice and they dont trigger any errors). While debugging i can see that there is a configuration found. This resource is named "META-INF/services/javax.xml.bind.JAXBContext". Here are the most important lines of how this resource leads to the attempt to load 'com.ibm.xml.xlxp2.jaxb.JAXBContextFactory' which then throws the ClassNotFoundException. This is simplified source of the mentioned javax.xml.bind.ContextFinder class:
URL resourceURL = ClassLoader.getSystemResource("META-INF/services/javax.xml.bind.JAXBContext");
BufferedReader r = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "UTF-8"));
String factoryClassName = r.readLine().trim();
The field factoryClassName now has the value 'com.ibm.xml.xlxp2.jaxb.JAXBContextFactory'
Because this has become a super lager question i will also add a bounty :)
I will work the entire day on this and let you know if there is any news.
Update/ Solution
This question has been solved. The original problem has occured because misconfiguration of complexly build multi model maven projects which one dependency used a updated version of a customized eclipse link jar that contained a definition for a JAXBFactory not available in the component where the error occured. Setting the JAXB context factory in most cases is configured with a jaxb.propertie file or JAXBContext file that contains the same definition. Detailed loading process of the appropriate JAXBContextFactory happens in javax.xml.bind.ContextFinder.
The error has not yet been solved (during the fact over 4 major EE/SE Applications lead to the error) and there is no general answer but that defined JAXBContextFactorys must exist in your classpath (wow what a wonder...) so you either have a that ClassNotFound Error because youre missing resources (well thats the acctual cause) or because you have a wrong JAXBContextFactory defined in any of the above mentioned propertie files which contain a definition according to the below answer.
Very many thanks for your great comments and support, i realy appreciate!
You can include a jaxb.properties file in the same package as your domain model to specify the JAXB (JSR-222) implementation you wish to use. For example it would look like the following to specify EclipseLink MOXy as your JAXB provider.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Another quick and dirty solution (a workaround, really) that worked for me is to explicitly include a JAXB implementation to the maven build. For example
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.7</version>
</dependency>
Note that this adds a somehow unnecessary dependency to your build, as JAXB obviously already is part of each JRE >= version 6.
Most likely this will only work when the WAS classloader is set to parent last.

Categories