Groovy Modules

Proxy is tied to one classloader limitation

Details

  • Type: Bug Bug
  • Status: Open Open
  • Priority: Major Major
  • Resolution: Unresolved
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: GroovyWS
  • Description:
    Hide

    I am using groovyws in a grails application (multiple requests/threads). Everything works with I create a new Client for every request, but this is very expensive. If I don't do this, and reuse the client, I get a "ClassNotFoundException" when I ask the client for a class (the create method)

    I actually downloaded and patched groovyws to use the same classloader that the client was initialized with, but when I did this, the class was never found in the classloader, so it's harder then I thought to do this.

    I deployed my application and the performance is just too slow to use groovyws with this limitation.

    Show
    I am using groovyws in a grails application (multiple requests/threads). Everything works with I create a new Client for every request, but this is very expensive. If I don't do this, and reuse the client, I get a "ClassNotFoundException" when I ask the client for a class (the create method) I actually downloaded and patched groovyws to use the same classloader that the client was initialized with, but when I did this, the class was never found in the classloader, so it's harder then I thought to do this. I deployed my application and the performance is just too slow to use groovyws with this limitation.
  • Environment:
    n/a
  1. WSClient.java.patch
    (2 kB)
    Sebastian Rühl
    27/Apr/10 7:40 AM

Activity

Hide
Ian Homer added a comment - 14/Jul/09 8:19 AM

We've got the same issue which we have a non-elegant work where we store the classloader (Thread.currentThread().getContextClassLoader() ) used for initialisation and reuse for class creation, e.g.

initialise

proxy=new WSClient("http://terraservice.net/TerraService.asmx?WSDL", this.class.classLoader)
proxy.initialize()
classLoader=Thread.currentThread().getContextClassLoader()

method call

def place = classLoader.loadClass("com.terraserver_usa.terraserver.Place").newInstance()
place.city = city
place.state = state
place.country = country
proxy.ConvertPlaceToLonLatPt(place)

See http://bemoko.googlecode.com/svn/sites/exercise_webservice_soap/trunk/plugins/LocationPlugin.groovy

Show
Ian Homer added a comment - 14/Jul/09 8:19 AM We've got the same issue which we have a non-elegant work where we store the classloader (Thread.currentThread().getContextClassLoader() ) used for initialisation and reuse for class creation, e.g. initialise proxy=new WSClient("http://terraservice.net/TerraService.asmx?WSDL", this.class.classLoader) proxy.initialize() classLoader=Thread.currentThread().getContextClassLoader() method call def place = classLoader.loadClass("com.terraserver_usa.terraserver.Place").newInstance() place.city = city place.state = state place.country = country proxy.ConvertPlaceToLonLatPt(place) See http://bemoko.googlecode.com/svn/sites/exercise_webservice_soap/trunk/plugins/LocationPlugin.groovy
Hide
Sebastian Rühl added a comment - 11/Feb/10 10:44 AM

Isn't that how it works in latest source?

