Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS DynamoDB: Could not unconvert attribute error

I have an issuse and I can't handle it. I've created CRUD methods for example in KitchenService. I have methods like addProduct etc and these works fine.. But I have Recipe class where I'm using Product class field.. In this case I have huge problems.

My addRecipe method:

public Recipe addRecipe (Recipe recipe){

    List<RecipeElement> recipeElements = recipe.getRecipeElements();
    for (RecipeElement recipeElement : recipeElements) {
        String id = recipeElement.getProduct().getId();
        Product product = databaseController.get(Product.class, id);
        recipeElement.setProduct(product);
    }

    databaseController.saveRecipe(recipe);
    logger.log("Recipe created");

    return recipe;

Build succesfull so I want to test it in POSTMAN, that's how look JSON witch I'm sending:

{"id":null,"name":"test3","labels":["GLUTEN_FREE"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"recipeElements":[{"product":{"id":"ecacaf36-29a2-41c6-942e-be5a715ed094"},"weight":"100"}],"approved":false}

And then I get "message": "Internal server error" so I'm checking logs, that's what I found there:

Product[Media]; could not unconvert attribute: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException

Caused by: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: could not invoke public void pl.javamill.model.kitchen.Product.setMedia(pl.javamill.model.common.Media) on class pl.kitchen.Product with value {name=heheszki, url=http://blabla.pl, mediaType=IMAGE} of type class java.util.LinkedHashMap

This is how look Recipe Class:

@DynamoDBTable(tableName = "recipe")
public class Recipe extends Request {
/**
 * Id of kitchen content
 */
private String id;
/**
 * Name of recipe
 */
private String name;

/**
 * Labels of product for example gluten fee product
 */
private List<KitchenLabel> labels;

/**
 * Author of content.
 */
private Author author;

/**
 * Address of content image.
 */
private Media media;

private Boolean approved;

private List<RecipeElement> recipeElements;

@DynamoDBHashKey(attributeName = "id")
@DynamoDBAutoGeneratedKey
public String getId() {
    return id;
}

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

@DynamoDBAttribute(attributeName = "Name")
public String getName() {
    return name;
}

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

@DynamoDBTypeConverted(converter = EnumConverter.class)
@DynamoDBAttribute(attributeName = "Labels")
public List<KitchenLabel> getLabels() {
    return labels;
}

public void setLabels(List<KitchenLabel> labels) {
    this.labels = labels;
}

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBAttribute(attributeName = "Author")
public Author getAuthor() {
    return author;
}

public void setAuthor(Author author) {
    this.author = author;
}

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBAttribute(attributeName = "Media")
public Media getMedia() {
    return media;
}

public void setMedia(Media media) {
    this.media = media;
}

@DynamoDBAttribute(attributeName = "Approved")
public Boolean getApproved() {
    return approved;
}

public void setApproved(Boolean approved) {
    this.approved = approved;
}

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBAttribute(attributeName = "RecipeElements")
public List<RecipeElement> getRecipeElements() {
    return recipeElements;
}

public void setRecipeElements(List<RecipeElement> recipeElements) {
    this.recipeElements = recipeElements;
}

RecipeElement class:

public class RecipeElement {


private Product product;
private Integer weight;

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBHashKey(attributeName = "product")
public Product getProduct() {
    return product;
}

public void setProduct(Product product) {
        this.product = product;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }
}

And Product class:

@DynamoDBTable(tableName = "product")
public class Product extends Request {
    /**
     * Id of kitchen content
     */
    private String id;

    /**
     * Name of product
     */
    private String name;

    /**
     * Calories in 100g
     */
    private Integer calories;

    /**
     * Fat in 100g
     */
    private Double fat;

    /**
     * Total carbo in 100g
     */
    private Double carbo;

    /**
     * Total Protein in 100g
     */
    private Double protein;

    /**
     * Labels of product for example gluten fee product
     */
    private List<ProductKind> productKinds;

    /**
     * Author of content.
     */
    private Author author;

    /**
     * Address of content image.
     */
    private Media media;

    private Boolean approved;

    @DynamoDBHashKey(attributeName = "id")
    @DynamoDBAutoGeneratedKey
    public String getId() {
        return id;
    }

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

    @DynamoDBAttribute(attributeName = "Name")
    public String getName() {
        return name;
    }

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

    @DynamoDBAttribute(attributeName = "Calories")
    public Integer getCalories() {
        return calories;
    }

    public void setCalories(Integer calories) {
        this.calories = calories;
    }

    @DynamoDBAttribute(attributeName = "Fat")
    public Double getFat() {
        return fat;
    }

    public void setFat(Double fat) {
        this.fat = fat;
    }

    @DynamoDBAttribute(attributeName = "Carbo")
    public Double getCarbo() {
        return carbo;
    }

    public void setCarbo(Double carbo) {
        this.carbo = carbo;
    }

    @DynamoDBAttribute(attributeName = "Protein")
    public Double getProtein() {
        return protein;
    }

    public void setProtein(Double protein) {
        this.protein = protein;
    }

    @DynamoDBTypeConverted(converter = EnumConverter.class)
    @DynamoDBAttribute(attributeName = "ProductKinds")
    public List<ProductKind> getProductKinds() {
        return productKinds;
    }

    public void setProductKinds(List<ProductKind> productKinds) {
        this.productKinds = productKinds;
    }

    @DynamoDBTypeConverted(converter = ObjectConverter.class)
    @DynamoDBAttribute(attributeName = "Author")
    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @DynamoDBTypeConverted(converter = ObjectConverter.class)
    @DynamoDBAttribute(attributeName = "Media")
    public Media getMedia() {
        return media;
    }

    public void setMedia(Media media) {
        this.media = media;
    }

    @DynamoDBAttribute(attributeName = "Approved")
    public Boolean getApproved() {
        return approved;
    }

    public void setApproved(Boolean approved) {
        this.approved = approved;
    }

And this is my converter class:

public class ObjectConverter<T extends Object> 
implements DynamoDBTypeConverter<String, T> {

    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convert(T object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException("Unable to parse JSON");
    }

    @Override
    public T unconvert(String object) {
        try {
            T unconvertedObject = objectMapper.readValue(object, 
                new TypeReference<T>() {
            });
            return unconvertedObject;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Can someone help me with this issue?

like image 265
D.Zet Avatar asked Dec 21 '17 14:12

D.Zet


1 Answers

Short answer:

Add a public empty constructor to Media class.

Long answer:

In your code, when you are performing databaseController.get(Product.class, id), your are invoking the underline method public T unconvert(final Map<String,AttributeValue> object) of DynamoDBMapperTableModel:

@Override
public T unconvert(final Map<String,AttributeValue> object) {
    final T result = StandardBeanProperties.DeclaringReflect.<T>newInstance(targetType);
    if (!object.isEmpty()) {
        for (final DynamoDBMapperFieldModel<T,Object> field : fields()) {
            try {
                final AttributeValue value = object.get(field.name());
                if (value != null) {
                    field.unconvertAndSet(result, value);
                }
            } catch (final RuntimeException e) {
                throw new DynamoDBMappingException(
                    targetType.getSimpleName() + "[" + field.name() + "]; could not unconvert attribute", e
                );
            }
        }
    }
    return result;
}

In the first line of this method, a new instance of the table model is being created using reflection (in your case a new instance of Product), and then the new instance's fields are being converted to the desired class and set accordingly.

StandardBeanProperties.DeclaringReflect.<T>newInstance(targetType) invoking .newInstance() on targetType, witch it's type is Class<T>.

As written in Class.newInstance() documentation, InstantiationException is thrown if the class has no nullary constructor. There are other scenarios in which this exception will be thrown but in my experience, it's probably because a public empty constructor is not implemented.

like image 84
Tom Ben Gal Avatar answered Oct 17 '22 05:10

Tom Ben Gal