Loading Resource in a OSGi Bundle from a different Bundle - osgi

I have two OSGi bundles A and B. Both are created with the API/implementation model. Both the bundles are activated through Blueprint mechanism. Both the API bundles export the interfaces whereas the implementation bundles don't export anything. Only A has dependency on B.
My issue is, I want to pass a resource file name (xml file) that resides in bundle A to Bundle B (after I get hold of B using the BundleContext.getServiceReferences() method) and then load the resource in Bundle B and process it. How can I achieve the same and is it even doable?
I read the following post and Neil Bartlett's answer suggest it should work.
Access resources in another osgi bundle?
However, when I read about getEntry, findEntries, getEntrypath methods, they don't talk about other bundles (or I misunderstood it). They talk only about fragments.
My question is, can I achieve what I am looking for using any of the above three methods? If so, can you please point to some other thread that has the answers or some sample code?
If this is not possible, can I use fragment bundles(containing only resources)? Again Bundle B is more like a provider where it can take any resource file and process it and satisfy clients requests (Bundle A). So even if I want to use bundle fragment approach, I need B to load the fragment bundle (only on needed basis) specified by clients (say they give the fragment bundle symbolic name). Please provide suggestions or other posts related to similar issues or any samples.

The easiest way to access a resource in a bundle is to use the classLoader of the bundle. In bundle A you get service MyService and do:
InputStream is = this.getClass().getResourceAsStream("myresource.xml");
MyService service.doSomething(is);
or if you prefer a URL:
URL url = this.getClass().getResource("myresource.xml");
MyService service.doSomething(url);
This works as we already resolve the resource before making the call. If you want to give the service just a relative path then you additionally need to give it the classLoader to get the resource from.
ClassLoader bundleALoader = this.getClass().getClassLoader();
MyService service.doSomething(bundleALoader, "myresource.xml");
Keep in mind though that this.getResource() will lookup the resource relatively to the package of "this" while classLoder.getResource() will lookup from the top of the name space.
Btw. These concepts also work fine outside of OSGi. The difference is only that in OSGi you have to make sure to use the classLoader that has visibility of a resource while outside there is often only one classLoader that sees all resources.

Related

java.util.ServiceConfigurationError Provider not a subtype while using OSGi bundle

