[ 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)