AbstractCXFWSClient.java
/**
     * Creates an object for the given classname using the classloader of the
     * current thread.
     *
     * @param classname The classname of the object which should be created.
     * @return An instance of the class.
     * @throws IllegalAccessException
     */
    public Object create(String classname) throws IllegalAccessException {

        if (classname == null) {
            throw new IllegalArgumentException("Must provide the class name");
        }

        Class clazz = null;
        try {
            clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        assert clazz != null;
        if (clazz.isEnum()){
//            for (Field f:clazz.getFields()) {
//                System.out.println("field: "+f.getName());
//            }
            return clazz;
        }

        Object obj = null;

        try {
            obj = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        return obj;
    }

but what I really don't understand is why then a ClassLoader is necessary... shouldn't then the create method overridden and you the classloader from WSClient?

WSClient.java
/**
     * The ClassLoader to use to generate classes from WSDL.
     */
    protected ClassLoader classloader;

    /**
     * Default constructor.
     *
     * @param wsdlLocation The url of the wsdl-file
     * @param classloader  The classoader
     */
    public WSClient(String wsdlLocation, ClassLoader classloader)
    {
        try {
            this.url = new URL(wsdlLocation);
            //this.localWSDL = this.url;
            this.classloader = classloader;
            //this.client = createClient(this.url, classloader);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("URL is not valid.", e);
        }

        this.sslHelper               = new SSLHelper();
        this.proxyHelper             = new ProxyHelper();
        this.basicAuthHelper         = new BasicAuthenticationHelper();
        this.mtomHelper              = new MtomHelper();
        this.connectionTimeoutHelper = new ConnectionTimeoutHelper();
        this.soapHelper              = new SoapHelper();
    }

Anyway I also experience Problems with the class Loading...
I will post a example later soon

Show
Sebastian Rühl added a comment - 11/Feb/10 10:44 AM Isn't that how it works in latest source?
AbstractCXFWSClient.java
/**
     * Creates an object for the given classname using the classloader of the
     * current thread.
     *
     * @param classname The classname of the object which should be created.
     * @return An instance of the class.
     * @throws IllegalAccessException
     */
    public Object create(String classname) throws IllegalAccessException {

        if (classname == null) {
            throw new IllegalArgumentException("Must provide the class name");
        }

        Class clazz = null;
        try {
            clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        assert clazz != null;
        if (clazz.isEnum()){
//            for (Field f:clazz.getFields()) {
//                System.out.println("field: "+f.getName());
//            }
            return clazz;
        }

        Object obj = null;

        try {
            obj = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        return obj;
    }
but what I really don't understand is why then a ClassLoader is necessary... shouldn't then the create method overridden and you the classloader from WSClient?
WSClient.java
/**
     * The ClassLoader to use to generate classes from WSDL.
     */
    protected ClassLoader classloader;

    /**
     * Default constructor.
     *
     * @param wsdlLocation The url of the wsdl-file
     * @param classloader  The classoader
     */
    public WSClient(String wsdlLocation, ClassLoader classloader)
    {
        try {
            this.url = new URL(wsdlLocation);
            //this.localWSDL = this.url;
            this.classloader = classloader;
            //this.client = createClient(this.url, classloader);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("URL is not valid.", e);
        }

        this.sslHelper               = new SSLHelper();
        this.proxyHelper             = new ProxyHelper();
        this.basicAuthHelper         = new BasicAuthenticationHelper();
        this.mtomHelper              = new MtomHelper();
        this.connectionTimeoutHelper = new ConnectionTimeoutHelper();
        this.soapHelper              = new SoapHelper();
    }
Anyway I also experience Problems with the class Loading... I will post a example later soon
Hide
Sebastian Rühl added a comment - 12/Feb/10 6:59 AM

Ok just for my Understanding...

If I create a

WSClient("url",cl)

then the ClassLoader defined by cl should be used to generate the classes from the WSDL...
This means the classes for example a Map<String,Object> (Which accords to the type package.String2AnyType) should be loadable trough this ClassLoader...
so I could override the create Method like:

class FixedWSClient extends WSClient{
	
	public FixedWSClient(String wsdlLocation, ClassLoader classloader) {
		super(wsdlLocation,classloader)
	}
	
	public Object create(String classname) {
		classloader.loadClass(classname).newInstance()
	}
}

the create Method from this class than will be used...

However I get a java.lang.ClassNotFoundException Exception

To be sure that cxf won't use a different I instantiate the FixedWSClient like this

def cl = new GroovyClassLoader()
client = cl.loadClass("package.FixedWSClient").newInstance(url,cl)
client.initialize()

I also tried to set the Thread.currentThread().contextClassLoader = cl

Nothing helps...
any Idea or explanations on this behaviour?

Show
Sebastian Rühl added a comment - 12/Feb/10 6:59 AM Ok just for my Understanding... If I create a
WSClient("url",cl)
then the ClassLoader defined by cl should be used to generate the classes from the WSDL... This means the classes for example a Map<String,Object> (Which accords to the type package.String2AnyType) should be loadable trough this ClassLoader... so I could override the create Method like:
class FixedWSClient extends WSClient{
	
	public FixedWSClient(String wsdlLocation, ClassLoader classloader) {
		super(wsdlLocation,classloader)
	}
	
	public Object create(String classname) {
		classloader.loadClass(classname).newInstance()
	}
}
the create Method from this class than will be used... However I get a java.lang.ClassNotFoundException Exception To be sure that cxf won't use a different I instantiate the FixedWSClient like this
def cl = new GroovyClassLoader()
client = cl.loadClass("package.FixedWSClient").newInstance(url,cl)
client.initialize()
I also tried to set the Thread.currentThread().contextClassLoader = cl Nothing helps... any Idea or explanations on this behaviour?
Hide
Sebastian Rühl added a comment - 15/Feb/10 4:19 AM

Ok here is my proposed fix...

When you look at the DynamicClientFactory you see following code:

org.apache.cxf.endpoint.dynamic.DynamicClientFactory.java
...
List srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$");
    if (!(compileJavaSrc(classPath.toString(), srcFiles, classes.toString()))) {
      LOG.log(Level.SEVERE, new Message("COULD_NOT_COMPILE_SRC", LOG, new Object[] { wsdlUrl }).toString());
    }
    FileUtils.removeDir(src);
    URLClassLoader cl;
    try {
      cl = new URLClassLoader(new URL[] { classes.toURI().toURL() }, classLoader);
    } catch (MalformedURLException mue) {
      throw new IllegalStateException("Internal error; a directory returns a malformed URL: " + mue.getMessage(), mue);
    }

    Map contextProperties = this.jaxbContextProperties;

    if (contextProperties == null)
      contextProperties = Collections.emptyMap();
    JAXBContext context;
    try
    {
      JAXBContext context;
      if (StringUtils.isEmpty(packageList))
        context = JAXBContext.newInstance(new Class[0], contextProperties);
      else
        context = JAXBContext.newInstance(packageList, cl, contextProperties);
    }
    catch (JAXBException jbe) {
      throw new IllegalStateException("Unable to create JAXBContext for generated packages: " + jbe.getMessage(), jbe);
    }

    JAXBDataBinding databinding = new JAXBDataBinding();
    databinding.setContext(context);
    svc.setDataBinding(databinding);

    ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();

    Thread.currentThread().setContextClassLoader(cl);
...

as you can see the ClassLoader given to the create Client is used as Parent for the URLClassloader, but then the classLoader will be set as the ContextClassLoader...

So to get the right behavior you need to patch following:

groovyx.net.ws.WSClient.java
public void initialize() {
        HTTPConduit conduit;
        URL url;

        url = this.url;

        this.proxyHelper.initialize();
        this.basicAuthHelper.initialize();

        final boolean isSSLProtocol = WSClient.HTTPS.equals(this.url.getProtocol());
        if (isSSLProtocol) {
            this.sslHelper.initialize();
            url = this.sslHelper.getLocalWsdlUrl(this.url);
        }

        // ClassLoading Patch
 
        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader()
        this.client = createClient(url, this.classloader);
        this.classloader = Thread.currentThread().getContextClassLoader()
        Thread.currentThread().setContextClassLoader(oldLoader)
        
        // ClassLoading Patch

        this.soapHelper.enable(this.client);
        this.proxyHelper.enable(this.client);
        this.basicAuthHelper.enable(this.client);

        if (isSSLProtocol) {
          this.sslHelper.enable(this.client);
        }

        this.mtomHelper.enable(this.client);

        conduit = (HTTPConduit) this.client.getConduit();
        configureHttpClientPolicy(conduit);
    }

additionally you need to add the following Method:

groovyx.net.ws.WSClient.java
public Object create(String classname) {
		classloader.loadClass(classname).newInstance()
	}

so now it should work

Show
Sebastian Rühl added a comment - 15/Feb/10 4:19 AM Ok here is my proposed fix... When you look at the DynamicClientFactory you see following code:
org.apache.cxf.endpoint.dynamic.DynamicClientFactory.java
...
List srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$");
    if (!(compileJavaSrc(classPath.toString(), srcFiles, classes.toString()))) {
      LOG.log(Level.SEVERE, new Message("COULD_NOT_COMPILE_SRC", LOG, new Object[] { wsdlUrl }).toString());
    }
    FileUtils.removeDir(src);
    URLClassLoader cl;
    try {
      cl = new URLClassLoader(new URL[] { classes.toURI().toURL() }, classLoader);
    } catch (MalformedURLException mue) {
      throw new IllegalStateException("Internal error; a directory returns a malformed URL: " + mue.getMessage(), mue);
    }

    Map contextProperties = this.jaxbContextProperties;

    if (contextProperties == null)
      contextProperties = Collections.emptyMap();
    JAXBContext context;
    try
    {
      JAXBContext context;
      if (StringUtils.isEmpty(packageList))
        context = JAXBContext.newInstance(new Class[0], contextProperties);
      else
        context = JAXBContext.newInstance(packageList, cl, contextProperties);
    }
    catch (JAXBException jbe) {
      throw new IllegalStateException("Unable to create JAXBContext for generated packages: " + jbe.getMessage(), jbe);
    }

    JAXBDataBinding databinding = new JAXBDataBinding();
    databinding.setContext(context);
    svc.setDataBinding(databinding);

    ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService();

    Thread.currentThread().setContextClassLoader(cl);
...
as you can see the ClassLoader given to the create Client is used as Parent for the URLClassloader, but then the classLoader will be set as the ContextClassLoader... So to get the right behavior you need to patch following:
groovyx.net.ws.WSClient.java
public void initialize() {
        HTTPConduit conduit;
        URL url;

        url = this.url;

        this.proxyHelper.initialize();
        this.basicAuthHelper.initialize();

        final boolean isSSLProtocol = WSClient.HTTPS.equals(this.url.getProtocol());
        if (isSSLProtocol) {
            this.sslHelper.initialize();
            url = this.sslHelper.getLocalWsdlUrl(this.url);
        }

        // ClassLoading Patch
 
        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader()
        this.client = createClient(url, this.classloader);
        this.classloader = Thread.currentThread().getContextClassLoader()
        Thread.currentThread().setContextClassLoader(oldLoader)
        
        // ClassLoading Patch

        this.soapHelper.enable(this.client);
        this.proxyHelper.enable(this.client);
        this.basicAuthHelper.enable(this.client);

        if (isSSLProtocol) {
          this.sslHelper.enable(this.client);
        }

        this.mtomHelper.enable(this.client);

        conduit = (HTTPConduit) this.client.getConduit();
        configureHttpClientPolicy(conduit);
    }
additionally you need to add the following Method:
groovyx.net.ws.WSClient.java
public Object create(String classname) {
		classloader.loadClass(classname).newInstance()
	}
so now it should work
Hide
Sebastian Rühl added a comment - 15/Feb/10 4:28 AM

Patch to solve that Problem...
Testing is required

Show
Sebastian Rühl added a comment - 15/Feb/10 4:28 AM Patch to solve that Problem... Testing is required
Hide
Oliver Eichhorn added a comment - 23/Apr/10 5:04 AM

The patch doesn't seem to work for me.
I still get a NullPointerException at groovyx.net.ws.AbstractCXFWSClient.create(AbstractCXFWSClient.java:164).
The new "WSClient.create()" seems to produce an exception and call create from its superclass AbstractCXFWSClient.

Are there any news in this topic?

Show
Oliver Eichhorn added a comment - 23/Apr/10 5:04 AM The patch doesn't seem to work for me. I still get a NullPointerException at groovyx.net.ws.AbstractCXFWSClient.create(AbstractCXFWSClient.java:164). The new "WSClient.create()" seems to produce an exception and call create from its superclass AbstractCXFWSClient. Are there any news in this topic?
Hide
Sebastian Rühl added a comment - 26/Apr/10 2:55 AM

Made the create Method more useful

Show
Sebastian Rühl added a comment - 26/Apr/10 2:55 AM Made the create Method more useful
Hide
Sebastian Rühl added a comment - 27/Apr/10 7:40 AM

Appended missing semicolons...
Should write more Java Code... Groovy makes lazy

Show
Sebastian Rühl added a comment - 27/Apr/10 7:40 AM Appended missing semicolons... Should write more Java Code... Groovy makes lazy
Hide
Sebastian Rühl added a comment - 29/Jun/10 8:33 AM

Is the patch now accepted or not?
Its a bit anoying since it makes this framework in most situations unusable...

Show
Sebastian Rühl added a comment - 29/Jun/10 8:33 AM Is the patch now accepted or not? Its a bit anoying since it makes this framework in most situations unusable...

People

Dates

  • Created:
    12/May/09 11:42 AM
    Updated:
    29/Jun/10 8:33 AM