ciscoo / cxf-codegen-gradle

Gradle plugin to generate Java artifacts from WSDL
Apache License 2.0
28 stars 6 forks source link

Improve documentation for wsdlLocation option #73

Closed mdiskin closed 1 year ago

mdiskin commented 1 year ago

By default cxf codegen puts the absolute build path in the generated class

e.g. @WebServiceClient(name = "tcnService", wsdlLocation = "file:/builds/arctravel/current-state/atpco-tcn/src/main/resources/wsdl/tcn.wsdl",

while can lead to runtime issues: Caused by: javax.wsdl.WSDLException: WSDLException: faultCode=PARSER_ERROR: Problem parsing 'file:/builds/arctravel/current-state/atpco-tcn/src/main/resources/wsdl/tcn.wsdl'.: java.io.FileNotFoundException: /builds/arctravel/current-state/atpco-tcn/src/main/resources/wsdl/tcn.wsdl (No such file or directory)

Fix (which should be atleast noted in documentation to help others not struggle) is to set the wsdlLocation to the classpath: location, however is there any way this can be handled in the plugin I feel this would be a great addition,

tasks.register("tcnWSDL",Wsdl2Java){ toolOptions{ wsdl.set(file("src/main/resources/wsdl/tcn.wsdl")) wsdlLocation.set("classpath:wsdl/tcn.wsdl") xjcArgs.add("-Xts") } }

ciscoo commented 1 year ago

It would have to be a documentation note somewhere since this plugin does not participate in the actual code generation process that the wsdl2java tool does.

anderso commented 10 months ago

I was also affected by this issue, using the configuration from the guide at

https://ciscoo.github.io/cxf-codegen-gradle/docs/current/user-guide/#minimal-usage

will generate source code with a an absolute path to your project in the filesystem, which I believe no one actually wants and it creates code which builds and works on your machine but not if you try it somewhere else.

It would be nice to have a separate configuration option for the common usecase that you want to use a wsdl-file from a classpath resource. Or maybe even do like https://github.com/bjornvester/xjc-gradle-plugin so that by default it would use any wsdl-file found in src/main/resources, providing a zero-configuration option. This could even be backwards compatible if it is used as a fallback if none of the wsdl* options is set.

ciscoo commented 10 months ago

@anderso can you elaborate more as to why one would need the generated code to use classpath?

I'm not familiar with this particular use case and primarily only familiar with generating code from WSDL to consume an external SOAP service of some sort.

anderso commented 9 months ago

@ciscoo It's the same use case. If you look at the original post you can see that wsdlLocation in the generated source for the client will be set to the value as provided to toolOptions.wsdl. If you then instantiate the service without specifying the wsdl location in the constructor then this same value will be used, and in case that file path is not valid on the machine you are running it on, it will fail.

But if you provide a url instead, it will probably work. But there are many reasons you might not want to use a url: builds will fail if the service is down, builds will fail if there is no internet connection, builds are not reproducible. Same problem if you supply absolute filesystem path, the build will be different depending on the location of the checkout, and it will fail later when the client is instantiated, but only if the checkout is no longer there. Maybe the reason you have not experienced it is because you don't instantiate the service without supplying the wsdl?

Anyway, having the wsdl on the classpath solves these issues.

ciscoo commented 9 months ago

Thanks for the detailed info @anderso. I get your point specifically on reproducible builds. As a look at the generated code from a example service, I can see that my filesystem is embedded within the generated code.

Maybe the reason you have not experienced it is because you don't instantiate the service without supplying the wsdl?

I think this is it, see below.

I also think because I'm not using 'pure'/plain Java EE/Jakarta constructs, but rather some specific Apache CXF functionality.


Using this service as an example http://www.dneonline.com/calculator.asmx and the WSDL located at the project root.

Here is how I typically construct a service given a WSDL:

Gradle Contents ```kotlin import io.mateo.cxf.codegen.wsdl2java.Wsdl2Java import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { java id("org.springframework.boot") version "3.2.0" id("io.mateo.cxf-codegen") version "2.2.0" } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } repositories { mavenCentral() } cxfCodegen { cxfVersion = libs.versions.cxf } dependencies { implementation(platform(SpringBootPlugin.BOM_COORDINATES)) implementation(platform(libs.cxfBom)) implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.apache.cxf:cxf-rt-transports-http") implementation("org.apache.cxf:cxf-rt-frontend-jaxws") } tasks.register("calculator") { toolOptions { wsdl = layout.projectDirectory.file("calculator.wsdl").asFile.toPath().toAbsolutePath().toString() } } ```

And a small Spring Boot example:

package io.mateo;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.tempuri.CalculatorSoap;

@SpringBootApplication
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }

    @Bean
    public CalculatorSoap calculator() {
        JaxWsProxyFactoryBean jaxWsClientFactoryBean = new JaxWsProxyFactoryBean();
        jaxWsClientFactoryBean.setAddress("http://www.dneonline.com/calculator.asmx");
        jaxWsClientFactoryBean.setServiceClass(CalculatorSoap.class);
        return (CalculatorSoap) jaxWsClientFactoryBean.create();
    }

    @EventListener(ApplicationReadyEvent.class)
    public void testCalculator(ApplicationReadyEvent event) {
        var calculator = event.getApplicationContext().getBean(CalculatorSoap.class);
        System.out.println(calculator.add(1, 1));
        System.out.println(calculator.multiply(2, 4));
    }

}

Yields the following printed to stdout:

2023-12-18T18:19:33.840-06:00  INFO 57082 --- [           main] io.mateo.ExampleApplication              : Starting ExampleApplication using Java 21.0.1 with PID 57082 (/Users/cisco/code/wsdl2java-issue/build/classes/java/main started by cisco in /Users/cisco/code/wsdl2java-issue)
2023-12-18T18:19:33.841-06:00  INFO 57082 --- [           main] io.mateo.ExampleApplication              : No active profile set, falling back to 1 default profile: "default"
2023-12-18T18:19:34.136-06:00  INFO 57082 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2023-12-18T18:19:34.140-06:00  INFO 57082 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-12-18T18:19:34.140-06:00  INFO 57082 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.16]
2023-12-18T18:19:34.156-06:00  INFO 57082 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-12-18T18:19:34.157-06:00  INFO 57082 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 300 ms
2023-12-18T18:19:34.219-06:00  INFO 57082 --- [           main] o.a.c.w.s.f.ReflectionServiceFactoryBean : Creating Service {http://tempuri.org/}CalculatorSoapService from class org.tempuri.CalculatorSoap
2023-12-18T18:19:34.461-06:00  INFO 57082 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
2023-12-18T18:19:34.465-06:00  INFO 57082 --- [           main] io.mateo.ExampleApplication              : Started ExampleApplication in 0.746 seconds (process running for 0.988)
2
8

Last two lines are calls to the service which worked without any issues related to wsdlLocation

I think I'd need a concrete example that demonstrates the issue pertaining to wsdlLocaltion since as you said, given the above arrangement, is not something I experience issues with.

I'm not against adding something to this plugin to account for that scenario, I require a working example otherwise I'm shooting/developing in the dark.