Forums de discussion

How to share service between plugins using Maven?

thumbnail
Marcus Hjortzén, modifié il y a 10 années.

How to share service between plugins using Maven?

Junior Member Publications: 45 Date d'inscription: 02/05/12 Publications récentes
Hello Community!

I'm considering the problem of sharing a plugins service between several plugins. This has been discussed several times and some of the solutions are based upon the IDE performing file copies, others that the ANT-build does similar things.

However. I'm using maven and so far I haven't seen any official post of how to do this.
Could someone point me in the correct direction?

That being said; I've of course done the obvious and declared the client project as dependent on the source project through maven-depencies and that works reasonably well.
However I've run into some problems with this approach and before opening up tickets regarding this problem I would like to be certain that my method of sharing a projects service is a supported one.

Kind regards,
Marcus
thumbnail
Bartlomiej Knabel, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Junior Member Publications: 76 Date d'inscription: 30/01/11 Publications récentes
Hi,

You can try following projects structure:

project 1 -> Service interfaces -> generates .jar -> install .jar in tomcat/lib/ext (tomcat restart required)
project 2-> Service implementation (dependency to project 1 as "provided") -> generates .war -> deploy war as normal porlet

portlet A -> dependency to project 1 as "provided"
portlet B -> dependency to project 1 as "provided"


kind regards,
Bartlomiej Knabel
thumbnail
Marcus Hjortzén, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Junior Member Publications: 45 Date d'inscription: 02/05/12 Publications récentes
Hello Bartlomiej,

your suggestion is a good one of course, that way the service classes will be loaded with a class loader common to all the projects.
However it's probably not the cleanest way to do it (and it might even prove impossible in some server environments) and in my case I do not have the privilege to access the installation directory other than the deploy directory.

//Marcus
thumbnail
M J, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Regular Member Publications: 184 Date d'inscription: 01/03/13 Publications récentes
You can skin the cat this way too:

Portlet-Project1 (Services). All database access generated using the service builder. You can share this portlet to all other portlet the following way:

Portlet Project2 Include Portlet-Project1 in liferay-plugin-package.properties file by adding "required-deployment-contexts=Portlet-Project1".

Portlet Project3 Include Portlet-Project1 in liferay-plugin-package.properties file by adding "required-deployment-contexts=Portlet-Project1".
.
.
.

and so on.

You can access Portlet-Project1 services from all your portlets this way.
thumbnail
Marcus Hjortzén, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Junior Member Publications: 45 Date d'inscription: 02/05/12 Publications récentes
Hi M J!

Correct me if I'm wrong but doesn't that method require you to use the ANT-based build (or Liferay IDE)?

//Marcus
thumbnail
M J, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Regular Member Publications: 184 Date d'inscription: 01/03/13 Publications récentes
Yes, it's based on ant build. I am sorry, I haven't tried maven build.
thumbnail
Krzysztof Gołębiowski, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 549 Date d'inscription: 25/06/11 Publications récentes
Hello Marcus,
Generally for maven builds, it should be enough to make standard compile dependency to service component of your portlet:

<dependency>
<groupId>some.group.id</groupId>
<artifactId>custom-plugin-portlet-service</artifactId>
<version>1.0</version>
</dependency>


It just includes service jar in your plugin (lib folder). Service API is static, so calling any service method will run all Liferay classloader black magic and redirect your call to other service context (check getService method of YourServiceLocalServiceUtil class for details).

I even have one non-liferay web application, and as long as it is deployed on the same Tomcat, it can use all LocalServiceUtils without any limits.

Regards,
KG
thumbnail
Marcus Hjortzén, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Junior Member Publications: 45 Date d'inscription: 02/05/12 Publications récentes
Krzysztof Gołębiowski:
Hello Marcus,
Generally for maven builds, it should be enough to make standard compile dependency to service component of your portlet:

<dependency>
<groupId>some.group.id</groupId>
<artifactId>custom-plugin-portlet-service</artifactId>
<version>1.0</version>
</dependency>


It just includes service jar in your plugin (lib folder). Service API is static, so calling any service method will run all Liferay classloader black magic and redirect your call to other service context (check getService method of YourServiceLocalServiceUtil class for details).

I even have one non-liferay web application, and as long as it is deployed on the same Tomcat, it can use all LocalServiceUtils without any limits.

Regards,
KG



Hi KG!

Very interesting, what you've described above is what I've tried also. And it works reasonably well!
However, have you tried adding a new method to the ServiceImpl-class and recompiled a couple of times?
That should change the Interface with the new method in it.

* Calling the new method from the containing plugin is no problem, works as expected.
* Calling the new method from the other plugin (the project that had the dependency) will cause an exception (UnsupportedOperationException I believe) to be raised.

This is probably due to the implementation-classes not being included and only the vanilla-service-classes. That is what got me suspecting that declaring the dependency the way I did is not the correct nor official way of sharing a service between plugins.

/Marcus
thumbnail
Krzysztof Gołębiowski, modifié il y a 10 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 549 Date d'inscription: 25/06/11 Publications récentes
Marcus,
The general problem with maven and Liferay is that "prefered" build environment is Plugins SDK based on Apache Ant. All official plugins are built with plugins SDK so there are no "good practices" available (except from Mika Koivisto's blog, who is an author of maven support). That's why there are lot of people doing the same thing in different ways. In my opinion, including a "compile" dependency to service jar is the most "like Plugins SDK" option.

As far as I remember, invoking build-service -> clean install on service and clean install on all dependent plugins done its job well (and of course redeployment). I don't remember we had any problems with UnsupportedOperationException.

The last DEVCON in Berlin I even managed to speak about that with Raymond Augé, one of core Liferay developers. He told me that maven is generally horrible and awful emoticon and they are never going to migrate from Ant to Maven (maybe Gradle in future).

Regards,
KG
Pierre Roukens, modifié il y a 9 années.

RE: How to share service between plugins using Maven?

New Member Publications: 14 Date d'inscription: 19/11/13 Publications récentes
Hi Krzysztof and forum,

I've tried to share my service jar this way with Maven, but I get an error on deployment "Failed to copy file for artifact", access denied. The service jar is at xxx-portlet-service\target but the error says it tries to to copy the jar from xxx-portlet-service\target\classes ?

Using Liferay 6.2 GA2 with m2e-liferay 2.1.1. Setup the projects (service builder portlet) with the Liferay Plugin Project wizard (twice) and added dependency in project A pom.xml to xxx-portlet-service artifact of project B.

Any suggestions (anybody)?

Regards,
Pierre
Jan Tošovský, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 566 Date d'inscription: 22/07/10 Publications récentes
I also use compile dependency. I usualy only add new methods to the already linked services. If any portlet consumes them, only those portlets are rebuilt and redeployed. The rest can operate with older version of service.jar without problems.

Jan
Ingo Müller, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

New Member Envoyer: 1 Date d'inscription: 25/10/12 Publications récentes
RE:
Ostap Antonevych, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

New Member Publications: 2 Date d'inscription: 20/08/13 Publications récentes
Krzysztof Gołębiowski:

Generally for maven builds, it should be enough to make standard compile dependency to service component of your portlet:

<dependency>
<groupId>some.group.id</groupId>
<artifactId>custom-plugin-portlet-service</artifactId>
<version>1.0</version>
</dependency>


It just includes service jar in your plugin (lib folder). Service API is static, so calling any service method will run all Liferay classloader black magic and redirect your call to other service context (check getService method of YourServiceLocalServiceUtil class for details).

I even have one non-liferay web application, and as long as it is deployed on the same Tomcat, it can use all LocalServiceUtils without any limits.

Hi,
this does not works for me in 6.2 EE if I use some custom classes from custom-plugin-portlet-service.jar as method parameters in the service interface (parameters referenced in *ServiceUtil). I get then
java.lang.ClassCastException: some.class.defined.in.custom-plugin-portlet-service.jar.SpecificClassName cannot be cast to some.class.defined.in.custom-plugin-portlet-service.jar.SpecificClassName
(with the same class on both sides of "cannot be cast to")
My explanation of that is (at least for tomcat) that the intialization of the static *ServiceUtil classes occurs independently in each tomcat webapplication using the specific for this tomcat webapplication classloader. That classloader search for the necessary classes in the specific web application WEB-INF/lib folder first and finds them there in the local version of custom-plugin-portlet-service.jar. So each plugin obtains its own version of some.class.defined.in.custom-plugin-portlet-service.jar.SpecificClassName and Class.isInstance(Object obj) method returns false if isInstanceOf is checked for them.

I have solved this problem by ensuring that the jar with some.class.defined.in.custom-plugin-portlet-service.jar.SpecificClassName is solely visible by the global class loader (placed in lib/ext). Then in the scenario described above the first tomcat webapplication tryes to initialize the static *ServiceUtil classes, figures out that the definition of the some.class.defined.in.custom-plugin-portlet-service.jar.SpecificClassName is missing and asks the parent class loader which uses the definition in lib/ext and "caches" it. The next tomcat webapplication goes the same way, but the parent classloader returs the "cached" in the previous step version of the class so that both plugins reference the same version and isInstanceOf works for both of them.

This solution is not very clean because we place the definition of the business objects into lib/ext but I do not have any idea how to solve this problem without some kind of serialization. If some kind of "Liferay classloader black magic"emoticon rsolves the problem it would be interesting to get it explained by someone who knows how the black magic works emoticon
Jan Tošovský, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 566 Date d'inscription: 22/07/10 Publications récentes
Dear Ostap,

class cast exception means that the mentioned class has been loaded by two different classloaders. So you most likely have another copy of your-service.jar somewhere on the classpath.

Please avoid putting your-service.jar into global classpath. You'll hit another problems I successfully got rid of using direct copies of your-service.jar in every particular consumer portlet. In this case, however, you cannot use your-service methods in hook. Those cases can be solved by reflection though.

Jan
Jan Tošovský, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 566 Date d'inscription: 22/07/10 Publications récentes
Jan Tošovský:
... you most likely have another copy of your-service.jar somewhere on the classpath.


Just a note. When hot-deploying the portlet, the target folder is updated, but not synchronized, i.e. some obsolete files can be left untouched. In case of testing various approaches where to place your-service.jar it may lead to incorrect conclusions as originally included your-service.jar later removed from your project is still available in the target portlet folder. When another copy is then placed also to the global classpath, it will cause clashes. During testing it is better to unistall the portlet first (or delete its folder from webapps :-).
thumbnail
Andrew Jardine, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Legend Publications: 2416 Date d'inscription: 22/12/10 Publications récentes
+1 on this. I've been cursed by this more than once. There is also a handy property that I tend to use in my development environment that will auto undeploy a plugin before deploying it. By default it is set to false, but flipping it to true save the step of having to delete the plugin. I use this often in my development environment.


    #
    # Set this to true to undeploy a plugin before deploying a new version. This
    # property will only be used if the property "hot.undeploy.enabled" is set
    # to true.
    #
    hot.undeploy.on.redeploy=true
Ostap Antonevych, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

New Member Publications: 2 Date d'inscription: 20/08/13 Publications récentes
Jan Tošovský:

So you most likely have another copy of your-service.jar somewhere on the classpath.
...
in every particular consumer portlet.
Jan

Dear Jan,
it is correct, I have many copies of "your-service.jar", but they meet each other only due to the Liferay magic mentioned above, but I thing I must explain the starting point more precisely.
In my previous post i used the term tomcat webapplication instead of portlet or Liferay plugin, so i will occasionally continue use " tomcat webapplication" because it helps the understanding the classloader hierarhy in this case.

Problem Description:
The my-services-portlet-service.jar will be added due to the compile-dependencies into WEB-INF/lib of each portlet which uses the services. For the case of simplicity we can consider the situation where there are two tomcat webapplications:
  • my-services-portlet.war (contains services created by service builder)
  • any-portlet.war (uses the services implemented in my-services-portlet.war

both tomcat webapplications must contain my-services-portlet-service.jar in WEB-INF/lib because there are compile-time dependencies on business entity (let say BE.class) that is used in the *ServiceUtil singleton in my-services-portlet-service.jar. And exactly this business entity BE.class figures in the ClassCastException. emoticon

My assumption about the problem cause
As I said my assumption is that the *ServiceUtil in any-portlet.war is initialized by the *ServiceImpl from my-services-portlet.war and at this moment the BE.class defined in my-services-portlet-service.jar lying in my-services-portlet.war is used. This is correct BE.class instantiation from my-services-portlet.war point of view: all classes in my-services-portlet.war will use the same class loader and therefore there will be no ClassCastExceptions concerning BE.class. But the problem is, that the point of view is wrong, because the *ServiceUtil initialisation occurs inside of any-portlet.war and all classes in any-portlet.war would expect, that the BE.class is instantiated from my-services-portlet-service.jar lying in any-portlet.war! So this is the cause of ClastCastException and this is what I understand under Liferay black magic, because usually one tomcat web application does not see the classes from the another one.
Now, the any-portlet creates the Instance of BE.class (using its own my-services-portlet-service.jar, not the one from my-services-portlet.war) and if it then tries to pass the business entity into his *ServiceUtil we get the ClassCastException.

dirty sollution that works
the single solution I found is to put my-services-portlet-service.jar into lib/ext and use the "provided"- maven dependency instead of maven compile-dependency in each portlet. This solution works (all tomcat web application use the same *ServiceUtil singleton and the business entity is only in global classpath, but it is "dirty".

P.S. what is still not clear to me is the Liferay initalization magic: how manages Liferay to initalize the *ServiceUtil in one tomcat web application with the *ServiceImplclasses from another tomcat webb application? They have different class loaders that "does not see" each other (they only have the same parent (global class loader).
thumbnail
Andrew Jardine, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Legend Publications: 2416 Date d'inscription: 22/12/10 Publications récentes

P.S. what is still not clear to me is the Liferay initalization magic: how manages Liferay to initalize the *ServiceUtil in one tomcat web application with the *ServiceImplclasses from another tomcat webb application? They have different class loaders that "does not see" each other (they only have the same parent (global class loader).


That is what is referred to as "Class Loader Magic" emoticon. The magic is debuggable though. Have a look at the classes generated for the service jar. There are a few with the post fix Clp (Class Loader Proxy). What you will find if you dig deep enough is that the internal service bus is part of the equation and there is a who messaging process with serialization/deserialization of objects that makes this magic happen.
Jan Tošovský, modifié il y a 8 années.

RE: How to share service between plugins using Maven?

Liferay Master Publications: 566 Date d'inscription: 22/07/10 Publications récentes
I would recommend undeploy your portlets, shutdown tomcat, delete its temp folder and for sure search for your-service.jar file in your server file system. Isn't it accidentally also in ROOT\WEB-INF\lib? If that exception still persist after redeploying portlets, let us know.

Btw, what you mean by that ServiceUtil 'singleton'? A real Java singleton?