Skip to main content

JackRabbit JCA Classloading issues

9 replies [Last post]
Gustavo Henriqu...
Offline
Joined: 2011-10-27

I have some issues with Glassfish ClassLoader.

I am using JackRabbit JCA (Resource Adapter for JCR Repositories) to
provide JCR repositories (JSR-283).

I have many problems and all looks like related to classloading.

It looks like that Glassfish ClassLoader do not find the libraries inside
the jackrabbit JCA rar.
JackRabbit JCA is shipped with JackRabbit Core inside that provides the
"server implementation". But the class with this implementation is not
found by the classloader.

I will try to explain what I found trying to solve this problem and the
workarounds I've used and see if someone may help me to understand better
the problems and how to solve them (or file a bug).

JackRabbit JCA is a Resource Adapter. You may create connection pool for a
specific JCR Repository providing some parameters such as HomeDir (the
directory where the repository is found).
JackRabbit JCA based on these parameters specified uses
org.apache.jackrabbit.client.RepositoryFactoryImpl class. This
RepositoryFactoryImpl class just get the parameters specified and check the
existence of the HomeDir parameter. When this HomeDir parameter is found,
it try to load the class org.apache.jackrabbit.core.RepositoryFactoryImpl
dynamically (provided in jackrabbit-core inside the jackrabbit-jca). The
code used to load org.apache.jackrabbit.core.RepositoryFactoryImpl is
similar to:
Class<?> repositoryFactoryClass =
Class.forName("org.apache.jackrabbit.core.RepositoryFactoryImpl",
true,Thread.currentThread().getContextClassLoader());
repositoryFactory = repositoryFactoryClass.newInstance();

If the clients perform explicit JNDI lookups, the
org.apache.jackrabbit.client.RepositoryFactoryImpl is not being found.

Another very common option for the clients to access the JCR repository is
to use JcrUtils.getRepository(String uri) provided in
jackrabbit-jcr-commons (specifying a "jndi:jcr/Repository" uri) . This
utility basically search using ServiceRegistry.lookupProviders for
RepositoryFactory providers that accepts the parameters specified. Code:
Iterator iterator =
ServiceRegistry.lookupProviders(RepositoryFactory.class);

Using this utility, the code fails again (it really make an explicit JNDI
lookup inside JndiRepositoryFactory class).

Since these classes were not being found inside JackRabbit JCA, I tried as
a workaround to add jackrabbit-core and jackrabbit-commons modules inside
the EAR.
I was able to get the repository but I needed another workaround.
I added JMX methods to a singleton EJB to make the tests easier (and study
JMX classloader issues).
If I invoke JCRUtils.getRepository directly from this singleton EJB, the
RepositoryFactory implementations are not found.
So, I did a following workaround: Created a Stateless Session bean. The
Singleton EJB works like a "proxy" and just delegate the methods from
Singleton ejb to the stateless ejb. Using this workround, finally I could
successfully get a JCR repository using JNDI. Additional note: if the
delegation is done to static methods defined in the stateless ejb, the
workaround doesn't work.

To make easier to reproduce these problems,
I have created a small EJB module with the stateless and the singleton ejb
as discussed.
This EJB module has a Singleton JMXServiceActivator. The
JMXServiceActivator is registered in managed bean server as
app.JMXServiceActivator and you can use jmx client (such as jconsole) to
launch the following methos:
1 - getRepositoryUsingExplicitJNDI(String jndiName);
2 - getRepositoryUsingJCRUtils(String jndiName);
3 - getRepositoryUsingExplicitJNDIWithoutProxy(String jndiName);
4 - getRepositoryUsingJCRUtilsWithoutProxy(String jndiName);

After, I created 3 different client EARs to check all these issues
(attached in this e-mail):
1 - EAR
No Jackrabbit dependencies
(It should be possible to get the jackrabbit repository using explicit
JNDI)
2 - EAR2
jackrabbit-commons and jackrabbit-core dependences
(it really can get the JCR repository but a "proxy" using Stateless
session being is needed to make it work)
3 - EAR3
Just jackrabbit-commons dependency
(It should be possible to get the JCR repository using
JCRUtils.getRepository(String uri) but it does not work)

I am doing something wrong?
Are these really Classloading bugs?
Is this just an unique bug or there are multiple bugs involved?

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
ss141213
Offline
Joined: 2005-03-30

Gustavo,

