BaseX close Spring Boot classloader - java

I have a Spring Boot (2.7.5) with BaseX 10.3
On start up I have a server component starting the server in a #PostConstruct
System.setProperty("org.basex.path", basexDataPath);
Context c = new Context();
c.soptions.set(StaticOptions.DBPATH, basexDataPath);
this.server = new BaseXServer(c,sl.finish());
And a client component creating a session in also a #PostConstruct
ClientSession clientSession = new ClientSession(basexServerHost, basexServerPort, UserText.ADMIN, basexAdminPassword);
And then I load functx (still in postconstruct) :
RepoInstall ri = new RepoInstall(tmpLib.getAbsolutePath(),null);//tmpLib = functx file
clientSession.execute(ri);
In Eclipse it works but when packaged and running from the jar, the application fail to start because I get a ClassNotFoundException of my own class.
I figured out, that it can not find the class because the classloader is closed.
And Basex is closing it :
Here is the stack :
What is wrong with my implementation ? How can I avoid BaseX to close the classloader ?

The BaseX ModuleLoader used to close URLClassLoaders, assuming that it had instantiated them itself for module loading:
if(loader instanceof URLClassLoader) {
try {
((URLClassLoader) loader).close();
} catch(final IOException ex) {
But this could also affect the default classloader, in case it was an URLClassLoader, so it was fixed recently (b82b4c). The fix is not yet contained in a released version, though.

Related

Java & SQL - XADataSource

We are running glassfish 4 for our java web application and are running into an issue with timers. Normal servlet calls are able to enjoy as many different connections as they want, which makes integrations much easier. Once we add timers however, the datasources need to be "XA" datasources instead. We set one up as such below:
public XADataSource getNewConnection() {
Encapsulations encap = new Encapsulations();
XADataSource ds = null;
try {
Context ctx = new InitialContext();
if(!encap.getDataSource().equals("Production")){
ds = (XADataSource) ctx.lookup("jdbc/XA_TEST");
}else{
ds = (XADataSource) ctx.lookup("jdbc/XA");
}
} catch (Exception e) {
CatchException.logException(null, e);
String error = e.toString();
}
return ds;
}
The problem is that when the ds = (XADataSource) ctx.lookup("jdbc/XA_TEST") line runs we get this error:
java.lang.ClassCastException: com.sun.gjc.spi.jdbc40.DataSource40 cannot be cast to javax.sql.XADataSource
We use the sqljdbc42 jar for our normal connections, so it is a bit strange to see 40 in there. Anyone know what the problem is? The datasource we are using was set up as an XADataSource, other than downloading a different jar I don't know what is missing.
It's been a while since I don't use Glassfish, but as far as I remember you should install the jar for your database provider that has the implementation of XADataSource interface. In MS SQL Server I've used the jTDS driver.
Another thing to consider from your code snippet is that you should have different configurations of glassfish for development and production, you should leave that kind of things to the application server and not inside your code. Glassfish is a full JavaEE Application Server, it has all the benefits including database connection pooling.

using custom keystore and jsseimplementation when upgrading to tomcat 8.5

We were using our own custom keystore and also provided the custom class implementation using JSSEImplementation and ServerSocketFactory and configured both in server.xml for "store" and "sslImplementation" attributes.
But now upgrading to 8.5, I started getting lot of ClassNotFoundException for JSSESocketFactory etc.
Doing little more research I found that they have removed many classes and methods like JSSESocketFactory.java, getServerSocketFactory(), getSSLUtil(AbstractEndpoint endpoint) etc.
So now, my question is:
is there any way in apache tomcat 8.5 in which I can configure my custom keystore under "store" in server.xml and use my own sslImplementation?
I was using AbstractEndpoint in the method signature to get the store name set in server.xml and then loading that keystore in MyJSSESocketFactory like this:
public class MySSLImplementation extends JSSEImplementation
{
#Override
public org.apache.tomcat.util.net.ServerSocketFactory getServerSocketFactory(
AbstractEndpoint endpoint) {
kStore = endpoint.getProperty("store");
return new MyJSSESocketFactory(endpoint, kStore);
}
}
public class MyJSSESocketFactory extends JSSESocketFactory {
private final AbstractEndpoint _endpoint;
private final String store;
public MyJSSESocketFactory(AbstractEndpoint endpoint, String store) {
super(endpoint);
this._endpoint = endpoint;
this.store = store;
}
/*
* Gets the SSL server's keystore.
*/
#Override
protected KeyStore getKeystore(String type, String provider, String pass)
throws IOException {
if ("MYKS".equalsIgnoreCase(type)) {
String keystoreName = store;
KeyStore ks = null;
try {
if (provider == null || provider.isEmpty()) {
ks = KeyStore.getInstance(type);
} else {
ks = KeyStore.getInstance(type, provider);
}
MyStoreParameter params = new MyStoreParameter(
keystoreName);
ks.load(params);
} catch (Exception ex) {
throw new IOException(
"Failed to load keystore " + keystoreName, ex);
}
return ks;
} else {
return super.getKeystore(type, provider, pass);
}
}
}
"MYKS" is set in server.xml for "store" attribute
For whatever it's worth, this is the commit that broke it:
Remove BIo specific JSSE code
Here is some of the rationale for removing it:
https://github.com/spring-projects/spring-boot/issues/6164
#markt-asf There appears to be a number of breaking changes in 8.5.3
(upgrading from 8.0.33):
Maven artifact org.apache.tomcat.embed:tomcat-embed-logging-juli no longer exists
Class org.apache.tomcat.util.net.ServerSocketFactory no longer exists
Class org.apache.tomcat.util.net.jsse.JSSESocketFactory no longer exists
Method JSSEImplementaton.getServerSockerFactory(AbstractEndpoint) no longer exists
Method JSSEImplementaton.getSSLUtil(AbstractEndpoint) no longer exists
Http11NioProtocol.getEndpoint() is no longer visible
Field org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML no longer exists
AbstractHttp11Protocol.setCompressableMimeTypes no longer exists
Reply:
1 is only required to enable container logging via log4j 1.x and that
version is no longer supported by the log4j community. log4j 2.x can
be used for container logging without any extra libraries.
2-6 are side effects of the connector refactoring in 8.5.x / 9.0.x.
They are all low-level internal APIs that I'm a little surprised to
find boot is using.
7 was part of the mechanism used to pass web.xml to Jasper for
processing. It was removed as it was no longer required as of Servlet
3.0 as all the necessary information was available via the standard Servlet API.
8 That was some API clean-up. That one could be restored fairly easily
for 8.5.x.
The commit was made in Nov, 2014.
As of Tomcat 8.0, the class was still there - and NOT on the *deprecated" list:
https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/tomcat/util/net/jsse/JSSESocketFactory.html
Here is the changelog that discussed "removing BIO" ("Blocking I/O"):
Migrating from Tomcat 8.0 to 8.5
Finallly, comparing these two links might help:
SSL/TLS Configuration HOW-TO Tomcat 8.0.39
SSL/TLS Configuration HOW-TO Tomcat 8.5.9

Tomcat 8 - LDAP: NameNotFoundException error code 32, remaining name empty string

Trying to migrate an application from WebLogic 12.2.1 to Tomcat 8.5.4, what under Weblogic was an entry as Foreign JNDI Providers for an LDAP connection has been migrated to a new Resource under Tomcat.
Following this advice on Stack Overflow, a custom LdapContextFactory has been packaged as a new jar file under Tomcat lib folder.
In the Tomcat server.xml file the following GlobalNamingResources/Resource has been configured:
<Resource name="ldapConnection"
auth="Container"
type="javax.naming.ldap.LdapContext"
factory="com.sample.custom.LdapContextFactory"
singleton="false"
java.naming.referral="follow"
java.naming.factory.initial="com.sun.jndi.ldap.LdapCtxFactory"
java.naming.provider.url="ldap://some.host:389"
java.naming.security.authentication="simple"
java.naming.security.principal="CN=some,OU=some,OU=some,DC=some,DC=a,DC=b"
java.naming.security.credentials="password"
com.sun.jndi.ldap.connect.pool="true"
com.sun.jndi.ldap.connect.pool.maxsize="10"
com.sun.jndi.ldap.connect.pool.prefsize="4"
com.sun.jndi.ldap.connect.pool.timeout="30000" />
The connection above works fine when browsing the LDAP directory via an LDAP browser like Apache Directory Studio / LDAP Browser embedded in Eclipse.
The custom com.sample.custom.LdapContextFactory is quite simple:
public class LdapContextFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
env.put(type, content);
}
return new InitialLdapContext(env, null);
}
}
However, at start-up Tomcat is throwing the following exception:
07-Sep-2016 15:04:01.064 SEVERE [main] org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans Exception processing Global JNDI Resources
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-031001E5, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; remaining name ''
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3160)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3081)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2888)
at com.sun.jndi.ldap.LdapCtx.c_listBindings(LdapCtx.java:1189)
at com.sun.jndi.toolkit.ctx.ComponentContext.p_listBindings(ComponentContext.java:592)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:330)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:317)
at javax.naming.InitialContext.listBindings(InitialContext.java:472)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:136)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:145)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:110)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.lifecycleEvent(GlobalResourcesLifecycleListener.java:82)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:94)
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:401)
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:345)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:784)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
at org.apache.catalina.startup.Catalina.start(Catalina.java:655)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:355)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:495)
Similar questions and investigations suggest an invalid LDAP DN, but:
The same LDAP configuration works fine via an LDAP Client
No search is actually performed, at start-up time Tomcat throws this exception without any query
The error suggests an empty string '' as remaining name, hence not really something not found, apparently
Question(s): Is this the correct way to migrate an Foreign JNDI Providers entry from WebLogic to Tomcat? How to fix an invalid LDAP DN entry with an empty remaining name? Could it be a missing baseDN to configure somewhere?
Update
The same exact error happens when changing the LdapContextFactory to the following, as suggested via comments:
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
String providerUrl = "no valid URL";
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
switch (type) {
case Context.PROVIDER_URL:
env.put(Context.PROVIDER_URL, content);
providerUrl = content;
break;
default:
env.put(type, content);
break;
}
}
InitialLdapContext context = null;
Object result = null;
try {
context = new InitialLdapContext(env, null);
LOGGER.info("looking up for " + providerUrl);
result = context.lookup(providerUrl);
} finally {
if (context != null) {
context.close();
}
}
LOGGER.info("Created new LDAP Context");
return result;
}
Change is confirmed via logging, to make sure it was deployed properly.
The involved listener is defined by default at the top of the server.xml file as
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
And cannot be disabled as per official documentation:
The Global Resources Lifecycle Listener initializes the Global JNDI resources defined in server.xml as part of the Global Resources element. Without this listener, none of the Global Resources will be available.
The same also happens on Tomcat version 8.5.5 and 7.0.69: simply adding the new global resource as above and the additional jar providing the factory above, the exception pointing at an empty remaining name will be thrown.
The stacktrace went away by appending to the java.naming.provider.url property the LDAP schema DN, using the first factory implementation provided in the question.
Below a screenshot of the LDAP client used in this context, the Apache Directory Studio / LDAP Browser embedded in Eclipse, from which it was possible to browse the concerned LDAP simply using the initial values of the question.
By appending the schema DN of the Root element to the connection URL, the exception went away and the LDAP resource is now shared via JNDI in Tomcat 8.
Further details as outcome of the troubleshooting:
In Tomcat 8 global resources are handled via a global resource listener, the GlobalResourcesLifecycleListener, defined by default in the server.xml file. Such a listener invokes a context.listBindings("") on bean creation, hence effectively browsing the LDAP directory.
This initial browsing may most probably be the difference between Tomcat and WebLogic, where LDAP is looked up via JNDI only when required, hence via direct query, rather than at start-up with a generic query. As such, in Tomcat the LDAP url would need further details, that is, a slightly different configuration as part of its url to directly point to a valid base DN.
From official WebLogic documentation:
On start up, WebLogic Server attempts to connect to the JNDI source. If the connection is successful, WebLogic Server sets up the requested objects and links in the local JNDI tree, making them available to WebLogic Server clients.
Hence, a connection is rather simpler than a listBindings:
Enumerates the names bound in the named context, along with the objects bound to them. The contents of any subcontexts are not included.

