martes, 24 de mayo de 2022

Gradle-7 (8) Gradle Multiproyecto (2) Shadow plugin con Apache CXF . Soluciónes al problema de ejecución- Manda Güevos!

0. Introducción

El famoso "jar hell" o infierno de los jars, se manifiesta aquí en todo su esplendor. En concreto la integración de Apache CXF ha corrido mas ríos de sangre y frustación que de tinta.

1. Shadow plugin con Apache CXF

Recordar crear el subproyecto dentro del módulo principal y añadirle la referencia al subproyecto en "settings.properties" del proyecto principal 


El settings.gradle seria:


rootProject.name = 'Multiproject'

include('lib', 

        'A000-Basic', 'A003-Excel', 'A004-PDF', 

        'B001-Model-Base', 'B003-DAO', 

        'C002-Model-Control', 'C005-Model-CSV', 

        'D001-DAO-Imp', 'D002-Sedipualba-WS-Shdw',   

        'PH01', 'PH01-Simple')


Veamos el fichero build.gradle

Hay que prestar especial atención a las líneas que hacen referencia al transformer y a las reubicaciones que son muy truculentas ("tricky")

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
plugins {
    // Apply the java-library plugin for API and implementation separation.
    id 'java-library'
    //id 'application'
    id 'com.github.johnrengelman.shadow' version '7.1.2'
}

import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer


repositories {
    jcenter()
    mavenCentral()
    maven { url "https://jitpack.io" } //@see: https://stackoverflow.com/questions/38905939/how-to-import-library-from-jitpack-io-using-gradle
}


dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'

    /***********************************************************************
    *********************** LOMBOK DEFINITION ******************************/
    compileOnly             'org.projectlombok:lombok:1.18.20'
    annotationProcessor     'org.projectlombok:lombok:1.18.20'
    testCompileOnly         'org.projectlombok:lombok:1.18.20'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
    /************************************************************************
    *********************** END LOMBOK DEFINITION ***************************/
    
    api(project(":A000-Basic"))
        
    // Apache general //
    implementation "commons-beanutils:commons-beanutils:1.9.4"
    implementation "commons-io:commons-io:2.11.0"
    implementation "org.apache.commons:commons-lang3:3.12.0"
        
    
    /*Apache CXF*/
    implementation 'org.apache.cxf:cxf-core:3.5.2'
    implementation 'org.apache.cxf:cxf-rt-features-logging:3.5.2'
    implementation 'org.apache.cxf:cxf-rt-ws-security:3.5.2' // WSS4JInterceptors, WSPasswordCallback
    implementation 'org.apache.cxf:cxf-rt-frontend-jaxws:3.5.2' //Dynamic client
    implementation 'org.apache.cxf:cxf-rt-transports-http:3.5.2'//Si no es fica,  es queda penjat !!!
    
    implementation 'com.sun.xml.ws:jaxws-ri:2.3.5' //FUNCIONA!  Quita:com.sun.tools.internal.xjc.api.XJC   
    
    /* Jackson */
    implementation "com.fasterxml.jackson.core:jackson-databind:2.13.3"
    
 } //dependencies

shadowJar {
    transform(AppendingTransformer) {
    	resource = 'META-INF/cxf/bus-extensions.txt'
    }	
    //1. Name and location of jars
    destinationDirectory = file("$rootDir/../mynewtargets") 
    archiveBaseName.set('d002-sedipualba-ws-shdw')
    archiveClassifier.set('')
    archiveVersion.set('2.1')
	//mainClassName = 'openadmin.utils.sedipualba.SedipuAlbaUtilsWSSeres'
	
    //2. Relocate (shadow) old problematic transitive dependency
    relocate 'com.sun.xml.bind.v2', 'com.sun.xmlv2.internal.bind.v2'
    relocate 'com.sun.xml'        , 'com.sun.xmlv2'    
    relocate 'javax.xml.bind'     , 'javax.xml.bindv2' 
	
	
    //4. Include sources
    from sourceSets.main.allSource
	
}//shadowJar


tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}//tasks

Veamos las líneas importantes:

5: Se importa el gradle shadow plugin.

8: Se importa el transformer, pieza clave para que se pueda empaquetar apache cxf. Ver la contestación de petternordholm 

40-44: Dependencias de Apache CXF

46-49: Dependencias que se requieren en apache CXF

