Create StandardYamlCoder

Created StandardYamlCoder which is like a StandardCoder, except that
the original converts to/from JSON, while the new class converts to/from
YAML.  Also added YamlMessageBodyHandler and incorporated it into the
http server so that it supports a media type of */yaml.

Change-Id: Ibd83a9f6d355a330f63e435f2bb41affcf1947c2
Issue-ID: POLICY-2065
Signed-off-by: Jim Hahn <jrh3@att.com>
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/RestServer.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/RestServer.java
index 2368c0b..decf95f 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/RestServer.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/RestServer.java
@@ -99,7 +99,8 @@
         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_AAF_SUFFIX,
                         String.valueOf(restServerParameters.isAaf()));
         props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
-                        String.join(",", GsonMessageBodyHandler.class.getName(), JsonExceptionMapper.class.getName()));
+                        String.join(",", GsonMessageBodyHandler.class.getName(), YamlMessageBodyHandler.class.getName(),
+                                        JsonExceptionMapper.class.getName()));
         return props;
     }
 
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/YamlMessageBodyHandler.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/YamlMessageBodyHandler.java
new file mode 100644
index 0000000..ab09c1a
--- /dev/null
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/YamlMessageBodyHandler.java
@@ -0,0 +1,110 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.http.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardYamlCoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provider that serializes and de-serializes JSON via gson.
+ */
+@Provider
+@Consumes(MediaType.WILDCARD)
+@Produces(MediaType.WILDCARD)
+public class YamlMessageBodyHandler implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
+
+    public static final Logger logger = LoggerFactory.getLogger(YamlMessageBodyHandler.class);
+
+    /**
+     * Constructs the object.
+     */
+    public YamlMessageBodyHandler() {
+        logger.info("Accepting YAML for REST calls");
+    }
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return canHandle(mediaType);
+    }
+
+    @Override
+    public long getSize(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+                    MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
+
+        try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8)) {
+            new StandardYamlCoder().encode(writer, object);
+
+        } catch (CoderException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return canHandle(mediaType);
+    }
+
+    /**
+     * Determines if this provider can handle the given media type.
+     *
+     * @param mediaType the media type of interest
+     * @return {@code true} if this provider handles the given media type, {@code false}
+     *         otherwise
+     */
+    private boolean canHandle(MediaType mediaType) {
+        return (mediaType != null && "yaml".equalsIgnoreCase(mediaType.getSubtype()));
+    }
+
+    @Override
+    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+                    MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException {
+
+        try (InputStreamReader streamReader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) {
+            Class<?> clazz = (Class<?>) genericType;
+            return new StandardYamlCoder().decode(streamReader, clazz);
+
+        } catch (CoderException e) {
+            throw new IOException(e);
+        }
+    }
+}
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java
index f2b5364..84f82f1 100644
--- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java
@@ -26,9 +26,8 @@
 import static org.junit.Assert.assertTrue;
 
 import com.google.gson.Gson;
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -40,6 +39,7 @@
 import org.junit.Test;
 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
+import org.onap.policy.common.utils.coder.StandardYamlCoder;
 import org.onap.policy.common.utils.gson.GsonTestUtils;
 import org.onap.policy.common.utils.network.NetworkUtil;
 import org.slf4j.Logger;
@@ -50,6 +50,8 @@
  */
 public class HttpServerTest {
     private static final String LOCALHOST = "localhost";
+    private static final String JSON_MEDIA = "application/json";
+    private static final String YAML_MEDIA = "application/yaml";
     private static final String SWAGGER_JSON = "/swagger.json";
     private static final String JUNIT_ECHO_HELLO = "/junit/echo/hello";
     private static final String JUNIT_ECHO_FULL_REQUEST = "/junit/echo/full/request";
@@ -84,6 +86,7 @@
 
         MyJacksonProvider.resetSome();
         MyGsonProvider.resetSome();
+        MyYamlProvider.resetSome();
     }
 
     private static void incrementPort() {
@@ -112,7 +115,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
     }
 
@@ -135,7 +138,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
 
         assertTrue(MyJacksonProvider.hasReadSome());
@@ -143,6 +146,9 @@
 
         assertFalse(MyGsonProvider.hasReadSome());
         assertFalse(MyGsonProvider.hasWrittenSome());
+
+        assertFalse(MyYamlProvider.hasReadSome());
+        assertFalse(MyYamlProvider.hasWrittenSome());
     }
 
     @Test