jUnit, Embedded Tomcat and Hibernate java.lang.ClassCastException

I have this very interesting issue that has been making me tear down my hair for the past two days.
I am using jUnit and an embedded Tomcat to test some API endpoints (Jersey).
I'm not a big fan of mocking and I made this setup to test the API responses in conditions as close as possible to production.
When the API receives a call, it should supply a response accordingly (found, not found, etc). This is where Hibernate comes in.
When I run this on my Tomcat set up in eclipse, or when I deploy the build (Maven) on a standalone Tomcat on the remote server, everything works fine, but when the API is called on the embedded Tomcat during the test I get this error:
java.lang.ClassCastException: com.models.listing.Listing cannot be
cast to com.models.listing.Listing
Yes, it's the same class name.
To retrieve the Listing object I use the Hibernate standard of getting a persisted object by ID
Listing listing = session.get(Listing.class, ID);
This is how the embedded tomcat setup looks like:
public void start(String appName) throws Exception {
File root = getRootFolder();
System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true");
Path tempPath = Files.createTempDirectory("tomcat-base-dir");
tomcat = new Tomcat();
tomcat.setPort(0);
tomcat.enableNaming();
tomcat.setSilent(true);
tomcat.setBaseDir(tempPath.toString());
tomcat.getHost().setDeployOnStartup(true);
tomcat.getHost().setAutoDeploy(true);
tomcat.getHost().setAppBase(tempPath.toString());
File webContentFolder = new File(root.getAbsolutePath(), "src/main/webapp/");
if (!webContentFolder.exists()) {
webContentFolder = Files.createTempDirectory("default-doc-base").toFile();
}
StandardContext ctx = (StandardContext) tomcat.addWebapp("/" + appName, webContentFolder.getAbsolutePath());
//Disable TLD scanning by default
if (System.getProperty(Constants.SKIP_JARS_PROPERTY) == null ) {
System.out.println("disabling TLD scanning");
StandardJarScanFilter jarScanFilter = (StandardJarScanFilter) ctx.getJarScanner().getJarScanFilter();
jarScanFilter.setTldSkip("*");
}
System.out.println("configuring app with basedir: " + webContentFolder.getAbsolutePath());
// Declare an alternative location for your "WEB-INF/classes" dir
// Servlet 3.0 annotation will work
File additionWebInfClassesFolder = new File(root.getAbsolutePath(), "target/classes");
WebResourceRoot resources = new StandardRoot(ctx);
WebResourceSet resourceSet;
if (additionWebInfClassesFolder.exists()) {
resourceSet = new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClassesFolder.getAbsolutePath(), "/");
System.out.println("loading WEB-INF resources from as '" + additionWebInfClassesFolder.getAbsolutePath() + "'");
} else {
resourceSet = new EmptyResourceSet(resources);
}
resources.addPreResources(resourceSet);
ctx.setResources(resources);
//start tomcat
tomcat.start();
}
And this is how the Hibernate configuration looks like:
private static SessionFactory buildSessionFactory() {
// setup the session factory
Configuration configuration = new Configuration();
//add annotated classes
configuration.addAnnotatedClass(Listing.class);
//connection properties
configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
configuration.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
configuration.setProperty("hibernate.connection.url", "jdbc:mysql://<some IP>:3306/<some db name>");
configuration.setProperty("hibernate.connection.release_mode", "auto");
configuration.setProperty("hibernate.connection.username", "<some username>");
configuration.setProperty("hibernate.connection.password", "<some password>");
configuration.setProperty("hibernate.connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider");
//sql properties
configuration.setProperty("show_sql", "true");
configuration.setProperty("format_sql", "true");
//misc properties
configuration.setProperty("hibernate.hbm2ddl.auto", "validate");
configuration.setProperty("hibernate.current_session_context_class", "thread");
//c3p0 properties
configuration.setProperty("hibernate.c3p0.min_size", "1");
configuration.setProperty("hibernate.c3p0.max_size", "10");
configuration.setProperty("hibernate.c3p0.timeout", "100");
configuration.setProperty("hibernate.c3p0.max_statements", "50");
configuration.setProperty("hibernate.c3p0.idle_test_period", "1000");
configuration.setProperty("hibernate.c3p0.validate", "true");
//return null
return configuration.buildSessionFactory();
}
To summarize, the test starts in jUnit, the embedded tomcat instance fires up and with a REST client I send a request to an API endpoint. The endpoint responds after it retrieves the resource from Hibernate.
Dependency versions:
Glassfish Jersey 2.23
jUnit 4.11
Tomcat embedded 8.5.3
Hibernate 5.2.1
My best bet is some issue with the class loader.
I know that the JVM sees classes as different if they were loaded with different class loaders even if it's basically the same class from the same package and so on, but I just don't seem to find a way to make this work.
Maybe my hypothesis is completely off and I am missing something here, so if anyone has encountered something like this or has some suggestions (I already tried tens of "solutions") please jump in.
Thanks everyone for the help in advance!
I still didn't figure out the mystery and I just let go after some other "trial and error" ideas with class loaders and so on.
I "fixed" it by downgrading to hibernate 4.3.11

