aws / aws-sdk-java-v2

The official AWS SDK for Java - Version 2
Apache License 2.0
2.16k stars 833 forks source link

dynamodb-enhanced cannot be used with GraalVM 21.1 #2445

Open Aleksandr-Filichkin opened 3 years ago

Aleksandr-Filichkin commented 3 years ago

Hi,

I'm trying to compile dynamodb-enhanced client with Graalvm 21.1 and awssdk.version=2.16.50 Here the code: DynamoDbTable<Book> dynamoDbTable = dynamoDbEnhancedClient.table(TABLE_NAME, TableSchema.fromBean(Book.class));

Reflection config doesn't help. I generated native-image artifacts with -agentlib: but it still doesn't help. GraalVM 21.1 supports Method Handler, I hope it can be fixed. https://github.com/aws/aws-sdk-java-v2/blob/master/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/mapper/LambdaToMethodBridgeBuilder.java#L76

Error: Exception in thread "main" java.lang.ExceptionInInitializerError at com.oracle.svm.core.classinitialization.ClassInitializationInfo.initialize(ClassInitializationInfo.java:315) Caused by: java.lang.IllegalArgumentException: Failed to generate method handle. at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.LambdaToMethodBridgeBuilder.build(LambdaToMethodBridgeBuilder.java:92) at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ObjectConstructor.create(ObjectConstructor.java:37) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.newObjectSupplierForClass(BeanTableSchema.java:361) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:172) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:129) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:121) at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(TableSchema.java:81) at com.filichkin.blog.lambda.v3.handler.test.Main.initDispatcher(Main.java:39)

No problem with simple dynamoDb client, it can be compiled with GraalVM

zoewangg commented 3 years ago

Hi @Aleksandr-Filichkin thanks for reporting the issue. We will investigate to see if we can fix it for BeanTableSchema. I should note that StaticTableSchema might work better for GraalVM native image since it doesn't involve any reflections.

Test code: https://github.com/aws/aws-sdk-java-v2/blob/8763931a0fe92081f7ddfc7f6d18213da55c92fd/test/sdk-native-image-test/src/main/java/software/amazon/awssdk/nativeimagetest/DynamoDbEnhancedClientTestRunner.java#L39-L62

Aleksandr-Filichkin commented 3 years ago

Thank you @zoewangg ! An appropriate issue was created for GraalVM https://github.com/oracle/graal/issues/3386

Nithanim commented 3 years ago

Hello! I am currently looking to make dynamodb-enhanced (specifically the BeanTableSchema) compatible with graalvm native image and quarkus.

I looked into this issue and found out that the error message is misleading. The problem has nothing to do with MethodHandles (they are fully supported) but rather with using the LambdaMetafactory. Behind the scenes it creates a lambda class which is then attempted to be loaded but loading a class is not possible because everything in a native image is pre-compiled.

My solution to this problem currently is to substitute the use of the LambdaMetafactory for the native-image with a MethodHandle and an inner class which seems to work fine (minus the likely performance hit).

While trying to fix this particular problem I stumbled upon another issue with the LambdaToMethodBridgeBuilder. It uses the Lookup wrong and crashes if the bean class is loaded in a different classloader than the AWS SDK, but this is probably better dicussed in another issue.

I have made a proof of concept as quarkus extension which at least seems to work for my very basic test case https://github.com/Nithanim/quarkus-dynamodb-enhanced.

deandelponte commented 2 years ago

As a workaround, I've been able to use StaticTableSchema for mapping simple objects, but run into issues when mapping more complex attributes. For example, how would I map something like this using StaticTableSchema?

Please note, CustomerOrgData is a bean and all fields are Strings.

@DynamoDbBean
public class CustomerData {
  private String id = UUID.randomUUID().toString();
  private String name;
  private String description;
  private List<CustomerOrgData> customerOrgData = new ArrayList<CustomerOrgData>();

  @DynamoDbPartitionKey
  public String getId() {
    return id;
  }

  public void setId(@NonNull @NotBlank String id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(@NonNull @NotBlank String name) {
    this.name = name;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public List<CustomerOrgData> getCustomerOrgData() {
    if (customerOrgData == null || customerOrgData.isEmpty()) {
      return new ArrayList<>();
    }
    return CustomerDataUtils.sortCustomerOrgData(customerOrgData);
  }

  public void setCustomerOrgData(List<CustomerOrgData> customerOrgData) {
    this.customerOrgData = customerOrgData;
  }

  public void addCustomerOrgData(CustomerOrgData customerOrgData) {
    this.customerOrgData.add(customerOrgData);
  }
}
madeupname commented 2 years ago

Assume this is still an issue? I'm looking to convert a project using DynamoDB mapper, but used the Java SDK v1. I want to move it to GraalVM and it seems v2 SDK is the only option. The open question about creating tables seems to be the only concern, but I'm not sure if it actually blocks anything.

@deandelponte did you find a workaround? I haven't really explored SDK 2, but I had a similar mapping where Customer had List<Charge> charges. In my case, "Customer has Charges" is a 1:many composition relationship, not association or aggregation - there is no separate table for Charge. It's also not involved in queries, so it does not need to be specified in the schema. All it needed was:

    public static final String CHARGES = "CHARGES";

    @DynamoDBAttribute(attributeName = CHARGES)
    @DynamoDBTypeConvertedJson
    public List<Charge> getCharges() {
        return charges;
    }

Is your example different? If not, I think it would simply leave out CustomerOrgData from the schema altogether. But I don't know if the BeanTableSchema is doing something special for collections.

EdWrld commented 1 year ago

Hi, I believe I was running into a similar issue with the BeanTableSchema, switching to StaticTableSchema worked for me; However, I was lucky that the library that was causing me this issue was under my control. If someone is to run into a similiar situation and isn't as lucky; my suggestion would be to try implementing the StaticTableSchema in a GRAALVM Substition method https://blog.frankel.ch/solving-substitution-graalvm-issue/ 2023-01-26 21:08:50,546 ERROR [io.qua.ama.lam.run.AbstractLambdaPollLoop] (Lambda Thread (NORMAL)) Failed to run lambda (NORMAL): java.lang.IllegalStateException: GraalVM Substitution: Unable to convert Constructor to MethodHandle at io.quarkus.amazon.dynamodb.enhanced.runtime.BeanTableSchemaSubstitutionImplementation.newObjectSupplierForClass(BeanTableSchemaSubstitutionImplementation.java:24) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.newObjectSupplierForClass(BeanTableSchema.java) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:182) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:138) at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:129) at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(TableSchema.java:83)

@madeupname this link was a pretty good reference that helped me out https://stackoverflow.com/questions/71573650/nested-beans-with-statictableschema-enhanced-dynamodb-client-from-aws-java-sdk

OmerShemesh commented 1 year ago

Has anyone found any solution to this? :(