chemistry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Sascha Homeier (JIRA)" <j...@apache.org>
Subject [jira] [Comment Edited] (CMIS-878) Allow loading classes from other OSGi Bundles in OSGi Client Wrapper
Date Mon, 11 May 2015 18:16:00 GMT

    [ https://issues.apache.org/jira/browse/CMIS-878?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14538292#comment-14538292
] 

Sascha Homeier edited comment on CMIS-878 at 5/11/15 6:15 PM:
--------------------------------------------------------------

It is much more straightforward than I thought.

My MANIFEST.MF now contains the following line:
{code}
Chemistry-SPI: org.apache.chemistry.opencmis.osgi.client.objectfactory.internal.MockObjectFactoryImpl
{code}

In OSGi Activator start():
{code}
public void start(BundleContext context) {
        // register the MetaTypeService now, that we are ready
        Dictionary<String, String> props = new Hashtable<String, String>();
        props.put(Constants.SERVICE_DESCRIPTION, "Apache Chemistry OpenCMIS Client Session
Factory");
        props.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");

        // try to find classloader of Chemistry OpenCMIS ObjectFactory or
        // AuthenticationProvider implementations
        Map<String, ClassLoader> chemistrySpiClassLoader = findChemistrySpiClassLoader(context);

        SessionFactoryImpl sessionFactory = SessionFactoryImpl.newInstance(chemistrySpiClassLoader);

        context.registerService(SessionFactory.class.getName(), sessionFactory, props);
 }

private Map<String, ClassLoader> findChemistrySpiClassLoader(BundleContext context)
{

        Map<String, ClassLoader> classLoaderMap = new HashMap<String, ClassLoader>();

        // get all currently loaded bundles
        // TODO: How to avoid playing with start-levels to ensure that chemistry spi bundles
are loaded first 
        Bundle[] bundles = context.getBundles();
        for (Bundle bundle : bundles) {
            
            // if chemistry spi header is set store the classloader(s) of the bundle with
key = class name
            String value = (String) bundle.getHeaders().get(CHEMISTRY_SPI_HEADER);
           if (value != null) {
                String[] split = value.split(",");
                for (String className : split) {

                    if ((className != null) && (!className.trim().isEmpty())) {
                        BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
                        ClassLoader classLoader = bundleWiring.getClassLoader();
                        classLoaderMap.put(className.trim(), classLoader);
                    }
                }
            }
        }

        return classLoaderMap;
 }
{code}

In SessionFactoryImpl:
{code}
protected SessionFactoryImpl(Map<String, ClassLoader> chemistrySpiClassLoader) {
        this.chemistrySpiClassLoader = chemistrySpiClassLoader;
}

public static SessionFactoryImpl newInstance(Map<String, ClassLoader> chemistrySpiClassLoader)
{
        return new SessionFactoryImpl(chemistrySpiClassLoader);
}

public Session createSession(Map<String, String> parameters) {
        return createSession(parameters, getObjectFactory(parameters), getAuthenticationProvider(parameters),
null,
                null);
}

// OSGi
// the following methods try to load ObjectFactory and
// AuthenticationProvider via ClassLoader injected by OSGi Activator

private ObjectFactory getObjectFactory(Map<String, String> parameters) {

        if (parameters.containsKey(SessionParameter.OBJECT_FACTORY_CLASS)) {

            String ofClassName = parameters.get(SessionParameter.OBJECT_FACTORY_CLASS);
            if ((this.chemistrySpiClassLoader != null) && (this.chemistrySpiClassLoader.containsKey(ofClassName)))
{
                ClassLoader classLoader = this.chemistrySpiClassLoader.get(ofClassName);

                Object objectFactory;
                try {
                    objectFactory = ClassLoaderUtil.loadClass(ofClassName, classLoader).newInstance();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Could not load object factory: " +
e, e);
                }

                if (!(objectFactory instanceof ObjectFactory)) {
                    throw new IllegalArgumentException("Object factory does not implement
ObjectFactory!");
                }
                return (ObjectFactory) objectFactory;
            }
        }

        return null;
 }

// same for AuthenticationProvider
...
{code}
(of course I did not remove existing no-arg newInstance()...just skipped it here for brevity)

So any vendor of an AuthenticationProvider and/or ObjectFactory now needs to do two things
inside an OSGi environment:
* Put the following line inside the Manifest: "Chemistry-SPI: <classname>"
* When creating the session pass the classname as SessionParameter

When one bundle holds both AuthenticationProvider and ObjectFactory then you need to separate
the class names by comma inside manifest.
Every ClassLoader is stored inside the SessionFactoryImpl in a Map with key = class name.

Just a rough outline to illustrate the approach.
What do you think?

(I completely removed both Caches cause it is not possible to implement them when interface
is internal)


was (Author: shomeier):
It is much more straightforward than I thought.

My MANIFEST.MF now contains the following line:
{code}
Chemistry-SPI: org.apache.chemistry.opencmis.osgi.client.objectfactory.internal.MockObjectFactoryImpl
{code}

In OSGi Activator start():
{code}
public void start(BundleContext context) {
        // register the MetaTypeService now, that we are ready
        Dictionary<String, String> props = new Hashtable<String, String>();
        props.put(Constants.SERVICE_DESCRIPTION, "Apache Chemistry OpenCMIS Client Session
Factory");
        props.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");

        // try to find classloader of Chemistry OpenCMIS ObjectFactory or
        // AuthenticationProvider implementations
        Map<String, ClassLoader> chemistrySpiClassLoader = findChemistrySpiClassLoader(context);

        SessionFactoryImpl sessionFactory = SessionFactoryImpl.newInstance(chemistrySpiClassLoader);

        context.registerService(SessionFactory.class.getName(), sessionFactory, props);
 }

private Map<String, ClassLoader> findChemistrySpiClassLoader(BundleContext context)
{

        Map<String, ClassLoader> classLoaderMap = new HashMap<String, ClassLoader>();

        // get all currently loaded bundles
        // TODO: How to avoid playing with start-levels to ensure that chemistry spi bundles
are loaded first 
        Bundle[] bundles = context.getBundles();
        for (Bundle bundle : bundles) {
            
            // if chemistry spi header is set store the classloader(s) of the bundle with
key = class name
            String value = (String) bundle.getHeaders().get(CHEMISTRY_SPI_HEADER);
            String[] split = value.split(",");
            for (String className : split) {

                if ((className != null) && (!className.trim().isEmpty())) {
                    BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
                    ClassLoader classLoader = bundleWiring.getClassLoader();
                    classLoaderMap.put(className.trim(), classLoader);
                }
            }
        }

        return classLoaderMap;
 }
{code}

In SessionFactoryImpl:
{code}
protected SessionFactoryImpl(Map<String, ClassLoader> chemistrySpiClassLoader) {
        this.chemistrySpiClassLoader = chemistrySpiClassLoader;
}

public static SessionFactoryImpl newInstance(Map<String, ClassLoader> chemistrySpiClassLoader)
{
        return new SessionFactoryImpl(chemistrySpiClassLoader);
}

public Session createSession(Map<String, String> parameters) {
        return createSession(parameters, getObjectFactory(parameters), getAuthenticationProvider(parameters),
null,
                null);
}

// OSGi
// the following methods try to load ObjectFactory and
// AuthenticationProvider via ClassLoader injected by OSGi Activator

private ObjectFactory getObjectFactory(Map<String, String> parameters) {

        if (parameters.containsKey(SessionParameter.OBJECT_FACTORY_CLASS)) {

            String ofClassName = parameters.get(SessionParameter.OBJECT_FACTORY_CLASS);
            if ((this.chemistrySpiClassLoader != null) && (this.chemistrySpiClassLoader.containsKey(ofClassName)))
{
                ClassLoader classLoader = this.chemistrySpiClassLoader.get(ofClassName);

                Object objectFactory;
                try {
                    objectFactory = ClassLoaderUtil.loadClass(ofClassName, classLoader).newInstance();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Could not load object factory: " +
e, e);
                }

                if (!(objectFactory instanceof ObjectFactory)) {
                    throw new IllegalArgumentException("Object factory does not implement
ObjectFactory!");
                }
                return (ObjectFactory) objectFactory;
            }
        }

        return null;
 }

// same for AuthenticationProvider
...
{code}
(of course I did not remove existing no-arg newInstance()...just skipped it here for brevity)

So any vendor of an AuthenticationProvider and/or ObjectFactory now needs to do two things
inside an OSGi environment:
* Put the following line inside the Manifest: "Chemistry-SPI: <classname>"
* When creating the session pass the classname as SessionParameter

When one bundle holds both AuthenticationProvider and ObjectFactory then you need to separate
the class names by comma inside manifest.
Every ClassLoader is stored inside the SessionFactoryImpl in a Map with key = class name.

Just a rough outline to illustrate the approach.
What do you think?

(I completely removed both Caches cause it is not possible to implement them when interface
is internal)

> Allow loading classes from other OSGi Bundles in OSGi Client Wrapper
> --------------------------------------------------------------------
>
>                 Key: CMIS-878
>                 URL: https://issues.apache.org/jira/browse/CMIS-878
>             Project: Chemistry
>          Issue Type: Improvement
>          Components: opencmis-client
>    Affects Versions: OpenCMIS 0.12.0
>         Environment: OSGi
>            Reporter: Sascha Homeier
>            Priority: Minor
>
> When using the OpenCMIS OSGi Client Wrapper it is hard to load classes from other bundles.
For example if you specify an own Authentication Provider class as Session Parameter then
the Wrapper will not find this class when it is located inside another bundle. Same problem
should occur when defining an own Cache.
> *1)*
> It would be nice if other bundles could register their Classloaders so that ClassLoaderUtil
can use it when trying to load classes.
> *2)*
> Another simpler option would be to simply add "DynamicImport-Package: *" to the Manifest
of the Wrapper. By doing this the Wrapper will find all classes which are exported by other
bundles. Though this approach feels more like a hack since it breaks modularity.
> What do you think about it?
> Cheers
> Sascha



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Mime
View raw message