Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for LocalDate (or OffsetDateTime, etc.) in HttpRequestMessage<Optional<T>> in Functions with Spring Cloud #709

Open
noopliez opened this issue Mar 16, 2023 · 2 comments

Comments

@noopliez
Copy link

noopliez commented Mar 16, 2023

Investigative information

Please provide the following:

  • Timestamp: 2023-03-16T10:26:35.390Z
  • Function App name: local development
  • Function name(s) (as appropriate): local development
  • Invocation ID: local development
  • Region: local development

Repro steps

Provide the steps required to reproduce the problem:

  1. Define any POJO with a LocalDate or OffsetDateTime property, e.g. User
  2. Use the POJO as request body for your HTTP call, e.g. HttpRequestMessage<Optional<User>>
  3. Build & run your Application (mvn azure-functions:run)
  4. Call your function and define the request body as defined in the POJO

Expected behavior

Provide a description of the expected behavior.

After Step 4 I expect to have called the function successfully

Actual behavior

Provide a description of the actual behavior observed.

Step 4 won't work because I always get the same error message as follows:

[2023-03-16T10:26:35.390Z] [WARNING] {CheckedBiFunction.tryApply}: InaccessibleObjectException: Unable to make field private final int java.time.LocalDate.year accessible: module java.base does not "opens java.time" to unnamed module @2b05039f
[2023-03-16T10:26:35.447Z] Executed 'Functions.person' (Failed, Id=c4d45e9c-b0df-49ab-9051-004d3e8a384b, Duration=288ms)
[2023-03-16T10:26:35.450Z] System.Private.CoreLib: Exception while executing function: Functions.person. System.Private.CoreLib: Result: Failure
Exception: ClassCastException: Cannot convert com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource@3a19100ato type com.microsoft.azure.functions.HttpRequestMessage<java.util.Optional<com.example.models.User>>
Stack: java.lang.ClassCastException: Cannot convert com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource@3a19100ato type com.microsoft.azure.functions.HttpRequestMessage<java.util.Optional<com.example.models.User>>
[2023-03-16T10:26:35.455Z]      at com.microsoft.azure.functions.worker.binding.DataOperations.generalAssignment(DataOperations.java:191)
[2023-03-16T10:26:35.460Z]      at com.microsoft.azure.functions.worker.binding.DataOperations.apply(DataOperations.java:120)
[2023-03-16T10:26:35.462Z]      at com.microsoft.azure.functions.worker.binding.DataSource.computeByType(DataSource.java:56)
[2023-03-16T10:26:35.470Z]      at com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource.computeByType(RpcHttpRequestDataSource.java:20)
[2023-03-16T10:26:35.477Z]      at com.microsoft.azure.functions.worker.binding.DataSource.computeByName(DataSource.java:42)
[2023-03-16T10:26:35.479Z]      at com.microsoft.azure.functions.worker.binding.RpcHttpRequestDataSource.computeByName(RpcHttpRequestDataSource.java:20)
[2023-03-16T10:26:35.496Z]      at com.microsoft.azure.functions.worker.binding.BindingDataStore.getDataByName(BindingDataStore.java:59)
[2023-03-16T10:26:35.505Z]      at com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource.getBindingData(ExecutionContextDataSource.java:176)
[2023-03-16T10:26:35.511Z]      at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolve(ParameterResolver.java:44)
[2023-03-16T10:26:35.518Z]      at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolveArguments(ParameterResolver.java:22)
[2023-03-16T10:26:35.525Z]      at com.microsoft.azure.functions.worker.broker.EnhancedJavaMethodExecutorImpl.execute(EnhancedJavaMethodExecutorImpl.java:20)
[2023-03-16T10:26:35.528Z]      at com.microsoft.azure.functions.worker.chain.FunctionExecutionMiddleware.invoke(FunctionExecutionMiddleware.java:19)
[2023-03-16T10:26:35.552Z]      at com.microsoft.azure.functions.worker.chain.InvocationChain.doNext(InvocationChain.java:21)
[2023-03-16T10:26:35.561Z]      at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:125)
[2023-03-16T10:26:35.566Z]      at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:37)
[2023-03-16T10:26:35.572Z]      at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:12)
[2023-03-16T10:26:35.580Z]      at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:44)
[2023-03-16T10:26:35.587Z]      at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:93)
[2023-03-16T10:26:35.589Z]      at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
[2023-03-16T10:26:35.603Z]      at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[2023-03-16T10:26:35.615Z]      at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
[2023-03-16T10:26:35.627Z]      at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
[2023-03-16T10:26:35.629Z]      at java.base/java.lang.Thread.run(Thread.java:833)

Known workarounds

Provide a description of any known workarounds.

The current workaround would be to use a String instead of the immutable date objects. But that would not be suffice, because in our project we want to work with generated models that uses the ISO datetime.

Another workaround would be that we use Java.util.Date when generating our models, but I don't really like the idea of using that.

Related information

Provide any related information

  • Programming language used: Java 17
  • Bindings used: EventHubBinding, HttpTrigger
Source
<!-- PersonHandler.java -->

@Component
@RequiredArgsConstructor
public class PersonHandler {

  private final Person person;

