OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.87k stars 6.59k forks source link

[BUG][JAVA][OKHTTP] UnsupportedOperationException when using okhhtpclient with gzip feature enabled #3432

Open hatzlj opened 5 years ago

hatzlj commented 5 years ago
Description

When instantiating a java/okhttp based client (generated with 'useGzipFeature' enabled) with parameters for oauth authentication, the constructor throws a java.lang.UnsupportedOperationException because the generated ApiClient tries to add the GzipRequestInterceptor' to an Unmodifiable Collection of the 'OkHttpClient.

The error originates in the generated code of the ApiClient during initialization, because httpClient.interceptors().add(...) is called after httpClient = builder.build();:

private void init() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addNetworkInterceptor(getProgressInterceptor());
        builder.addInterceptor(new GzipRequestInterceptor());
        httpClient = builder.build();

        // Enable gzip request compression
        httpClient.interceptors().add(new GzipRequestInterceptor());

        verifyingSsl = true;

        json = new JSON();

        // Set default User-Agent.
        setUserAgent("OpenAPI-Generator/0.2.1-SNAPSHOT/java");

        authentications = new HashMap<String, Authentication>();
}
openapi-generator version

4.0.0

OpenAPI declaration file content or url
openapi: 3.0.2
info:
  version: 0.2.1
  title: My Api
servers:
  - url: 'https://my.domain.org'
security:
  - myAuth:
      - 'api:access'
paths:
  '/api/v1/test':
    post:
      summary: Create a new object
      description: >-
        Creates a new object
      operationId: createObject
      tags:
        - command
        - object
      requestBody:
        $ref: '#/components/requestBodies/Object.JsonApi.v1'
      responses:
        '202':
          description: 'Object is being created'
        default:
          $ref: '#/components/responses/Error.JsonApi.v1'

components:
  # ------------------------------------------------------------------------- SCHEMAS
  schemas:
    # ------------------------------------------------------------------------- BASE SCHEMAS

    Object:
      description: 'An object'
      type: object
      required:
        - type
      properties:
        id:
          type: string
          description: 'The domain object identifier'
          example: 'c24c0bfb-7a15-4f17-94e2-9b112491b882'
        type:
          type: string
          readOnly: true
          description: 'The type of the domain object'
          default: 'my.api.model.Object'

    Object.JsonApi.v1:
      description: 'Object details, JSON:API v1.0'
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/Object'

    ErrorMetadata:
      # only used in responses for now, no need to declare required properties
      description: 'Basic error object, JSON:API v1.0'
      type: object
      properties:
        code:
          type: string
          description: 'Application specific error code'
          readOnly: true
          example: 'ERR_0011'
        status:
          type: string
          description: 'The HTTP status code applicable to this problem'
          readOnly: true
          example: '400'
        detail:
          type: string
          description: 'A verbose, human readable description of the error, may be localized'
          readOnly: true
          example: 'The validation has failed due to a missing parameter.'

    # ------------------------------------------------------------------------- RESPONSES
  responses:
    Error.JsonApi.v1:
      description: Unexpected, unspecified error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorMetadata'
          example:
            errors:
              - code: 'ERR_0500'
                status: '500'
                details: "Something happened, but we cannot determine what exactly."

  # ------------------------------------------------------------------------- REQUEST BODIES
  requestBodies:
    Object.JsonApi.v1:
      description: Object
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Object.JsonApi.v1'

  # ------------------------------------------------------------------------- SECURITY SCHEMES
  securitySchemes:
    myAuth:
      type: oauth2
      flows:
        password:
          tokenUrl: /api/v1/oauth2/token
          refreshUrl: /api/v1/oauth2/token
          scopes:
            api:access: "Access My REST API"
Command line used for generation

used via the openapi-generator-maven-plugin

<plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>4.0.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <phase>process-sources</phase>
                        <configuration>
                            <verbose>false</verbose>
                            <inputSpec>minimal.yaml</inputSpec>
                            <skipValidateSpec>false</skipValidateSpec>
                            <generatorName>java</generatorName>
                            <output>${project.build.directory}/generated-sources/openapi</output>
                            <addCompileSourceRoot>true</addCompileSourceRoot>
                            <apiPackage>my.api.client</apiPackage>
                            <modelPackage>my.api.model</modelPackage>
                            <modelNamePrefix>DTO</modelNamePrefix>
                            <generateApis>true</generateApis>
                            <generateApiTests>false</generateApiTests>
                            <generateApiDocumentation>true</generateApiDocumentation>
                            <generateModels>true</generateModels>
                            <generateModelTests>false</generateModelTests>
                            <generateModelDocumentation>true</generateModelDocumentation>
                            <generateSupportingFiles>true</generateSupportingFiles>
                            <groupId>my.api</groupId>
                            <artifactId>client</artifactId>
                            <artifactVersion>${project.version}</artifactVersion>
                            <withXml>false</withXml>
                            <configOptions>
                                <library>okhttp-gson</library>
                                <title>${project.name}</title>
                                <fullJavaUtil>false</fullJavaUtil>
                                <sourceFolder>src/main/java</sourceFolder>
                                <dateLibrary>java8</dateLibrary>
                                <java8>true</java8>
                                <booleanGetterPrefix>is</booleanGetterPrefix>
                                <useBeanValidation>true</useBeanValidation>
                                <performBeanValidation>true</performBeanValidation>
                                <useGzipFeature>true</useGzipFeature>
                                <caseInsensitiveResponseHeaders>true</caseInsensitiveResponseHeaders>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
Steps to reproduce
  1. generate the client with the sample file (oauth authentication included)
  2. instantiate a client by
    String clientId = "myClientId";
    String clientSecret = "myClientSecret";
    String username = "myUsername";
    String password = "myPasswd";
    Map<String, String> params = new HashMap<>();
    params.put("username", username);
    params.put("password", password);
    ApiClient client = new ApiClient(clientId, clientSecret, params);

acutal output: java.lang.UnsupportedOperationException expected output: client is instantiated with specified parameters for OAuth

Related issues/PRs

see also https://github.com/square/okhttp/issues/2219

Suggest a fix

fix the ApiClient Mustache Template in line 147 to add the GzipRequestInterceptor before the builder is built

private void init() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addNetworkInterceptor(getProgressInterceptor());
        // Enable gzip request compression (MOVED HERE)
        builder.addInterceptor(new GzipRequestInterceptor());

        httpClient = builder.build();

       // gzip request compression MOVED FROM HERE

        verifyingSsl = true;

        json = new JSON();

        // Set default User-Agent.
        setUserAgent("OpenAPI-Generator/0.2.1-SNAPSHOT/java");

        authentications = new HashMap<String, Authentication>();
}
hatzlj commented 5 years ago

Same happens also within the constructor when adding the RetryingOauth interceptor

    public ApiClient(String clientId, String clientSecret, Map<String, String> parameters) {
        init();

        RetryingOAuth retryingOAuth = new RetryingOAuth("/api/v1/oauth2/token", clientId, OAuthFlow.password, clientSecret, parameters);
        authentications.put(
                "myAuth",
                retryingOAuth
        );
        httpClient.interceptors().add(retryingOAuth);

        // Prevent the authentications from being modified.
        authentications = Collections.unmodifiableMap(authentications);
    }

will add a separate Bug report if necessary when this one is verified.

wing328 commented 5 years ago

fix the ApiClient Mustache Template in line 147 to add the GzipRequestInterceptor before the builder is built

@hatzlj please file a PR so that we can review the fix more easily.

hatzlj commented 5 years ago

ok, i will try to do so as soon as possible

msbouchedid commented 5 years ago

Any update on this? My team currently ran into this issue and we would like a fix in

trohrberg commented 4 years ago

I provided the referenced pull request to fix this issue. Waiting for approval and merge by maintainers.