I'm creating a Liferay 7.1 OSGi bundle, which has some external dependencies in it. In consideration of time, we opted to embed the external JAR in our OSGi Bundle. I've managed to create a bnd file, which includes all of the ElasticSearch dependencies, and put them on the bundle classpath. I've used the source-code from github (https://github.com/liferay/liferay-portal/blob/master/modules/apps/portal-search-elasticsearch6/portal-search-elasticsearch6-impl/build.gradle) and the bnd.bnd file, to check what's imported.
When activating the bundle, an exception is thrown:
The activate method has thrown an exception
java.util.ServiceConfigurationError: org.elasticsearch.common.xcontent.XContentBuilderExtension: Provider org.elasticsearch.common.xcontent.XContentElasticsearchExtension not a subtype
at java.util.ServiceLoader.fail(ServiceLoader.java:239)
at java.util.ServiceLoader.access$300(ServiceLoader.java:185)
at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:376)
at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
at org.elasticsearch.common.xcontent.XContentBuilder.<clinit>(XContentBuilder.java:118)
at org.elasticsearch.common.settings.Setting.arrayToParsableString(Setting.java:1257)
The XContentBuilderExtension is from the elasticsearch-x-content-6.5.0.jar,
the XContentElasticsearchExtension class, is included in the elasticsearch-6.5.0.jar. Both are Included Resources, and have been put on the classpath.
The Activate-method initializes a TransportClient in my other jar, hence it happens on activation ;).
Edit:
I've noticed that this error does NOT occur when installing this the first time, or when the portal restarts. So it only occurs when I uninstall and reinstall the bundle. (This is functionality I really prefer to have!). Maybe a stupid thought.. But could it be that there is some 'hanging thread'? That the bundle is not correctly installed, or that the TransportClient still is alive? I'm checking this out. Any hints are welcome!
Edit 2:
I'm fearing this is an incompatibility between SPI and OSGi? I've checked: The High Level Rest Client has the same issue. (But then with another Extension). I'm going to try the Low-Level Rest Client. This should work, as there are minimal dependencies, I'm guessing. I'm still very curious on why the incompatibility is there. I'm certainly no expert on OSGi, neither on SPI. (Time to learn new stuff!)
Seems like a case where OSGi uses your bundle to solve a dependency from another bundle, probably one that used your bundle to solve a package when the system started.
Looking at the symptoms: it does not occur when booting or restarts. Also it is not a subtype.
When OSGi uses that bundle to solve a dependency, it will keep a copy around, even when you remove it. When the bundle comes back a package that was previously used by another bundle may still be around and you can have the situation where a class used has two version of itself, from different classloaders, meaning they are not the same class and therefore, not a subtype.
Expose only the necessary to minimize the effects of this. Import only if needs importing. If you are using Liferay Gradle configuration to include the bundle inside, stop - it's a terrible way to include as it exposes a lot. If using the bnd file to include a resource and create an entry for the adicional classpath location, do not expose if not necessary. If you have several bundles using one as dependency, make sure about the version they use and if the exchange objects from the problematic class, if they do, than extra care is required.
PS: you can include attributes when exporting and/or importing in order to be more specific and avoid using packages from the wrong origin.
You can have 2 elastic search connections inside one Java app and Liferay is by default not exposing the connection that it holds.
A way around it is to rebuild the Liferay ES connector. It's not a big deal because you don't need to change the code only the OSGi descriptor to expose more services.
I did it in one POC project and worked fine. The tricky thing is to rebuild the Liferay jar but that was explained by Pettry by his google like search blog posts. https://community.liferay.com/blogs/-/blogs/creating-a-google-like-search (it is a series but it's kind of hard to navigate in the new Liferay blogs but Google will probably help) Either way it is all nicely documented here https://github.com/peerkar/liferay-gsearch
the only thing then what needs to be done is to add org.elasticsearch.* in the bnd.bnd file in the export section. You will then be able to work with the native elastic API.

class casting of runtime added classes

I'm having a problem I simply can't get my head around.
I'm creating a jsf application where I (as administrator) can upload a jar file, and that way around update the application.
Through this tutorial: http://docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html I have managed to classload the jar file. The first issue is that I can only class load as "Object" and not cast to an other class type. I get a ClassCastException.
JarClassLoader jcl=new JarClassLoader(url);
Class cl= jcl.retreiveClass(jcl.getMainClassName());
Object ob=cl.newInstance(); //I would like to make this a RouteBuilder object instead of Object
Concretely I'm using Apache Camel, where I can add routes to an existing "core" (CamelContext). What's happening is that I have the core of apache camel running in my web app, and I can then add routes runtime. It's a route I want to package as a jar and load into the app runtime (I want to develop and test the route in my local environment, and then afterwords upload it to the production application). A concrete route is just a simple java class that extends RouteBuilder. I want to upload the jar, classLoad (URLClassLoader) the main class (maybe the whole jar? - does that make sense?), and convert it to a RouteBuilder. This seems to be impossible. I have chosen to upload the jar file to a folder outside my war, so that the routes do not get erased when I restart the webapp (is this smart or should this be done in an other way?). Is that a problem regarding name spaces? Furthermore, I haven't been able to find out whether I need to traverse my entire jar file and classload ever single class inside. Any comments on that?
To me it seems like I have some fundamental misconceptions on how extending / updating a java application is done. I simply can't find any good examples/explanations on how to solve my problem and therefore I conclude that I don't get this on a conceptual level.
Could someone elaborate on how to extend a running jsf application (I guess the same issues are relevant in native java apps), explain how to upload and class load a jar at runtime, and how to cast loaded classes to other class types than Object?
Thanks
Lasse
If you are interested in loading Routes without stopping your application you could consider using an OSGi container like Karaf. Karaf provides support for Apache Camel routes out-of-the-box: http://camel.apache.org/karaf.html
All class loading is managed by the OSGi container and you just need to run some commands to update things. I am not sure if this could work with your JSF application but it worths to take a look.

How to share resources of a OSGI bundle to other bundles

How can resource of a bundle be shared with other bundle(but not just one)?
For example I don't want to duplicate the images or properties file to all bundles, instead I want to access them from a single place.
I tried to use Fragment-Host, but there is not possible to specify multiple bundles(at least I do not know how)
eg:
<!-rest of the pom-->
<instructions>
<Fragment-Host>
com.bundlehost
</Fragment-Host>
</instructions>
As Dmytro notes, you can use OSGi API methods to access the resources in any bundle. However the harder question is this: how do you know from which bundle to access these resources, and how do you know where they are located within the bundle?
If you just make assumptions or hard-code the answer, then you end up with a very brittle system and a hidden coupling between the bundles. Then when somebody deploys your bundles into an application but doesn't include the resource bundle, everything breaks. This defeats the point of modularity.
You can use Bundle.getEntryPaths() and Bundle.getEntry() to get bundle resources. To read content use URL.openStream()
You can also use normal package imports and exports to ensure the resources are on the class path of the consuming bundle. See, for example, access common property file inside bundle with osgi.

how to use a service in the class file generated using javaassist

Can i use a service in the class file which is generated using javaassist. how can i achieve that?? and as this class is created dynamically how can i register that this class is using the provided service of another class?
I've asked a similar question here.
The smallest unit of 'installation' would be a bundle. You could generate a sort of 'virtual bundle' around your class file, including a OSGI-INF/componentX.xml descriptor, and install that into OSGi.
If it changes, regenerate the bundle and update OSGi.
I didn't go down this path as I have a lot of generated code (about 4500 different scripts that all have dependencies) and I fear that I'd get into problems if I'd generate that many bundles.
I also got a tip about the Felix Dependency manager, but I haven't thoroughly checked that out yet, it might do the things we need.

Obtaining list of installed OSGI bundles at runtime

My application obtains class names from a properties file. Classes represented by these class names could reside in certain OSGI bundles unknown at advance so in order to instantiate them I firstly have to find which bundle these classes belong to. I'm thinking about getting all installed bundles from BundleContext#getBundles, this means I have to obtain reference to BundleContext in AbstractUIPlugin#start. But I'm not sure if holding reference to BundleContext is the right thing to do since it's should be used only in the start method. So I need advice from OSGI experts here about the alternatives to get list of bundles.
Any help would be greatly appreciated.
Regards,
Setya
This is not really how OSGi is intended. If a bundle has something to add to the 'global' context, you should register a service. So each bundle that has something to share can do that in its own start method.
Some other component (DS, ServiceTracker, Blueprint, something like that) can then listen to these events, and act accordingly.
This is really important, if you start manually searching through all bundles you completely lose the advantages of OSGi.
Like written before you should try to use services to achieve what you want. I guess you have an Interface with one or more implementations that should be installable at runtime. So if you control the bundles that implement the interface then simply let them install their implementation as a service by using an Activator or a blueprint context. You can use service properties to describe your implementation.
The bundles that need the implementation can then lookup the services using a service tracker or a service reference in blueprint.
If that is not possible then you can use the bundle context to obtain the running bundles and instantiate the classes but this is not how OSGi should work. You will run into classloading problems as the bundle that tries to instantiate the classes will not have direct access to them.
Your bundle gets control at start up through the bundle activator, or better, through DS. At that time it can register services with the services registry so others can find/use them.
The route your planning to go (properties that name classes) is evil since you undoubtedly will run in class loading hell. Modularity is about hiding your implementation details, the name of your implementation classes are such details.
Exposing implementation classes in properties files is really bad practice and it looses the advantage of modularity. It does not matter if another class refers to your implementation class or a property file, the problem is that the impl. class is exposed.
Unfortunately this model has become so prevalent in our industry that many developers think it is normal :-(
OSGi allows you share instances typed by interfaces in a way that allows the implementation class to only be known inside the module.

Resources