dschulten / hydra-java

Annotate your Java beans and serialize them as json-ld with hydra
Apache License 2.0
108 stars 27 forks source link

java.lang.NullPointerException at de.escalon.hypermedia.hydra.serialize.JacksonHydraSerializer.getTerms(JacksonHydraSerializer.java:252) #8

Closed ceefour closed 9 years ago

ceefour commented 9 years ago

The NullPointerException should give an explanation what is expected.

The exception is thrown here:

        for (Field field : fields) {
            final Expose fieldExpose = field.getAnnotation(Expose.class);
            if (Enum.class.isAssignableFrom(field.getType())) {
                Map<String, String> map = new LinkedHashMap<String, String>();
                termsMap.put(field.getName(), map);
                if (fieldExpose != null) {
                    map.put(AT_ID, fieldExpose.value());
                }
                map.put(AT_TYPE, AT_VOCAB);
                final Enum value = (Enum)field.get(bean);

                // -------------- EXCEPTION HERE ------------
                final Expose enumValueExpose = getAnnotation(value.getClass().getField(value.name()), Expose.class);

                // TODO redefine actual enum value to exposed on enum value definition
                if (enumValueExpose != null) {
                    termsMap.put(value.toString(), enumValueExpose.value());
                } else {

With following models:

Product.java:

public class Product extends Thing<Product> implements IProduct {

    public static final long serialVersionUID = 1L;

    public OrganizationOrBrand brand;
    public Organization manufacturer;
    public String sku;
    public String barcode;
    public CurrencyUnit priceCurrency;
    public BigDecimal price;
    public DecimalMeasure<Mass> weight;
    public DecimalMeasure<Length> depth;
    public DecimalMeasure<Length> width;
    public DecimalMeasure<Length> height;
    public IColor color;
    public BigDecimal productionCost;
    public InventoryManagement inventoryManagement;
    public InventoryPolicy inventoryPolicy;
    public DecimalMeasure<Quantity> inventoryLevel;
    @Expose("http://www.soluvas.org/commerceplug/1.0#inventoryOnHand")
    public DecimalMeasure<Quantity> inventoryOnHand;
    @JsonInclude(Include.NON_EMPTY)
    public final List<OptionType> optionTypes = new ArrayList<>();
    @JsonInclude(Include.NON_EMPTY)
    public final List<Product> variants = new ArrayList<>();
    protected final List<Offer> offers = new ArrayList<>();
    public String serialNumber;
    @JsonProperty("additionalProperty") @JsonInclude(Include.NON_EMPTY)
    public final List<PropertyValue<?>> additionalProperties = new ArrayList<>();

    @Override
    public OrganizationOrBrand getBrand() {
        return brand;
    }

    @Override
    public void setBrand(OrganizationOrBrand brand) {
        this.brand = brand;
    }

    public Product withBrand(OrganizationOrBrand brand) {
        this.brand = brand;
        return this;
    }

    @Override
    public Organization getManufacturer() {
        return manufacturer;
    }

    @Override
    public void setManufacturer(Organization manufacturer) {
        this.manufacturer = manufacturer;
    }

    public Product withManufacturer(Organization manufacturer) {
        this.manufacturer = manufacturer;
        return this;
    }

    @Override
    public String getSku() {
        return sku;
    }

    @Override
    public void setSku(String sku) {
        this.sku = sku;
    }

    public Product withSku(String sku) {
        this.sku = sku;
        return this;
    }

    @Override
    public String getBarcode() {
        return barcode;
    }

    @Override
    public void setBarcode(String barcode) {
        this.barcode = barcode;
    }

    public Product withBarcode(String barcode) {
        this.barcode = barcode;
        return this;
    }

    @Override
    public CurrencyUnit getPriceCurrency() {
        return priceCurrency;
    }

    @Override
    public void setPriceCurrency(CurrencyUnit currency) {
        this.priceCurrency = currency;
    }

    public Product withPriceCurrency(CurrencyUnit currency) {
        this.priceCurrency = currency;
        return this;
    }

    @Override
    public BigDecimal getPrice() {
        return price;
    }

    @Override
    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Product withPrice(BigDecimal price) {
        this.price = price;
        return this;
    }

    @Override
    public DecimalMeasure<Mass> getWeight() {
        return weight;
    }

    @Override
    public void setWeight(DecimalMeasure<Mass> weight) {
        this.weight = weight;
    }

    public Product withWeight(DecimalMeasure<Mass> weight) {
        this.weight = weight;
        return this;
    }

    @Override
    public DecimalMeasure<Length> getDepth() {
        return depth;
    }

    @Override
    public void setDepth(DecimalMeasure<Length> depth) {
        this.depth = depth;
    }

    public Product withDepth(DecimalMeasure<Length> depth) {
        this.depth = depth;
        return this;
    }

    @Override
    public DecimalMeasure<Length> getWidth() {
        return width;
    }

    @Override
    public void setWidth(DecimalMeasure<Length> width) {
        this.width = width;
    }

    public Product withWidth(DecimalMeasure<Length> width) {
        this.width = width;
        return this;
    }

    @Override
    public DecimalMeasure<Length> getHeight() {
        return height;
    }

    @Override
    public void setHeight(DecimalMeasure<Length> height) {
        this.height = height;
    }

    public Product withHeight(DecimalMeasure<Length> height) {
        this.height = height;
        return this;
    }

    @Override
    public IColor getColor() {
        return color;
    }

    @Override
    public void setColor(IColor color) {
        this.color = color;
    }

    public Product withColor(IColor color) {
        this.color = color;
        return this;
    }

    @Override
    public BigDecimal getProductionCost() {
        return productionCost;
    }

    @Override
    public void setProductionCost(BigDecimal productionCost) {
        this.productionCost = productionCost;
    }

    public Product withProductionCost(BigDecimal productionCost) {
        this.productionCost = productionCost;
        return this;
    }

    public InventoryManagement getInventoryManagement() {
        return inventoryManagement;
    }

    public void setInventoryManagement(InventoryManagement inventoryManagement) {
        this.inventoryManagement = inventoryManagement;
    }

    public Product withInventoryManagement(InventoryManagement inventoryManagement) {
        this.inventoryManagement = inventoryManagement;
        return this;
    }

    public InventoryPolicy getInventoryPolicy() {
        return inventoryPolicy;
    }

    public void setInventoryPolicy(InventoryPolicy inventoryPolicy) {
        this.inventoryPolicy = inventoryPolicy;
    }

    public Product withInventoryPolicy(InventoryPolicy inventoryPolicy) {
        this.inventoryPolicy = inventoryPolicy;
        return this;
    }

    @Override
    public DecimalMeasure<Quantity> getInventoryLevel() {
        return inventoryLevel;
    }

    @Override
    public void setInventoryLevel(DecimalMeasure<Quantity> inventoryLevel) {
        this.inventoryLevel = inventoryLevel;
    }

    public Product withInventoryLevel(DecimalMeasure<Quantity> inventoryLevel) {
        this.inventoryLevel = inventoryLevel;
        return this;
    }

    @Override
    public DecimalMeasure<Quantity> getInventoryOnHand() {
        return inventoryOnHand;
    }

    @Override
    public void setInventoryOnHand(DecimalMeasure<Quantity> inventoryOnHand) {
        this.inventoryOnHand = inventoryOnHand;
    }

    public Product withInventoryOnHand(DecimalMeasure<Quantity> inventoryOnHand) {
        this.inventoryOnHand = inventoryOnHand;
        return this;
    }

    @Override
    public List<OptionType> getOptionTypes() {
        return optionTypes;
    }

    public Product addOptionType(OptionType optionType) {
        getOptionTypes().add(optionType);
        return this;
    }

    @Override
    public List<Product> getVariants() {
        return variants;
    }

    public Product addVariant(Product variant) {
        getVariants().add(variant);
        return this;
    }

    @JsonInclude(Include.NON_EMPTY)
    public List<Offer> getOffers() {
        return offers;
    }

    public Product addOffer(Offer offer) {
        getOffers().add(offer);
        return this;
    }

    @Override
    public String getSerialNumber() {
        return serialNumber;
    }

    @Override
    public void setSerialNumber(String serialNumber) {
        this.serialNumber = serialNumber;
    }

    public Product withSerialNumber(String serialNumber) {
        this.serialNumber = serialNumber;
        return this;
    }

    @Override
    public List<PropertyValue<?>> getAdditionalProperties() {
        return additionalProperties;
    }

    public Product withAdditionalProperty(PropertyValue<?> propertyValue) {
        getAdditionalProperties().add(propertyValue);
        return this;
    }

}

Thing.java:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"id", "name", "description", "image", "url",
    "additionalType", "alternateName", "potentialAction", "sameAs"})
@SuppressWarnings("unchecked")
public class Thing<C extends IThing> extends ResourceSupport 
    implements IThing, Serializable {

    public static final long serialVersionUID = 1L;

    public String id;
    @JsonProperty("additionalType") @JsonInclude(Include.NON_EMPTY)
    public final List<String> additionalTypes = new ArrayList<>();
    public String alternateName;
    public String description;
    public ImageObject image;
    public String name;
    public Action potentialAction;
    public String sameAs;
    public String url;
    @JsonIgnore
    public final Map<String, Object> customProperties = new LinkedHashMap<>();

    @Override
    @JsonProperty("id")
    public String getThingId() {
        return id;
    }

    @Override
    public void setThingId(String id) {
        this.id = id;
    }

    public C withThingId(String id) {
        this.id = id;
        return (C) this;
    }

    @Override
    public List<String> getAdditionalTypes() {
        return additionalTypes;
    }

    public C addAdditionalType(String additionalType) {
        getAdditionalTypes().add(additionalType);
        return (C) this;
    }

    @Override
    public String getAlternateName() {
        return alternateName;
    }

    public void setAlternateName(String alternateName) {
        this.alternateName = alternateName;
    }

    @Override
    public String getDescription() {
        return description;
    }

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

    public C withDescription(String description) {
        this.description = description;
        return (C) this;
    }

    @Override
    public ImageObject getImage() {
        return image;
    }

    public void setImage(ImageObject image) {
        this.image = image;
    }

    @Override
    public String getName() {
        return name;
    }

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

    public C withName(String name) {
        this.name = name;
        return (C) this;
    }

    @Override
    public Action getPotentialAction() {
        return potentialAction;
    }

    public void setPotentialAction(Action potentialAction) {
        this.potentialAction = potentialAction;
    }

    @Override
    public String getSameAs() {
        return sameAs;
    }

    public void setSameAs(String sameAs) {
        this.sameAs = sameAs;
    }

    @Override
    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @JsonAnyGetter
    public Map<String, Object> getCustomProperties() {
        return this.customProperties;
    }

    @JsonAnySetter
    public void setCustomProperty(String name, Object value) {
        this.customProperties.put(name, value);
    }

    public C withCustomProperty(String name, Object value) {
        this.customProperties.put(name, value);
        return (C) this;
    }

}

Error:

com.fasterxml.jackson.databind.JsonMappingException: java.lang.NullPointerException
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:125)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2866)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2323)
    at id.co.bippo.product.rs.commerceplug.ProductOrServiceImplJsonLdTest.productOrService(ProductOrServiceImplJsonLdTest.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
    at de.escalon.hypermedia.hydra.serialize.JacksonHydraSerializer.serializeContext(JacksonHydraSerializer.java:199)
    at de.escalon.hypermedia.hydra.serialize.JacksonHydraSerializer.serialize(JacksonHydraSerializer.java:105)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:114)
    ... 27 more
