Merge pull request #300 from aguibert/serialize-improve-err-msg

Include property name when serialization fails
diff --git a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
index 3336dda..95fb57e 100644
--- a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
+++ b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
@@ -68,6 +68,7 @@
     INCOMPATIBLE_FACTORY_CREATOR_RETURN_TYPE("incompatibleFactoryCreatorReturnType"),
     MULTIPLE_JSONB_CREATORS("multipleJsonbCreators"),
     INTERNAL_ERROR("internalError"),
+    SERIALIZE_PROPERTY_ERROR("serializePropertyError"),
     DESERIALIZE_VALUE_ERROR("deserializeValueError"),
     PARSING_NUMBER("parsingNumber"),
     UNKNOWN_BINARY_DATA_STRATEGY("unknownBinaryDataStrategy"),
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
index 242a3e9..ab81280 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
  * which accompanies this distribution.
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
index 0194151..7ab0f6a 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
  * which accompanies this distribution.
@@ -17,7 +17,10 @@
 import org.eclipse.yasson.internal.ReflectionUtils;
 import org.eclipse.yasson.internal.model.ClassModel;
 import org.eclipse.yasson.internal.model.PropertyModel;
+import org.eclipse.yasson.internal.properties.MessageKeys;
+import org.eclipse.yasson.internal.properties.Messages;
 
+import javax.json.bind.JsonbException;
 import javax.json.bind.serializer.JsonbSerializer;
 import javax.json.bind.serializer.SerializationContext;
 import javax.json.stream.JsonGenerator;
@@ -56,9 +59,15 @@
 
     @Override
     protected void serializeInternal(T object, JsonGenerator generator, SerializationContext ctx) {
-        final PropertyModel[] allProperties = ((Marshaller) ctx).getMappingContext().getOrCreateClassModel(object.getClass()).getSortedProperties();
+        final PropertyModel[] allProperties = ((Marshaller) ctx).getMappingContext()
+                .getOrCreateClassModel(object.getClass()).getSortedProperties();
         for (PropertyModel model : allProperties) {
-            marshallProperty(object, generator, ctx, model);
+            try {
+                marshallProperty(object, generator, ctx, model);
+            } catch (Exception e) {
+                throw new JsonbException(Messages.getMessage(MessageKeys.SERIALIZE_PROPERTY_ERROR, model.getWriteName(),
+                        object.getClass().getCanonicalName()), e);
+            }
         }
     }
 
@@ -72,7 +81,6 @@
         generator.writeStartObject(key);
     }
 