In your case, one jackrabbit class is loading another jackrabbit class.
I don't see any reason to load using thread's context class loader.
Could you try changing
Class<?> repositoryFactoryClass =
Class.forName("org.apache.jackrabbit.core.RepositoryFactoryImpl",
true,Thread.currentThread().getContextClassLoader());
to
Class<?> repositoryFactoryClass =
Class.forName("org.apache.jackrabbit.core.RepositoryFactoryImpl");

Sahoo

On Tuesday 20 December 2011 01:13 AM, Gustavo Henrique Orair wrote:
> I have some issues with Glassfish ClassLoader.
>
> I am using JackRabbit JCA (Resource Adapter for JCR Repositories) to
> provide JCR repositories (JSR-283).
>
> I have many problems and all looks like related to classloading.
>
> It looks like that Glassfish ClassLoader do not find the libraries
> inside the jackrabbit JCA rar.
> JackRabbit JCA is shipped with JackRabbit Core inside that provides
> the "server implementation". But the class with this implementation is
> not found by the classloader.
>
> I will try to explain what I found trying to solve this problem and
> the workarounds I've used and see if someone may help me to understand
> better the problems and how to solve them (or file a bug).
>
> JackRabbit JCA is a Resource Adapter. You may create connection pool
> for a specific JCR Repository providing some parameters such as
> HomeDir (the directory where the repository is found).
> JackRabbit JCA based on these parameters specified uses
> org.apache.jackrabbit.client.RepositoryFactoryImpl class. This
> RepositoryFactoryImpl class just get the parameters specified and
> check the existence of the HomeDir parameter. When this HomeDir
> parameter is found, it try to load the class
> org.apache.jackrabbit.core.RepositoryFactoryImpl dynamically (provided
> in jackrabbit-core inside the jackrabbit-jca). The code used to load
> org.apache.jackrabbit.core.RepositoryFactoryImpl is similar to:
> Class<?> repositoryFactoryClass =
> Class.forName("org.apache.jackrabbit.core.RepositoryFactoryImpl",
> true,Thread.currentThread().getContextClassLoader());
> repositoryFactory = repositoryFactoryClass.newInstance();
>
> If the clients perform explicit JNDI lookups, the
> org.apache.jackrabbit.client.RepositoryFactoryImpl is not being found.
>
>
> Another very common option for the clients to access the JCR
> repository is to use JcrUtils.getRepository(String uri) provided in
> jackrabbit-jcr-commons (specifying a "jndi:jcr/Repository" uri) . This
> utility basically search using ServiceRegistry.lookupProviders for
> RepositoryFactory providers that accepts the parameters specified. Code:
> Iterator iterator =
> ServiceRegistry.lookupProviders(RepositoryFactory.class);
>
> Using this utility, the code fails again (it really make an explicit
> JNDI lookup inside JndiRepositoryFactory class).
>
> Since these classes were not being found inside JackRabbit JCA, I
> tried as a workaround to add jackrabbit-core and jackrabbit-commons
> modules inside the EAR.
> I was able to get the repository but I needed another workaround.
> I added JMX methods to a singleton EJB to make the tests easier (and
> study JMX classloader issues).
> If I invoke JCRUtils.getRepository directly from this singleton EJB,
> the RepositoryFactory implementations are not found.
> So, I did a following workaround: Created a Stateless Session bean.
> The Singleton EJB works like a "proxy" and just delegate the methods
> from Singleton ejb to the stateless ejb. Using this workround, finally
> I could successfully get a JCR repository using JNDI. Additional note:
> if the delegation is done to static methods defined in the stateless
> ejb, the workaround doesn't work.
>
>
> To make easier to reproduce these problems,
> I have created a small EJB module with the stateless and the singleton
> ejb as discussed.
> This EJB module has a Singleton JMXServiceActivator. The
> JMXServiceActivator is registered in managed bean server as
> app.JMXServiceActivator and you can use jmx client (such as jconsole)
> to launch the following methos:
> 1 - getRepositoryUsingExplicitJNDI(String jndiName);
> 2 - getRepositoryUsingJCRUtils(String jndiName);
> 3 - getRepositoryUsingExplicitJNDIWithoutProxy(String jndiName);
> 4 - getRepositoryUsingJCRUtilsWithoutProxy(String jndiName);
>
> After, I created 3 different client EARs to check all these issues
> (attached in this e-mail):
> 1 - EAR
> No Jackrabbit dependencies
> (It should be possible to get the jackrabbit repository using
> explicit JNDI)
> 2 - EAR2
> jackrabbit-commons and jackrabbit-core dependences
> (it really can get the JCR repository but a "proxy" using Stateless
> session being is needed to make it work)
> 3 - EAR3
> Just jackrabbit-commons dependency
> (It should be possible to get the JCR repository using
> JCRUtils.getRepository(String uri) but it does not work)
>
> I am doing something wrong?
> Are these really Classloading bugs?
> Is this just an unique bug or there are multiple bugs involved?
>
>
>