Caused by: java.lang.NullPointerException
    at de.escalon.hypermedia.hydra.serialize.JacksonHydraSerializer.getTerms(JacksonHydraSerializer.java:252)
    at de.escalon.hypermedia.hydra.serialize.JacksonHydraSerializer.serializeContext(JacksonHydraSerializer.java:149)
    ... 29 more
dschulten commented 9 years ago

I tried to reproduce it by making classes from the code above, but was unable to. The problem seems related to enums somehow. Could you please tell me which of the dummy classes below is an enum in your model, or maybe which of these classes contain enums in turn, maybe as inherited members? How do you initialize your Product? I tried to make OptionType an enum and wrote this test, which unfortunately succeeds:

    @Test
    public void convertsClassWithoutTerms() throws IOException {
        final Product product = new Product();
        product.addOptionType(OptionType.BAR);
        mapper.writeValue(w, product);
        assertEquals("{\"@context\":{\"@vocab\":\"http://schema.org/\"," +
                        "\"inventoryOnHand\":\"http://www.soluvas.org/commerceplug/1.0#inventoryOnHand\"}," +
                        "\"@type\":\"Product\"," +
                        "\"optionTypes\":[\"BAR\"]}",
                w.toString());
    }

Dummy classes I created:

    public class Action {}
    public class ImageObject {}

    public interface IThing {
        void setThingId(String id);
        List<String> getAdditionalTypes();
        String getAlternateName();
        String getDescription();
        ImageObject getImage();
        String getName();
        Action getPotentialAction();
        String getSameAs();
        String getUrl();
     }
     public interface IProduct {
        OrganizationOrBrand getBrand();
        void setBrand(OrganizationOrBrand brand);
        Organization getManufacturer();
        void setManufacturer(Organization manufacturer);
        String getSku();
        void setSku(String sku);
        String getBarcode();
        void setBarcode(String barcode);
        CurrencyUnit getPriceCurrency();
        void setPriceCurrency(CurrencyUnit currency);
        BigDecimal getPrice();
        void setPrice(BigDecimal price);
        DecimalMeasure<Mass> getWeight();
        void setWeight(DecimalMeasure<Mass> weight);
        DecimalMeasure<Length> getDepth();
        void setDepth(DecimalMeasure<Length> depth);
        DecimalMeasure<Length> getWidth();
        void setWidth(DecimalMeasure<Length> width);
        DecimalMeasure<Length> getHeight();
        void setHeight(DecimalMeasure<Length> height);
        IColor getColor();
        void setColor(IColor color);
        BigDecimal getProductionCost();
        void setProductionCost(BigDecimal productionCost);
        DecimalMeasure<Quantity> getInventoryLevel();
        void setInventoryLevel(DecimalMeasure<Quantity> inventoryLevel);
        DecimalMeasure<Quantity> getInventoryOnHand();
        void setInventoryOnHand(DecimalMeasure<Quantity> inventoryOnHand);
        List<OptionType> getOptionTypes();
        List<Product> getVariants();
        String getSerialNumber();
        void setSerialNumber(String serialNumber);
        List<PropertyValue<?>> getAdditionalProperties();
    }

    public class OrganizationOrBrand {}
    public class Organization {}
    public class CurrencyUnit {}
    public class Mass {}
    public class Length {}
    public class DecimalMeasure<T> {}
    public class IColor {}
    public class InventoryManagement {}
    public class InventoryPolicy {}
    public class Quantity {}
    public enum OptionType { FOO, BAR }
    public class PropertyValue<T> {}
dschulten commented 9 years ago

I was able to reproduce this now, in combination with #7. No need to give more details at the moment.

dschulten commented 9 years ago

Is it possible that in your test case the availability in Offer is null? That is the reason why my test case fails now, after solving the accessibility problem. When I use setAvailability to assign a value, the test runs through. The serializer should of course ignore null values for enum members and not throw an exception.

ceefour commented 9 years ago

Yes, uninitialized enums are null by default. These are part of current work developing CommercePlug API which uses Hydra (and this project hydra-java in order to render). Thanks for developing this library @dschulten :)

dschulten commented 9 years ago

Solved in 0.2.0-SNAPSHOT