54-56: Aplicación del transformer. Sin esto, da un montón de referencias nulas (Bindings...)

58-61: Se define nombre y ubicación del jar generado

66: Reubicación importante: Hay conflictos a partir de la versión 3, pues se cambia la referencia desde javax a jakarta. Por tanto hay que utilizar el shadow para que reubique estas dependencias antiguas que las llama apache cxf. (que internamente llaman a javax)

65: Volvemos atras una línea, para indicar que si se reubiva la línea 65, entonces el sistema se "inventa" una reubicación intermedia llamada "internal". Por tanto hay que tener cuidado

67: Reubicación del javax para que nlo lo machaque el jakarta

71. Incluimos las fuentes ".java" para que la depuración sea más fácil

2. Subproyecto que usa este como referencia.

Aquí la cosa tiene truco.

Para ello seguimos la entrada anterior del blog y creamos un subproyecto "PH01-Simple" que solo va a tener de dependencia al subproyecto anterior, y otro. Recordar de crear la referencia a settings.gradle del proyecto padre.


Veamos el build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
plugins {
    // Apply the java-library plugin for API and implementation separation.
    id 'java-library'
    id 'application'
}

repositories {
    jcenter()
    mavenCentral()
}

gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS

project.jar.destinationDirectory = file("$rootDir/../mynewtargets")
project.archivesBaseName = 'PH01-Simple' 
project.version = '2.0'


dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
        
    //=============LOMBOK BEGIN ============================
    // Not generated by the Vaadin starting-project
    compileOnly 'org.projectlombok:lombok:1.18.20'
    annotationProcessor 'org.projectlombok:lombok:1.18.20'
	
    testCompileOnly 'org.projectlombok:lombok:1.18.20'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
    //=============LOMBOK END ==============================
    api(project(":A000-Basic"))
implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow') } application { mainClass = 'alba.utils.AlbaEdu' } jar { duplicatesStrategy = 'exclude' from sourceSets.main.allSource //Include java sources }

Observar que:

  • No utilizamos el shadow plugin
  • Para referenciar a un proyecto hecho shadow plugin, ver la línea 32. En cambio un proyecto normal se referencia con la línea 31

Si le damos a la task de gradle application -run, parece ser que funciona 


Pero si le damos a ejecutar una clase que hace referencia al proyecto anterior (AlbaEdu) que usamos el shadow plugin. da error de ejecución y no encuentra la referencia a apache cxf.


Exception in thread "main" java.lang.NoClassDefFoundError: org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory

at openadmin.utils.sedipualba.SedipualbaUtilsOther.getClientsAndClassLoadersIRunBO(SedipualbaUtilsOther.java:270)

at alba.utils.AlbaEdu.<init>(AlbaEdu.java:32)

at alba.utils.AlbaEdu.main(AlbaEdu.java:106)

Caused by: java.lang.ClassNotFoundException: org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory

at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:827)

at java.base/jdk.internal.loader.ClassLoaders$Aimplementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')ppClassLoader.loadClass(ClassLoaders.java:188)

at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:1112)

... 3 more

3. 1ª Solución al problema

Para poder depurar esta clase en Eclipse, es importante que se pueda ejecutar. Para ello, como Eclipse no  puede encontrar las referencias al proyecto anterior (del shadow plugin), hay que indicárselo en la configuración de ejecución.

Apretar el botón derecho del ratón sobre la clase en cuestión (en mi caso AlbaEdu) y Run As ->Run Configurations o también Debug As > Debug Configurations

Le indicamos Add External Jars, y buscamos donde está el jar generado en el subproyecto anterior y lo añadimos. Y a funcionar!!!



4. 2ª Solución al problema

Es evidente que la solución anterior no es precisamente muy elegante. Parece ser que el shadow plugin y la implementación multiproyecto de eclipse no se comunican muy bien.

Otra solución poco elegante pero no tanto consiste en realizar estos pasos:

1. Generar el jar del proyecto que contiene el shadow plugin (maven lo contempla)

2. En vez de hacer referencia a este proyecto, se haría referencia al jar generado. En concreto bastaría con cambiar esta línea en el build-gradle:

implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')

por esta otra

implementation files(

        '../mynewtargets/d002-sedipualba-ws-shdw-2.1.jar',

)




Ahora me toca probar con un war!



No hay comentarios :

Publicar un comentario