Gustavo Henriqu...
Offline
Joined: 2011-10-27

I checked the j2EE6 specification for classloading behaviour.
In section 8.3.2 - EJB Container Class Loading Requirements it states:

"*Components in the EJB container must have access to the following classes
and resources.
(*...)
** The contents of all jar files included in each resource adapter archive
(rar file) deployed separately to the application server, if that resource
adapter is used to satisfy any resource references in the module.
(...)"

*So, the behaviour I found in Glassfish seems like not J2EE6 compliant.

Should I file a bug?

ss141213
Offline
Joined: 2005-03-30

No, it is not a bug. Your ejb modules does not have any resource
references that are satisfied by jackrabbit rar. Your ejb seems to be
accessing jackrabbit jca rar classes directly using jackrabbit API
called JCRUtils and GlassFish has no idea that this dependency comes
from jackrabbit rar. Add @Resource to your ejb to a jackrabbit rar
supplied connection factory for the class loader to detect the dependency.

Sahoo
On Tuesday 20 December 2011 11:39 PM, Gustavo Henrique Orair wrote:
> I checked the j2EE6 specification for classloading behaviour.
> In section 8.3.2 - EJB Container Class Loading Requirements it states:
>
> "/Components in the EJB container must have access to the following
> classes and resources.
> (/...)
> /* The contents of all jar files included in each resource adapter
> archive (rar file) deployed separately to the application server, if
> that resource adapter is used to satisfy any resource references in
> the module.
> (...)"
>
> /So, the behaviour I found in Glassfish seems like not J2EE6 compliant.
>
> Should I file a bug?

Gustavo Henriqu...
Offline
Joined: 2011-10-27

Hi Sahoo, thanks for your prompt response!

Regarding your comment about my ejb seems to be accessing jackrabbit jca
rar classes directly, my ejb uses just the JcrUtils.getRepository code.
This class is provided by the jackrabbit-jcr-commons module. Inside this
jackrabbit-jcr-commons module there is also a JndiRepositoryFactory class,
this class should be found by the
ServiceRegistry.lookupProviders(RepositoryFactory.class) and make an
explicit JNDI lookup (as I used an URI with jndi: scheme). My ejb just need
the jackrabbit-jcr-commons dependency and in fact, using this approach, I
am not tied to JackRabbit implementation and could use any other JCR
implementation (jackrabbit-jcr-commons module just use JCR API code). This
is exactly the EAR3 use case I have provided in my first e-mail. Based on
your concerns, this use case seems to fail because the explicit JNDI lookup
couldn't get the JCR Repository.

The problem seems to be really concerned (as you said in your first e-mail)
as how org.apache.jackrabbit.client.RepositoryFactoryImpl class (inside
jackrabbit-api module shipped with JackRabbit-JCA) try to load the
org.apache.jackrabbit.core.RepositoryFactoryImpl class (inside
jackrabbit-core module shipped with JackRabbit-JCA) since it uses
Thread.currentThread().getContextClassLoader() instead of the default
classLoader. Notice that the
org.apache.jackrabbit.client.RepositoryFactoryImpl class is used in
multiple different environments, not just J2EE or Glassfish, etc.

My conclusion is that Glassfish's default class loading policy (derived) is
incompatible with classloading policy expected by the jackrabbit-api module.

Anyway, I will give your suggestion a try and change the code from
org.apache.jackrabbit.client.RepositoryFactoryImpl class inside
jackrabbit-api module to use the default classloader instead of
Thread.currentThread().getContextClassLoader() while loading the
org.apache.jackrabbit.core.RepositoryFactoryImpl class. But Apache
Jackrabbit developers will probably argue this would break all other uses
of this class in other environments.

Jagadish Prasat...
Offline
Joined: 2011-03-11

Hi Gustavo,

If you have a to a connector-resource of JackRabbit-RAR
in the application (either via @Resource or via the descriptor as Sahoo
stated), the application will be able to see the RAR classes. (Thread
context classloader should be application's classloader in this case and
it will have RAR's classloader in the parent's chain).

Yes, "derived" is the default class-loading policy. You can try setting
it to "global" to see whether it works fine. Later, once you have the
defined the application's descriptor (or @Resource), the
RAR will be available to the application.

Reference:
http://docs.oracle.com/cd/E18930_01/html/821-2418/bealr.html#gjjyy

Thanks,
-Jagadish
On Wed, 2011-12-21 at 11:23 -0200, Gustavo Henrique Orair wrote:
> Hi Sahoo, thanks for your prompt response!
>
> Regarding your comment about my ejb seems to be accessing jackrabbit
> jca rar classes directly, my ejb uses just the JcrUtils.getRepository
> code. This class is provided by the jackrabbit-jcr-commons module.
> Inside this jackrabbit-jcr-commons module there is also a
> JndiRepositoryFactory class, this class should be found by the
> ServiceRegistry.lookupProviders(RepositoryFactory.class) and make an
> explicit JNDI lookup (as I used an URI with jndi: scheme). My ejb just
> need the jackrabbit-jcr-commons dependency and in fact, using this
> approach, I am not tied to JackRabbit implementation and could use any
> other JCR implementation (jackrabbit-jcr-commons module just use JCR
> API code). This is exactly the EAR3 use case I have provided in my
> first e-mail. Based on your concerns, this use case seems to fail
> because the explicit JNDI lookup couldn't get the JCR Repository.
>
> The problem seems to be really concerned (as you said in your first
> e-mail) as how org.apache.jackrabbit.client.RepositoryFactoryImpl
> class (inside jackrabbit-api module shipped with JackRabbit-JCA) try
> to load the org.apache.jackrabbit.core.RepositoryFactoryImpl class
> (inside jackrabbit-core module shipped with JackRabbit-JCA) since it
> uses Thread.currentThread().getContextClassLoader() instead of the
> default classLoader. Notice that the
> org.apache.jackrabbit.client.RepositoryFactoryImpl class is used in
> multiple different environments, not just J2EE or Glassfish, etc.
>
> My conclusion is that Glassfish's default class loading policy
> (derived) is incompatible with classloading policy expected by the
> jackrabbit-api module.
>
> Anyway, I will give your suggestion a try and change the code from
> org.apache.jackrabbit.client.RepositoryFactoryImpl class inside
> jackrabbit-api module to use the default classloader instead of
> Thread.currentThread().getContextClassLoader() while loading the
> org.apache.jackrabbit.core.RepositoryFactoryImpl class. But Apache
> Jackrabbit developers will probably argue this would break all other
> uses of this class in other environments.
>

Gustavo Henriqu...
Offline
Joined: 2011-10-27

Hi Jagadish,

I really can't inject these resources in my ejb, this is motivated because
in fact I am using a StorageFacade that may use a JcrStorage implementation
or another storage implementation such as FSStorage. This is decided
dynamically based on properties files. So, I really want to know how to get
the JCR repository from the JackRabbit JCA using explicit JNDI lookup.

If I really understand the problem, the best workaround from me would be
insert in EAR's descriptor application.xml (or glassfish-resources.xml ???)
a "fake" injection just to tell Glassfish that I want to get resources
using explicit JNDI lookup that references the JackRabbit JCA Resource
Adapter inside the EAR's modules.

I read http://docs.oracle.com/cd/E18930_01/html/821-2418/bealr.html#gjjyyand
searched for this resource-adapter-mid but I couldn't realize how
exactly could I do that.
May someone point me how to do that?

Anyway, If you are really concerned why I need to make the explicit JNDI
lookups, I will try to explain better the problem.
My code has a Crawler class used to download documents from internet, these
crawlers instances may be used both in a J2EE and J2SE environment. These
crawlers just download the documents and storage them using a
StorageFacade. There are two implementations of the StorageFacade:
JCRStorage and FSStorage. The crawlers read a properties file to chose
which implementation to be used. This feature is important for us because I
do not need to change and compile code if I use different environments, I
just need to change the properties file.

If the JCRStorage is used, it get from another properties file the URI to
be accessed and use the JcrUtils.getRepository method to access the JCR
Repository. Again, this approach make possible to use the same binary
without changing the code and compiling in both J2EE environment and J2SE
environment. If I decided to change from a JackRabbit JCA get using
explicit JNDI lookup to a Jackrabbit WebDav Server, I just need to change
one properties file. Notice that this code used in the JCRStorage
implementation uses just code from jackrabbit-jcr-commons module that is
not tied to the JackRabbit implementation. It makes my framework very easy
to change the JCR implementation. I would need just the
jackrabbit-jcr-commons dependency inside my EAR and this would be the
unique JackRabbit dependency inside the EAR.

Actually, in production environment, I have a CrawlerManager EJB that
creates, executes and manage the lifecycle of the Crawlers instances. In
this environment I configure the StorageFacade to use JCRStorage
implementation and the uri to be accessed:
# Inform the Storage facade to get the JCR repository by an explicit Jndi
lookup by the name jcr/Repository
uri=jndi:jcr/Repository
It results in an explicit JNDI lookup for the JCR Repository.
The Jcr Repository is really accessed inside a class that may be used as
J2SE and I cannot inject the repository.
I need just make the explicit JNDI lookup works.

Gustavo Henriqu...
Offline
Joined: 2011-10-27

Hi Jagadish and Sahoo,

I changed the class loading policy to global.
It resulted in many conflicts because the classes and resources inside the
resource-adapter were "preferred" against my own libraries and
implementations. These are some of the conflicts:
1- Derby dependence conflict
Jackrabbit-jca has a derby library, I had to remove the derby library
dependency from jackrabbit-jca that was conflicting.
2 - commons-io conflict
My code uses new commons-io version 2 API. The code failed because
JackRabbit JCA is shipped with commons-io version 1.4. I replaced
commons-io 1.4 by the commons-io 2.0.1 inside jackrabbit-jca.

After solving these conflicts, there were unsolved issues with Logging
because jackrabbit-jca uses slf4j and logback and it broke my Logging also
based on slf4j and logback but I am ignoring this problems for a while.

But the client was able to get the Jcr Repository correctly and the
Crawlers were executed ok.

So, using classloader global policy did the trick, but I really would like
to solve the problem and keep the derived classloading policy.

I would really prefer a workaround such as inserting some xml inside ear
to inform to glassfish to make jackrabbit-jca resource adapter should be
available just to this specific application.

Best regards,

Gustavo Henriqu...
Offline
Joined: 2011-10-27

Dear all,

I tried to write both glassfish-application.xml and glassfish-resources.xml
to make jackrabbit-jca work together to derived classloading policy.
But, these files didn't change the classloader behaviour.

I am senting attached the Sample Maven Project I've used to test these
concerns.
I've attached again the examples EAR generated. Notice my use case is EAR3
module (ear with only jackrabbit-jcr-commons dependency)... The
JndiRepositoryFactory class inside jackrabbit-jcr-commons performed an
explicit JNDI lookup. It found the JNDI resource, got the connection pool
parameters and tried to create the Repository, but the creation failed
because the provider's classes inside jackrabbit-jca were not found (you
can see in stacktrace the unique provider found was JndiRepositoryFactory
from jackrabbit-jcr-commons module installed inside the ear's lib
directory).

These are the content of glassfish-application.xml and
glassfish-resources.xml files:
glassfish-application.xml:

jcr/Repository
jcr/Repository

glassfish-resources.xml:
<?xml version="1.0" encoding="UTF-8"?>

Stack trace:
Caused by: javax.jcr.RepositoryException: Unable to access a repository
with the following settings:
org.apache.jackrabbit.repository.home: /tmp/jcr_repository
The following RepositoryFactory classes were consulted:
org.apache.jackrabbit.commons.JndiRepositoryFactory: declined
Perhaps the repository you are trying to access is not available at the
moment.
at
org.apache.jackrabbit.commons.JcrUtils.getRepository(JcrUtils.java:215)
at
org.apache.jackrabbit.jca.JCARepositoryManager.createRepository(JCARepositoryManager.java:71)
at
org.apache.jackrabbit.jca.JCAManagedConnectionFactory.getRepository(JCAManagedConnectionFactory.java:195)
at
org.apache.jackrabbit.jca.JCAManagedConnection.openSession(JCAManagedConnection.java:100)
... 82 more

Best regards,

Bernhard Thalma...
Offline
Joined: 2011-10-10

I had similar issue and discussion some weeks ago.

GlassFish will only load the adapter classes if one of your JEE deployment
descriptors (web.xml ,ejb-jar.xml) has a resource-ref element for the
adapter.

I worked around this by deploying the resource-adapter separately.

-Bernhard

On Tue, Dec 20, 2011 at 7:09 PM, Gustavo Henrique Orair <
> wrote:

> I checked the j2EE6 specification for classloading behaviour.
> In section 8.3.2 - EJB Container Class Loading Requirements it states:
>
> "*Components in the EJB container must have access to the following
> classes and resources.
> (*...)
> ** The contents of all jar files included in each resource adapter
> archive (rar file) deployed separately to the application server, if that
> resource adapter is used to satisfy any resource references in the module.
> (...)"
>
> *So, the behaviour I found in Glassfish seems like not J2EE6 compliant.
>
> Should I file a bug?
>
>
>