Jetty embedded server - change the path where the war file will be deployed

I am using Jetty 9.2 to run a war file inside the embedded Jetty server. I have no 'web.xml', no webapp folder, just the war file i want to deploy. I am running the war file without any problems with this code:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class DeployWar {
public static void main(String[] args) {
Server server = new Server(9090);
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar("test.war");
server.setHandler(webapp);
try {
server.start();
System.out.println("Press any key to stop the server...");
System.in.read(); System.in.read();
server.stop();
} catch (Exception ex) {
System.out.println("error");
}
System.out.println("Server stopped");
}
}
The 'problem' is that the war is deployed(unpacked) in a predefined location (something like C:\Users\MyPCname\AppData\Local\Temp\jetty...) and i want to change it to something different, let's say to the bin folder of my project.
Is this possible?
Actually the answer to my problem turned out to be pretty simple. The desired functionality is already provided by jetty. I had to just add these lines above the 'setWar' method:
File webappsFolder = new File("jettyWebapps/");
webappsFolder.mkdirs();
webapp.setTempDirectory(webappsFolder);
here you can see the jetty documentation.
This is pretty well described in jetty documentation. Please have a look at http://www.eclipse.org/jetty/documentation/current/embedding-jetty.html.
public class OneWebApp
{
public static void main( String[] args ) throws Exception
{
// Create a basic jetty server object that will listen on port 8080.
// Note that if you set this to port 0 then a randomly available port
// will be assigned that you can either look in the logs for the port,
// or programmatically obtain it for use in test cases.
Server server = new Server(8080);
// Setup JMX
MBeanContainer mbContainer = new MBeanContainer(
ManagementFactory.getPlatformMBeanServer());
server.addBean(mbContainer);
// The WebAppContext is the entity that controls the environment in
// which a web application lives and breathes. In this example the
// context path is being set to "/" so it is suitable for serving root
// context requests and then we see it setting the location of the war.
// A whole host of other configurations are available, ranging from
// configuring to support annotation scanning in the webapp (through
// PlusConfiguration) to choosing where the webapp will unpack itself.
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
File warFile = new File(
"C:\Users\MyPCname\AppData\Local\Temp\jetty\your.war");
webapp.setWar(warFile.getAbsolutePath());
webapp.addAliasCheck(new AllowSymLinkAliasChecker());
// A WebAppContext is a ContextHandler as well so it needs to be set to
// the server so it is aware of where to send the appropriate requests.
server.setHandler(webapp);
// Start things up!
server.start();
// The use of server.join() the will make the current thread join and
// wait until the server is done executing.
// See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
server.join();
}
}

Categories