2

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

Miroslav Ligas
  • 1,287
  • 8
  • 22
Kornelito Benito
  • 1,067
  • 1
  • 17
  • 38
  • Question: why do you embed code in your own bundle which is already available within liferay? see https://i.imgur.com/Rra4dAa.png – Daniele Baggio Feb 04 '19 at 13:09
  • Because my external dependency uses ElasticSearch natively. Liferay does not export the elastic packages. The elastic packages are not available through OSGi. But correct me if I'm wrong. :) – Kornelito Benito Feb 04 '19 at 13:13
  • I'm not a osgi master, but why not to take the existing portal-search-elasticsearch6-impl bundle, make a copy , change bdn.bdn and export what you want, give it a the new version and deploy the jar... ? – Daniele Baggio Feb 04 '19 at 13:58
  • Well, thats exactly what I'm not willing to do. Touch Liferay's internal bundles to fix mine. That's really really bad practice, and I advise you to never do this. In your case, whenever the bundle would be updated, you should manually update the classes. And apart from the Elastic Dependency, our bundle has 0 similarities with the existing portal-search-elasticsearch6-impl.The whole point of OSGi is that bundles has their own classpath, and that it could be possible to even have multiple versions deployed of the same dependency. I'm not going to misuse an existing bundle for my own... – Kornelito Benito Feb 04 '19 at 14:15
  • Ok Kornelito, it's clear – Daniele Baggio Feb 06 '19 at 11:19

2 Answers2

2

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.

Victor
  • 3,520
  • 3
  • 38
  • 58
1

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.

Miroslav Ligas
  • 1,287
  • 8
  • 22
  • That's almost the same answer as Daniele Baggio in the comments-section. I'm aware this is possible, but I would prefer to keep changes in my own code. I'm not willing to rebuild the default Liferay ES Connector. Every change they make, I would be responsible to rebuild that module. By the way, redeploying that connector, also needs a system restart, and according to me It has something to do with the SPI & OSGi incompatibility. I added bounty because I wanted more information on WHY it does not work as I have implemented this for the moment. Why does that exception happen? – Kornelito Benito Feb 07 '19 at 19:21
  • I think the problem is more related to the ES connector then Liferay or OSGi although I was also surprised when this was not working last time when I played with it. The problem with the old client was in the way how ES is connecting to the cluster. But I would expect the HLRC to me more disconnected from the actual ES server. Did you looked into the source code of the ES? – Miroslav Ligas Feb 09 '19 at 17:18
  • You are most certainly right. I noticed the Liferay connector has this problem too. Whenever a redeploy of the connector happens, I got a log message that the connector will be installed on next startup. (Probably to get around those issues.). The HLRC package also contains some SPI's. And let that exactly be the problem. – Kornelito Benito Feb 11 '19 at 07:10