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

Open
#709 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
azure, java, spring
Domain
api, backend, cloud

Research direction

Start with the binding path in DataOperations.java and RpcHttpRequestDataSource.java, then reproduce the issue using the supplied PersonHandler example and mvn azure-functions:run. Verify how a POJO containing LocalDate or OffsetDateTime is converted for HttpRequestMessage<Optional>; done means the request binds successfully without the reported exception.

Written by the indexing model from the issue text.

Description

area:java-functions Enhancement needs-discussion Needs: Triage (Functions) reviewed
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>


Dominant language
Java
Stars
103
Forks
74
Avg merge
1d 11h
Merged PRs (30d)
3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Azure/azure-functions-java-worker

All issues in Azure/azure-functions-java-worker

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.