-    @SuppressWarnings("unchecked")
     private void marshallProperty(T object, JsonGenerator generator, SerializationContext ctx, PropertyModel propertyModel) {
         Marshaller marshaller = (Marshaller) ctx;
 
diff --git a/src/main/resources/yasson-messages.properties b/src/main/resources/yasson-messages.properties
index 59b8a68..b0efc2b 100644
--- a/src/main/resources/yasson-messages.properties
+++ b/src/main/resources/yasson-messages.properties
@@ -63,6 +63,7 @@
 incompatibleFactoryCreatorReturnType=Return type of creator {0} must be of type {1}.
 multipleJsonbCreators=More than one @JsonbCreator declared in class {0}.
 internalError=Internal error: {0}
+serializePropertyError=Unable to serialize property ''{0}'' from {1}
 deserializeValueError=Error deserialize JSON value into type: {0}.
 parsingNumber=Error parsing number {0} with format {1}.
 unknownBinaryDataStrategy=Unknown binary data strategy: {0}
diff --git a/src/test/java/org/eclipse/yasson/Assertions.java b/src/test/java/org/eclipse/yasson/Assertions.java
new file mode 100644
index 0000000..509ba75
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/Assertions.java
@@ -0,0 +1,67 @@
+package org.eclipse.yasson;
+
+import static org.junit.Assert.fail;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import javax.json.bind.JsonbException;
+
+public class Assertions {
+	
+	/**
+	 * Asserts that the given operation will fail with a JsonbException
+	 * @param operation The operation that is expected to fail
+	 */
+	public static void shouldFail(Supplier<?> operation) {
+		shouldFail(operation, JsonbException.class, msg -> true);
+	}
+	
+	public static void shouldFail(Runnable operation) {
+		shouldFail(() -> {
+			operation.run();
+			return null;
+		});
+	}
+	
+	/**
+	 * Asserts that the given operation will fail with a JsonbException
+	 * @param operation The operation that is expected to fail
+	 * @param checkExceptionMessage Any checks that should be made on the exception message. For example, ensuring the exception
+	 * includes a specific token. 
+	 */
+	public static void shouldFail(Supplier<?> operation, Function<String,Boolean> checkExceptionMessage) {
+		shouldFail(operation, JsonbException.class, checkExceptionMessage);
+	}
+
+	/**
+	 * Asserts that the given operation will fail
+	 * @param operation The operation that is expected to fail
+	 * @param expectedType The expected exception type to receive when evaluating the operation
+	 * @param checkExceptionMessage Any checks that should be made on the exception message. For example, ensuring the exception
+	 * includes a specific token. 
+	 */
+	public static void shouldFail(Supplier<?> operation, Class<? extends Throwable> expectedType, Function<String,Boolean> checkExceptionMessage) {
+		try {
+			operation.get();
+			fail("The operation should have failed with a " + expectedType.getCanonicalName() + " but it succeeded.");
+		} catch (Throwable t) {
+			String fullErrorMessage = "";
+			for (Throwable current = t; current != null && current.getCause() != current; current = current.getCause()) {
+			    fullErrorMessage += current.getClass().getCanonicalName() + ": ";
+				fullErrorMessage += current.getMessage() + "\n";
+			}
+			if (expectedType.isAssignableFrom(t.getClass())) {
+				if (!checkExceptionMessage.apply(fullErrorMessage)) {
+					t.printStackTrace();
+					fail("Exception did not contain the proper content: " + fullErrorMessage);
+				}
+			} else {
+				t.printStackTrace();
+				fail("Expected to get an exception of " + expectedType + " but instead was " + t.getClass());
+			}
+		}
+		
+	}
+
+}
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
index a2cbadc..16d5e5d 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
@@ -14,6 +14,7 @@
 
 package org.eclipse.yasson.defaultmapping.basic;
 
+import static org.eclipse.yasson.Assertions.*;
 import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.defaultmapping.basic.model.BigDecimalInNumber;
 import org.eclipse.yasson.defaultmapping.generics.model.ScalarValueWrapper;
@@ -26,10 +27,18 @@
 import javax.json.JsonWriter;
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbException;
 import javax.json.stream.JsonGenerator;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.StringWriter;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
 
 /**
  * @author Roman Grigoriadi
@@ -207,5 +216,37 @@
                 "\"unsafeBigDecimalValue\":9223372036854775807}", w.toString());
 
     }
+    
+    public static class NumberContainer {
+    	public Double doubleProp;
+    	public Collection<Double> collectionProp;
+    	public Map<String,Double> mapProp;
+    }
+    
+    @Test
+    public void testSerializeInvalidDouble() {
+        shouldFail(() -> jsonb.toJson(Double.POSITIVE_INFINITY));
+
+        NumberContainer obj = new NumberContainer();
+        obj.doubleProp = Double.POSITIVE_INFINITY;
+        shouldFail(() -> jsonb.toJson(obj), msg -> msg.contains("doubleProp") && msg.contains("NumberContainer"));
+    }
+    
+    
+    @Test
+    public void testSerializeInvalidDoubleCollection() {
+        NumberContainer obj = new NumberContainer();
+        obj.collectionProp = Collections.singleton(Double.POSITIVE_INFINITY);
+        shouldFail(() -> jsonb.toJson(obj),
+                msg -> msg.contains("collectionProp") && msg.contains("NumberContainer"));
+    }
+
+    @Test
+    public void testSerializeInvalidDoubleMap() {
+        NumberContainer obj = new NumberContainer();
+        obj.mapProp = Collections.singletonMap("doubleKey", Double.POSITIVE_INFINITY);
+        shouldFail(() -> jsonb.toJson(obj),
+                msg -> msg.contains("mapProp") && msg.contains("NumberContainer"));
+    }
 
 }
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
index bf2a7b4..8bb67eb 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
@@ -17,6 +17,7 @@
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.JsonbException;
 
+import org.eclipse.yasson.Assertions;
 import org.eclipse.yasson.defaultmapping.modifiers.model.ChildOfPackagePrivateParent;
 import org.eclipse.yasson.defaultmapping.modifiers.model.FieldModifiersClass;
 import org.junit.Before;
@@ -67,14 +68,9 @@
         NestedPackageChild child = new NestedPackageChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("Unable to serialize property 'id'") &&
+                msg.contains("java.lang.IllegalAccessException")); 
     }
 
     private class NestedPrivateParent {
@@ -90,14 +86,8 @@
         NestedPrivateChild child = new NestedPrivateChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
 
@@ -114,14 +104,8 @@
         NestedStaticPackageChild child = new NestedStaticPackageChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
     private static class NestedStaticPrivateParent {
@@ -137,14 +121,8 @@
         NestedStaticPrivateChild child = new NestedStaticPrivateChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
 }
diff --git a/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java b/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
index 63337b0..d72b799 100644
--- a/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
+++ b/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
@@ -9,6 +9,7 @@
  ******************************************************************************/
 package org.eclipse.yasson.jsonpsubstitution;
 
+import org.eclipse.yasson.Assertions;
 import org.eclipse.yasson.JsonBindingProvider;
 import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.YassonJsonb;
@@ -165,12 +166,7 @@
         generator.writeStartObject();
         //key not written
 
-        try {
-            jsonb.toJson(dog, generator);
-            Assert.fail("JsonbException not thrown");
-        } catch (JsonbException e) {
-            //OK
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(dog, generator));
     }
 
     @Test