@@ -164,7 +170,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
 
         assertTrue(MyGsonProvider.hasReadSome());
@@ -172,6 +178,43 @@
 
         assertFalse(MyJacksonProvider.hasReadSome());
         assertFalse(MyJacksonProvider.hasWrittenSome());
+
+        assertFalse(MyYamlProvider.hasReadSome());
+        assertFalse(MyYamlProvider.hasWrittenSome());
+    }
+
+    @Test
+    public void testYamlPackageServer() throws Exception {
+        logger.info("-- testYamlPackageServer() --");
+
+        HttpServletServer server = HttpServletServerFactoryInstance.getServerFactory()
+                        .build("echo", LOCALHOST, port, "/", false, true);
+
+        server.setSerializationProvider(MyYamlProvider.class.getName());
+        server.addServletPackage("/*", this.getClass().getPackage().getName());
+        server.addFilterClass("/*", TestFilter.class.getName());
+        server.waitedStart(5000);
+
+        assertTrue(HttpServletServerFactoryInstance.getServerFactory().get(port).isAlive());
+
+        RestEchoReqResp request = new RestEchoReqResp();
+        request.setRequestId(100);
+        request.setText(SOME_TEXT);
+        String reqText = new StandardYamlCoder().encode(request);
+
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, YAML_MEDIA, reqText);
+
+        // response reader strips newlines, so we should, too, before comparing
+        assertEquals(reqText.replace("\n", ""), response);
+
+        assertTrue(MyYamlProvider.hasReadSome());
+        assertTrue(MyYamlProvider.hasWrittenSome());
+
+        assertFalse(MyGsonProvider.hasReadSome());
+        assertFalse(MyGsonProvider.hasWrittenSome());
+
+        assertFalse(MyJacksonProvider.hasReadSome());
+        assertFalse(MyJacksonProvider.hasWrittenSome());
     }
 
     @Test
@@ -191,7 +234,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
     }
 
@@ -213,7 +256,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
 
         assertTrue(MyJacksonProvider.hasReadSome());
@@ -221,6 +264,9 @@
 
         assertFalse(MyGsonProvider.hasReadSome());
         assertFalse(MyGsonProvider.hasWrittenSome());
+
+        assertFalse(MyYamlProvider.hasReadSome());
+        assertFalse(MyYamlProvider.hasWrittenSome());
     }
 
     @Test
@@ -241,7 +287,7 @@
         request.setText(SOME_TEXT);
         String reqText = gson.toJson(request);
 
-        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, reqText);
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, JSON_MEDIA, reqText);
         assertEquals(reqText, response);
 
         assertTrue(MyGsonProvider.hasReadSome());
@@ -249,6 +295,42 @@
 
         assertFalse(MyJacksonProvider.hasReadSome());
         assertFalse(MyJacksonProvider.hasWrittenSome());
+
+        assertFalse(MyYamlProvider.hasReadSome());
+        assertFalse(MyYamlProvider.hasWrittenSome());
+    }
+
+    @Test
+    public void testYamlClassServer() throws Exception {
+        logger.info("-- testYamlClassServer() --");
+
+        HttpServletServer server = HttpServletServerFactoryInstance.getServerFactory()
+                        .build("echo", LOCALHOST, port, "/", false, true);
+        server.setSerializationProvider(MyYamlProvider.class.getName());
+        server.addServletClass("/*", RestEchoService.class.getName());
+        server.addFilterClass("/*", TestFilter.class.getName());
+        server.waitedStart(5000);
+
+        assertTrue(HttpServletServerFactoryInstance.getServerFactory().get(port).isAlive());
+
+        RestEchoReqResp request = new RestEchoReqResp();
+        request.setRequestId(100);
+        request.setText(SOME_TEXT);
+        String reqText = new StandardYamlCoder().encode(request);
+
+        String response = http(portUrl + JUNIT_ECHO_FULL_REQUEST, YAML_MEDIA, reqText);
+
+        // response reader strips newlines, so we should, too, before comparing
+        assertEquals(reqText.replace("\n", ""), response);
+
+        assertTrue(MyYamlProvider.hasReadSome());
+        assertTrue(MyYamlProvider.hasWrittenSome());
+
+        assertFalse(MyGsonProvider.hasReadSome());
+        assertFalse(MyGsonProvider.hasWrittenSome());
+
+        assertFalse(MyJacksonProvider.hasReadSome());
+        assertFalse(MyJacksonProvider.hasWrittenSome());
     }
 
     @Test
@@ -416,7 +498,7 @@
      * @throws IOException thrown is IO exception occurs
      * @throws InterruptedException thrown if thread interrupted occurs
      */
-    private String http(String urlString, String post)
+    private String http(String urlString, String mediaType, String post)
             throws IOException, InterruptedException {
         URL url = new URL(urlString);
         if (!NetworkUtil.isTcpPortOpen(url.getHost(), url.getPort(), 25, 2)) {
@@ -425,7 +507,7 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
         conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/json");
+        conn.setRequestProperty("Content-Type", mediaType);
         IOUtils.write(post, conn.getOutputStream());
         return response(conn);
     }
@@ -438,14 +520,9 @@
      * @throws IOException if an I/O error occurs
      */
     private String response(URLConnection conn) throws IOException {
-        StringBuilder response = new StringBuilder();
-        try (BufferedReader ioReader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
-            String line;
-            while ((line = ioReader.readLine()) != null) {
-                response.append(line);
-            }
+        try (InputStream inpstr = conn.getInputStream()) {
+            return String.join("", IOUtils.readLines(inpstr));
         }
-        return response.toString();
     }
 
 }
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyYamlProvider.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyYamlProvider.java
new file mode 100644
index 0000000..098ecb4
--- /dev/null
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyYamlProvider.java
@@ -0,0 +1,82 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.http.server.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import lombok.AccessLevel;
+import lombok.Setter;
+import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
+
+/**
+ * YamlMessageBodyHandler that tracks activities.
+ */
+public class MyYamlProvider extends YamlMessageBodyHandler {
+
+    @Setter(AccessLevel.PRIVATE)
+    private static boolean readSome = false;
+
+    @Setter(AccessLevel.PRIVATE)
+    private static boolean wroteSome = false;
+
+    /**
+     * Constructs the object and resets the variables to indicate that no activity has
+     * occurred yet.
+     */
+    public MyYamlProvider() {
+        super();
+    }
+
+    @Override
+    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+                    MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException {
+
+        setReadSome(true);
+        return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
+    }
+
+    @Override
+    public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+                    MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
+
+        setWroteSome(true);
+        super.writeTo(object, type, genericType, annotations, mediaType, httpHeaders, entityStream);
+    }
+
+    public static boolean hasReadSome() {
+        return readSome;
+    }
+
+    public static boolean hasWrittenSome() {
+        return wroteSome;
+    }
+
+    public static void resetSome() {
+        MyYamlProvider.readSome = false;
+        MyYamlProvider.wroteSome = false;
+    }
+
+}
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestEchoService.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestEchoService.java
index 9db1053..27ce300 100644
--- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestEchoService.java
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestEchoService.java
@@ -56,7 +56,7 @@
 
     @POST
     @Path("/full/request")
-    @Produces(MediaType.APPLICATION_JSON)
+    @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
     @ApiOperation(value = "echoes back the request structure", response = RestEchoReqResp.class)
     public Response echoFullyPost(RestEchoReqResp reqResp) {
         return Response.status(Status.OK).entity(reqResp).build();
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestServerTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestServerTest.java
index fd9b59f..ee28b96 100644
--- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestServerTest.java
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/RestServerTest.java
@@ -56,6 +56,7 @@
 import org.onap.policy.common.endpoints.http.server.JsonExceptionMapper;
 import org.onap.policy.common.endpoints.http.server.RestServer;
 import org.onap.policy.common.endpoints.http.server.RestServer.Factory;
+import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
 import org.onap.policy.common.endpoints.http.server.aaf.AafAuthFilter;
 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
@@ -211,7 +212,8 @@
         assertEquals(PASS, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_PASSWORD_SUFFIX));
         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HTTPS_SUFFIX));
         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_AAF_SUFFIX));
-        assertEquals(String.join(",", GsonMessageBodyHandler.class.getName(), JsonExceptionMapper.class.getName()),
+        assertEquals(String.join(",", GsonMessageBodyHandler.class.getName(), YamlMessageBodyHandler.class.getName(),
+                        JsonExceptionMapper.class.getName()),
                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER));
     }
 
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/YamlMessageBodyHandlerTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/YamlMessageBodyHandlerTest.java
new file mode 100644
index 0000000..b8963e9
--- /dev/null
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/YamlMessageBodyHandlerTest.java
@@ -0,0 +1,230 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.http.server.test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import javax.ws.rs.core.MediaType;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
+
+public class YamlMessageBodyHandlerTest {
+    private static final String EXPECTED_EXCEPTION = "expected exception";
+
+    private static final String GEN_TYPE = "some-type";
+    private static final String[] subtypes = {"yaml"};
+
+    @SuppressWarnings("rawtypes")
+    private static final Class GEN_CLASS = MyObject.class;
+
+    @SuppressWarnings("unchecked")
+    private static final Class<Object> CLASS_OBJ = GEN_CLASS;
+
+    private YamlMessageBodyHandler hdlr;
+
+    @Before
+    public void setUp() {
+        hdlr = new YamlMessageBodyHandler();
+    }
+
+    @Test
+    public void testIsWriteable() {
+        for (String subtype : subtypes) {
+            assertTrue("writeable " + subtype, hdlr.isWriteable(null, null, null, new MediaType(GEN_TYPE, subtype)));
+
+        }
+
+        // the remaining should be FALSE
+
+        // null media type
+        assertFalse(hdlr.isWriteable(null, null, null, null));
+
+        // null subtype
+        assertFalse(hdlr.isWriteable(null, null, null, new MediaType(GEN_TYPE, null)));
+
+        // text subtype
+        assertFalse(hdlr.isWriteable(null, null, null, MediaType.TEXT_HTML_TYPE));
+    }
+
+    @Test
+    public void testGetSize() {
+        assertEquals(-1, hdlr.getSize(null, null, null, null, null));
+    }
+
+    @Test
+    public void testWriteTo_testReadFrom() throws Exception {
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        MyObject obj1 = new MyObject(10);
+        hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr);
+
+        Object obj2 = hdlr.readFrom(CLASS_OBJ, CLASS_OBJ, null, null, null,
+                        new ByteArrayInputStream(outstr.toByteArray()));
+        assertEquals(obj1.toString(), obj2.toString());
+    }
+
+    @Test
+    public void testWriteTo_DifferentTypes() throws Exception {
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+
+        // use a derived type, but specify the base type when writing
+        MyObject obj1 = new DerivedObject(10);
+        hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr);
+
+        Object obj2 = hdlr.readFrom(CLASS_OBJ, CLASS_OBJ, null, null, null,
+                        new ByteArrayInputStream(outstr.toByteArray()));
+        assertEquals(obj1.toString(), obj2.toString());
+    }
+
+    @Test
+    public void testWriteTo_Ex() throws Exception {
+        OutputStream outstr = new OutputStream() {
+            @Override
+            public void write(int value) throws IOException {
+                throw new IOException(EXPECTED_EXCEPTION);
+            }
+        };
+
+        MyObject obj1 = new MyObject(10);
+
+        assertThatThrownBy(() -> hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr))
+                        .isInstanceOf(IOException.class);
+
+        outstr.close();
+    }
+
+    @Test
+    public void testIsReadable() {
+        for (String subtype : subtypes) {
+            assertTrue("readable " + subtype, hdlr.isReadable(null, null, null, new MediaType(GEN_TYPE, subtype)));
+
+        }
+
+        // the remaining should be FALSE
+
+        // null media type
+        assertFalse(hdlr.isReadable(null, null, null, null));
+
+        // null subtype
+        assertFalse(hdlr.isReadable(null, null, null, new MediaType(GEN_TYPE, null)));
+
+        // text subtype
+        assertFalse(hdlr.isReadable(null, null, null, MediaType.TEXT_HTML_TYPE));
+    }
+
+    @Test
+    public void testReadFrom_DifferentTypes() throws Exception {
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        MyObject obj1 = new MyObject(10);
+        hdlr.writeTo(obj1, obj1.getClass(), CLASS_OBJ, null, null, null, outstr);
+
+        // use a derived type, but specify the base type when reading
+        @SuppressWarnings("rawtypes")
+        Class clazz = DerivedObject.class;
+
+        @SuppressWarnings("unchecked")
+        Class<Object> objclazz = clazz;
+
+        Object obj2 = hdlr.readFrom(objclazz, CLASS_OBJ, null, null, null,
+                        new ByteArrayInputStream(outstr.toByteArray()));
+        assertEquals(obj1.toString(), obj2.toString());
+    }
+
+    @Test
+    public void testReadFrom_Ex() throws Exception {
+        InputStream inpstr = new InputStream() {
+            @Override
+            public int read() throws IOException {
+                throw new IOException(EXPECTED_EXCEPTION);
+            }
+        };
+
+        assertThatThrownBy(() -> hdlr.readFrom(CLASS_OBJ, CLASS_OBJ, null, null, null, inpstr))
+                        .isInstanceOf(IOException.class);
+
+        inpstr.close();
+    }
+
+    @Test
+    public void testMapDouble() throws Exception {
+        MyMap map = new MyMap();
+        map.props = new HashMap<>();
+        map.props.put("plainString", "def");
+        map.props.put("negInt", -10);
+        map.props.put("doubleVal", 12.5);
+        map.props.put("posLong", 100000000000L);
+
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        hdlr.writeTo(map, map.getClass(), map.getClass(), null, null, null, outstr);
+
+        Object obj2 = hdlr.readFrom(Object.class, map.getClass(), null, null, null,
+                        new ByteArrayInputStream(outstr.toByteArray()));
+        assertEquals(map.toString(), obj2.toString());
+
+        map = (MyMap) obj2;
+
+        assertEquals(-10, map.props.get("negInt"));
+        assertEquals(100000000000L, map.props.get("posLong"));
+        assertEquals(12.5, map.props.get("doubleVal"));
+    }
+
+    public static class DerivedObject extends MyObject {
+        public DerivedObject(int id) {
+            super(id);
+        }
+    }
+
+    public static class MyObject {
+        private int id;
+
+        public MyObject() {
+            super();
+        }
+
+        public MyObject(int id) {
+            this.id = id;
+        }
+
+        @Override
+        public String toString() {
+            return "MyObject [id=" + id + "]";
+        }
+    }
+
+    private static class MyMap {
+        private Map<String, Object> props;
+
+        @Override
+        public String toString() {
+            return props.toString();
+        }
+    }
+}
diff --git a/utils/pom.xml b/utils/pom.xml
index 78bddab..96d9c96 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -34,6 +34,10 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
         </dependency>
diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java
index 80eddb8..d3d6981 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java
@@ -211,6 +211,16 @@
     }
 
     /**
+     * Encodes an object into a json tree, without catching exceptions.
+     *
+     * @param object object to be encoded
+     * @return a json element representing the object
+     */
+    protected JsonElement toJsonTree(Object object) {
+        return GSON.toJsonTree(object);
+    }
+
+    /**
      * Encodes an object into json, without catching exceptions.
      *
      * @param object object to be encoded
@@ -232,6 +242,17 @@
     }
 
     /**
+     * Decodes a json element into an object, without catching exceptions.
+     *
+     * @param json json element to be decoded
+     * @param clazz class of object to be decoded
+     * @return the object represented by the given json element
+     */
+    protected <T> T fromJson(JsonElement json, Class<T> clazz) {
+        return convertFromDouble(clazz, GSON.fromJson(json, clazz));
+    }
+
+    /**
      * Decodes a json string into an object, without catching exceptions.
      *
      * @param json json string to be decoded
diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/StandardYamlCoder.java b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardYamlCoder.java
new file mode 100644
index 0000000..357b106
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardYamlCoder.java
@@ -0,0 +1,264 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.emitter.Emitter;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.SequenceNode;
+import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.resolver.Resolver;
+import org.yaml.snakeyaml.serializer.Serializer;
+
+/**
+ * YAML encoder and decoder using the "standard" mechanism, which is currently gson. All
+ * of the methods perform conversion to/from YAML (instead of JSON).
+ */
+public class StandardYamlCoder extends StandardCoder {
+
+    /**
+     * Constructs the object.
+     */
+    public StandardYamlCoder() {
+        super();
+    }
+
+    @Override
+    protected String toJson(Object object) {
+        StringWriter output = new StringWriter();
+        toJson(output, object);
+        return output.toString();
+    }
+
+    @Override
+    protected void toJson(Writer target, Object object) {
+        DumperOptions dumper = new DumperOptions();
+        Serializer serializer = new Serializer(new Emitter(target, dumper), new Resolver(), dumper, null);
+
+        try {
+            serializer.open();
+            serializer.serialize(makeYaml(toJsonTree(object)));
+            serializer.close();
+
+        } catch (IOException e) {
+            throw new YAMLException(e);
+        }
+    }
+
+    @Override
+    protected <T> T fromJson(String yaml, Class<T> clazz) {
+        Node node = new Yaml().compose(new StringReader(yaml));
+        return fromJson(makeGson(node), clazz);
+    }
+
+    @Override
+    protected <T> T fromJson(Reader source, Class<T> clazz) {
+        Node node = new Yaml().compose(source);
+        return fromJson(makeGson(node), clazz);
+    }
+
+    /**
+     * Converts an arbitrary gson element into a corresponding Yaml node.
+     *
+     * @param jel gson element to be converted
+     * @return a yaml node corresponding to the element
+     */
+    private Node makeYaml(JsonElement jel) {
+        if (jel.isJsonArray()) {
+            return makeYamlSequence((JsonArray) jel);
+
+        } else if (jel.isJsonObject()) {
+            return makeYamlMap((JsonObject) jel);
+
+        } else if (jel.isJsonPrimitive()) {
+            return makeYamlPrim((JsonPrimitive) jel);
+
+        } else {
+            return new ScalarNode(Tag.NULL, "", null, null, DumperOptions.ScalarStyle.PLAIN);
+        }
+    }
+
+    /**
+     * Converts an arbitrary gson array into a corresponding Yaml sequence.
+     *
+     * @param jel gson element to be converted
+     * @return a yaml node corresponding to the element
+     */
+    private Node makeYamlSequence(JsonArray jel) {
+        List<Node> nodes = new ArrayList<>(jel.size());
+        jel.forEach(item -> nodes.add(makeYaml(item)));
+
+        return new SequenceNode(Tag.SEQ, true, nodes, null, null, DumperOptions.FlowStyle.AUTO);
+    }
+
+    /**
+     * Converts an arbitrary gson object into a corresponding Yaml map.
+     *
+     * @param jel gson element to be converted
+     * @return a yaml node corresponding to the element
+     */
+    private Node makeYamlMap(JsonObject jel) {
+        List<NodeTuple> nodes = new ArrayList<>(jel.size());
+
+        for (Entry<String, JsonElement> entry : jel.entrySet()) {
+            Node key = new ScalarNode(Tag.STR, entry.getKey(), null, null, DumperOptions.ScalarStyle.PLAIN);
+            Node value = makeYaml(entry.getValue());
+
+            nodes.add(new NodeTuple(key, value));
+        }
+
+        return new MappingNode(Tag.MAP, true, nodes, null, null, DumperOptions.FlowStyle.AUTO);
+    }
+
+    /**
+     * Converts an arbitrary gson primitive into a corresponding Yaml scalar.
+     *
+     * @param jel gson element to be converted
+     * @return a yaml node corresponding to the element
+     */
+    private Node makeYamlPrim(JsonPrimitive jel) {
+        Tag tag;
+        if (jel.isNumber()) {
+            Class<? extends Number> clazz = jel.getAsNumber().getClass();
+
+            if (clazz == Double.class || clazz == Float.class) {
+                tag = Tag.FLOAT;
+
+            } else {
+                tag = Tag.INT;
+            }
+
+        } else if (jel.isBoolean()) {
+            tag = Tag.BOOL;
+
+        } else {
+            // treat anything else as a string
+            tag = Tag.STR;
+        }
+
+        return new ScalarNode(tag, jel.getAsString(), null, null, DumperOptions.ScalarStyle.PLAIN);
+    }
+
+    /**
+     * Converts an arbitrary Yaml node into a corresponding gson element.
+     *
+     * @param node node to be converted
+     * @return a gson element corresponding to the node
+     */
+    private JsonElement makeGson(Node node) {
+        if (node instanceof MappingNode) {
+            return makeGsonObject((MappingNode) node);
+
+        } else if (node instanceof SequenceNode) {
+            return makeGsonArray((SequenceNode) node);
+
+        } else {
+            return makeGsonPrim((ScalarNode) node);
+        }
+
+        // yaml doesn't appear to use anchor nodes when decoding so ignore them for now
+    }
+
+    /**
+     * Converts a Yaml sequence into a corresponding gson array.
+     *
+     * @param node node to be converted
+     * @return a gson element corresponding to the node
+     */
+    private JsonElement makeGsonArray(SequenceNode node) {
+        List<Node> nodes = node.getValue();
+
+        JsonArray array = new JsonArray(nodes.size());
+        nodes.forEach(subnode -> array.add(makeGson(subnode)));
+
+        return array;
+    }
+
+    /**
+     * Converts a Yaml map into a corresponding gson object.
+     *
+     * @param node node to be converted
+     * @return a gson element corresponding to the node
+     */
+    private JsonElement makeGsonObject(MappingNode node) {
+        JsonObject obj = new JsonObject();
+
+        for (NodeTuple tuple : node.getValue()) {
+            Node key = tuple.getKeyNode();
+            String skey = ((ScalarNode) key).getValue();
+
+            obj.add(skey, makeGson(tuple.getValueNode()));
+        }
+
+        return obj;
+    }
+
+    /**
+     * Converts a Yaml scalar into a corresponding gson primitive.
+     *
+     * @param node node to be converted
+     * @return a gson element corresponding to the node
+     */
+    private JsonElement makeGsonPrim(ScalarNode node) {
+        try {
+            Tag tag = node.getTag();
+
+            if (tag == Tag.INT) {
+                return new JsonPrimitive(Long.valueOf(node.getValue()));
+
+            } else if (tag == Tag.FLOAT) {
+                return new JsonPrimitive(Double.valueOf(node.getValue()));
+
+            } else if (tag == Tag.BOOL) {
+                return new JsonPrimitive(Boolean.valueOf(node.getValue()));
+
+            } else if (tag == Tag.NULL) {
+                return JsonNull.INSTANCE;
+
+            } else {
+                // treat anything else as a string
+                return new JsonPrimitive(node.getValue());
+            }
+
+        } catch (NumberFormatException ex) {
+            // just treat it as a string
+            return new JsonPrimitive(node.getValue());
+        }
+    }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/YamlException.java b/utils/src/main/java/org/onap/policy/common/utils/coder/YamlException.java
new file mode 100644
index 0000000..d6591b3
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/coder/YamlException.java
@@ -0,0 +1,63 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import org.yaml.snakeyaml.error.Mark;
+
+/**
+ * Runtime Exception generated by StandardYamlCoder.
+ */
+public class YamlException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    private final Mark problemMark;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param reason reason for the exception
+     * @param problemMark where the exception occurred within the input
+     */
+    public YamlException(String reason, Mark problemMark) {
+        super(reason);
+
+        this.problemMark = problemMark;
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param reason reason for the exception
+     * @param problemMark where the exception occurred within the input
+     * @param cause cause of the exception
+     */
+    public YamlException(String reason, Mark problemMark, Throwable cause) {
+        super(reason, cause);
+
+        this.problemMark = problemMark;
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + problemMark;
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/coder/StandardYamlCoderTest.java b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardYamlCoderTest.java
new file mode 100644
index 0000000..6ec2066
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardYamlCoderTest.java
@@ -0,0 +1,151 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import org.junit.Before;
+import org.junit.Test;
+
+public class StandardYamlCoderTest {
+    private static final File YAML_FILE =
+                    new File("src/test/resources/org/onap/policy/common/utils/coder/StandardYamlCoder.yaml");
+
+    private StandardYamlCoder coder;
+    private Container cont;
+
+    @Before
+    public void setUp() throws CoderException {
+        coder = new StandardYamlCoder();
+        cont = coder.decode(YAML_FILE, Container.class);
+    }
+
+    @Test
+    public void testToJsonObject() throws CoderException {
+        String yaml = coder.encode(cont);
+
+        Container cont2 = coder.decode(yaml, Container.class);
+        assertEquals(cont, cont2);
+    }
+
+    @Test
+    public void testToJsonWriterObject() throws Exception {
+        IOException ex = new IOException("expected exception");
+
+        // writer that throws an exception when the write() method is invoked
+        Writer wtr = new Writer() {
+            @Override
+            public void write(char[] cbuf, int off, int len) throws IOException {
+                throw ex;
+            }
+
+            @Override
+            public void flush() throws IOException {
+                // do nothing
+            }
+
+            @Override
+            public void close() throws IOException {
+                // do nothing
+            }
+        };
+
+        assertThatThrownBy(() -> coder.encode(wtr, cont)).isInstanceOf(CoderException.class);
+
+        wtr.close();
+    }
+
+    @Test
+    public void testFromJsonStringClassOfT() throws Exception {
+        String yaml = new String(Files.readAllBytes(YAML_FILE.toPath()), StandardCharsets.UTF_8);
+        Container cont2 = coder.decode(yaml, Container.class);
+        assertEquals(cont, cont2);
+    }
+
+    @Test
+    public void testFromJsonReaderClassOfT() {
+        assertNotNull(cont.item);
+        assertTrue(cont.item.boolVal);
+        assertEquals(1000L, cont.item.longVal);
+        assertEquals(1010.1f, cont.item.floatVal, 0.00001);
+
+        assertEquals(4, cont.list.size());
+        assertNull(cont.list.get(1));
+
+        assertEquals(20, cont.list.get(0).intVal);
+        assertEquals("string 30", cont.list.get(0).stringVal);
+        assertNull(cont.list.get(0).nullVal);
+
+        assertEquals(40.0, cont.list.get(2).doubleVal, 0.000001);
+        assertNull(cont.list.get(2).nullVal);
+        assertNotNull(cont.list.get(2).another);
+        assertEquals(50, cont.list.get(2).another.intVal);
+
+        assertTrue(cont.list.get(3).boolVal);
+
+        assertNotNull(cont.map);
+        assertEquals(3, cont.map.size());
+
+        assertNotNull(cont.map.get("itemA"));
+        assertEquals("stringA", cont.map.get("itemA").stringVal);
+
+        assertNotNull(cont.map.get("itemB"));
+        assertEquals("stringB", cont.map.get("itemB").stringVal);
+
+        double dbl = 123456789012345678901234567890.0;
+        assertEquals(dbl, cont.map.get("itemB").doubleVal, 1000.0);
+
+        assertNotNull(cont.map.get("itemC"));
+        assertTrue(cont.map.get("itemC").boolVal);
+    }
+
+
+    @EqualsAndHashCode
+    public static class Container {
+        private Item item;
+        private List<Item> list;
+        private Map<String, Item> map;
+    }
+
+    @EqualsAndHashCode
+    public static class Item {
+        private boolean boolVal;
+        private int intVal;
+        private long longVal;
+        private double doubleVal;
+        private float floatVal;
+        private String stringVal;
+        private Object nullVal;
+        private Item another;
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/coder/YamlExceptionTest.java b/utils/src/test/java/org/onap/policy/common/utils/coder/YamlExceptionTest.java
new file mode 100644
index 0000000..bfa6249
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/coder/YamlExceptionTest.java
@@ -0,0 +1,61 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertSame;
+
+import java.io.IOException;
+import org.junit.Test;
+import org.yaml.snakeyaml.error.Mark;
+
+public class YamlExceptionTest {
+    private static final char[] str = {'d', 'a', 't', 'a'};
+    private static final String DATA = new String(str);
+    private static final String MESSAGE = "hello";
+
+    private static final Mark mark = new Mark("a file", 3, 5, 9, str, 0);
+
+    @Test
+    public void testYamlExceptionStringMark() {
+        YamlException ex = new YamlException(MESSAGE, mark);
+
+        String str = ex.getMessage();
+        assertThat(str).contains(MESSAGE).contains(DATA);
+
+        str = ex.toString();
+        assertThat(str).contains(MESSAGE).contains(DATA);
+    }
+
+    @Test
+    public void testYamlExceptionStringMarkThrowable() {
+        Throwable thr = new IOException("expected exception");
+        YamlException ex = new YamlException(MESSAGE, mark, thr);
+
+        assertSame(thr, ex.getCause());
+
+        String str = ex.getMessage();
+        assertThat(str).contains(MESSAGE).contains(DATA);
+
+        str = ex.toString();
+        assertThat(str).contains(MESSAGE).contains(DATA);
+    }
+}
diff --git a/utils/src/test/resources/org/onap/policy/common/utils/coder/StandardYamlCoder.yaml b/utils/src/test/resources/org/onap/policy/common/utils/coder/StandardYamlCoder.yaml
new file mode 100644
index 0000000..1da7bfa
--- /dev/null
+++ b/utils/src/test/resources/org/onap/policy/common/utils/coder/StandardYamlCoder.yaml
@@ -0,0 +1,21 @@
+---
+item: &top
+  boolVal: true
+  longVal: 1000
+  floatVal: 1010.1
+list:
+- intVal: 20
+  stringVal: string 30
+  nullVal:
+-
+- doubleVal: 40
+  another:
+    intVal: 50
+- *top
+map:
+  itemA:
+    stringVal: stringA
+  itemB:
+    stringVal: stringB
+    doubleVal: 123456789012345678901234567890
+  itemC: *top