  @FunctionName("person")
  public HttpResponseMessage execute(
      @HttpTrigger(
          name = "request",
          methods = {HttpMethod.POST, HttpMethod.DELETE},
          authLevel = AuthorizationLevel.ANONYMOUS
      ) HttpRequestMessage<Optional<User>> request,
      @EventHubOutput(
        <!-- not relevant here -->
      ) OutputBinding<byte[]> output,
      ExecutionContext context) {

    context.getLogger().info("Hello there, General Kenobi");

    final User user= request.getBody().orElse(null);

    if (user == null) {
      return request
          .createResponseBuilder(HttpStatus.BAD_REQUEST)
          .body("Please do it right")
          .build();
    } else {
      final CloudEvent event = person.apply(user);

      final byte[] serializedEvent = Objects.requireNonNull(EventFormatProvider
              .getInstance()
              .resolveFormat(JsonFormat.CONTENT_TYPE))
          .serialize(event);

      output.setValue(serializedEvent);

      return request
          .createResponseBuilder(HttpStatus.OK)
          .body("Sucess!")
          .build();
    }
  }
}

<!-- Person.java -->

@Component
@RequiredArgsConstructor
public class Person implements Function<User, CloudEvent> {

  @Override
  public CloudEvent apply(User user) {
    try {
      final ObjectMapper objectMapper = new ObjectMapper();
      final byte[] serializedPojo = objectMapper.writeValueAsBytes(user);

      final LocalDateTime now = LocalDateTime.now();
      final ZoneId zone = ZoneId.of("Europe/Berlin");
      final ZoneOffset zoneOffset = zone.getRules().getOffset(now);

      return CloudEventBuilder.v1()
          .withId(UUID.randomUUID().toString())
          .withSource(URI.create("/some/uri"))
          .withType("TYPE")
          .withDataContentType("application/json")
          .withSubject("SUBJECT")
          .withTime(OffsetDateTime.of(now, zoneOffset))
          .withData(serializedPojo)
          .build();
    } catch (JsonProcessingException e) {
      return null;
    }
  }
}

<!-- pom.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>beep</artifactId>
    <version>0.0.1-${revision}</version>
    <name>com.example.beep</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <description>BeepFunction</description>

    <properties>
        <revision>local</revision>
        <java.version>17</java.version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>

        <azure.functions.maven.plugin.version>1.24.0</azure.functions.maven.plugin.version>
        <azure.functions.java.library.version>3.0.0</azure.functions.java.library.version>
        <spring.cloud.function.dependencies>4.0.0</spring.cloud.function.dependencies>
        <cloudevents.version>2.4.2</cloudevents.version>
        <lombok.version>1.18.26</lombok.version>

        <!-- customize those two properties. The functionAppName should be unique across Azure -->
        <functionResourceGroup>not relevant</functionResourceGroup>
        <functionAppServicePlanName>not relevant</functionAppServicePlanName>
        <functionAppName>not relevant</functionAppName>

        <functionAppRegion>westeurope</functionAppRegion>
        <functionAppPricingTier>EP1</functionAppPricingTier>
        <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>

        <start-class>com.example.BeepApplication</start-class>
    </properties>

    <dependencies>
        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-azure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-function-webflux</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- Sonstiges -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.cloudevents</groupId>
            <artifactId>cloudevents-core</artifactId>
            <version>${cloudevents.version}</version>
        </dependency>
        <dependency>
            <groupId>io.cloudevents</groupId>
            <artifactId>cloudevents-json-jackson</artifactId>
            <version>${cloudevents.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.4</version>
        </dependency>
        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>2.2.8</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>3.0.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api -->
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.23.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.24.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-function-dependencies</artifactId>
                <version>${spring.cloud.function.dependencies}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.microsoft.azure.functions</groupId>
                <artifactId>azure-functions-java-library</artifactId>
                <version>${azure.functions.java.library.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>com.microsoft.azure</groupId>
                    <artifactId>azure-functions-maven-plugin</artifactId>
                    <version>${azure.functions.maven.plugin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.3.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>3.4.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-clean-plugin</artifactId>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>6.3.0</version>
                <executions>
                    <execution>
                        <id>generate-contract</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>${project.basedir}/src/main/resources/some-spec.yaml</inputSpec>
                            <generatorName>spring</generatorName>
                            <configOptions>
                                <interfaceOnly>true</interfaceOnly>
                                <useJakartaEe>true</useJakartaEe>
                                <useSpringBoot3>true</useSpringBoot3>
                                <!--suppress UnresolvedMavenProperty -->
                                <additionalModelTypeAnnotations>
                                    @lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor
                                </additionalModelTypeAnnotations>
                            </configOptions>
                            <generateApis>false</generateApis>
                            <generateSupportingFiles>false</generateSupportingFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.microsoft.azure</groupId>
                <artifactId>azure-functions-maven-plugin</artifactId>
                <configuration>
                    <resourceGroup>${functionResourceGroup}</resourceGroup>
                    <appName>${functionAppName}</appName>
                    <region>${functionAppRegion}</region>
                    <appServicePlanName>${functionAppServicePlanName}</appServicePlanName>
                    <pricingTier>${functionAppPricingTier}</pricingTier>

                    <hostJson>${project.basedir}/src/main/azure/host.json</hostJson>
                    <localSettingsJson>${project.basedir}/src/main/azure/local.settings.json</localSettingsJson>

                    <runtime>
                        <os>linux</os>
                        <javaVersion>17</javaVersion>
                    </runtime>
                    <appSettings>
                        <!-- Run Azure Function from package file by default -->
                        <property>
                            <name>FUNCTIONS_EXTENSION_VERSION</name>
                            <value>~4</value>
                        </property>
                    </appSettings>
                </configuration>
                <executions>
                    <execution>
                        <id>package-functions</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!--Remove obj folder generated by .NET SDK in maven clean-->
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>obj</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
@CrawX
Copy link

CrawX commented Jul 7, 2023

Could this be related to #424?

@noopliez
Copy link
Author

@CrawX I'm not sure if it fits for our Problem, but it sure would be nice to configure the serializer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants