diff options
author | Jim Hahn <jrh3@att.com> | 2019-02-28 21:59:33 -0500 |
---|---|---|
committer | Jim Hahn <jrh3@att.com> | 2019-03-01 01:08:42 -0500 |
commit | ddb5911efe46953200be8965ac65ae9bbc8a36ee (patch) | |
tree | 5e328e8205d2076b262943b65ade74584893430d | |
parent | 3849ce18d51e308bbd58b71219472c23b5f3e814 (diff) |
Add StandardCoderObject to hide GSON internals
Added StandardCoderObject to hide GSON's JsonElement so that classes
are not dependent on the use of JsonElement, making it easier to switch
out serialization mechanisms in the future.
Added a test for field-not-found.
Converted tabs to spaces in json test file.
Simplified StandardCoderObject and added methods to Coder for
translating to and from StandardCoderObject.
Removed a test for a method that no longer exists.
Added more tests to a test case.
Change-Id: I5123dc3f17c940ded431ef7f9ccd8c4bff6b1c5f
Issue-ID: POLICY-1444
Signed-off-by: Jim Hahn <jrh3@att.com>
5 files changed, 303 insertions, 1 deletions
diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/Coder.java b/utils/src/main/java/org/onap/policy/common/utils/coder/Coder.java index 66a308f7..bb51f2b9 100644 --- a/utils/src/main/java/org/onap/policy/common/utils/coder/Coder.java +++ b/utils/src/main/java/org/onap/policy/common/utils/coder/Coder.java @@ -106,4 +106,22 @@ public interface Coder { * @throws CoderException if an error occurs */ <T> T decode(File source, Class<T> clazz) throws CoderException; + + /** + * Converts an object/POJO to a standard object. + * + * @param object object to be converted + * @return a new standard object representing the original object + * @throws CoderException if an error occurs + */ + StandardCoderObject toStandard(Object object) throws CoderException; + + /** + * Converts a standard object to an object/POJO. + * + * @param sco the standard object to be converted + * @return a new object represented by the standard object + * @throws CoderException if an error occurs + */ + <T> T fromStandard(StandardCoderObject sco, Class<T> clazz) throws CoderException; } 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 389720f9..69a211b6 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 @@ -21,6 +21,11 @@ package org.onap.policy.common.utils.coder; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -42,7 +47,8 @@ public class StandardCoder implements Coder { /** * Gson object used to encode and decode messages. */ - private static final Gson GSON = new Gson(); + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(StandardCoderObject.class, new StandardTypeAdapter()).create(); /** * Constructs the object. @@ -137,6 +143,26 @@ public class StandardCoder implements Coder { } } + @Override + public StandardCoderObject toStandard(Object object) throws CoderException { + try { + return new StandardCoderObject(GSON.toJsonTree(object)); + + } catch (RuntimeException e) { + throw new CoderException(e); + } + } + + @Override + public <T> T fromStandard(StandardCoderObject sco, Class<T> clazz) throws CoderException { + try { + return GSON.fromJson(sco.getData(), clazz); + + } catch (RuntimeException e) { + throw new CoderException(e); + } + } + // the remaining methods are wrappers that can be overridden by junit tests /** @@ -223,4 +249,32 @@ public class StandardCoder implements Coder { protected <T> T fromJson(Reader source, Class<T> clazz) { return GSON.fromJson(source, clazz); } + + /** + * Adapter for standard objects. + */ + private static class StandardTypeAdapter extends TypeAdapter<StandardCoderObject> { + + /** + * Used to read/write a JsonElement. + */ + private static TypeAdapter<JsonElement> elementAdapter = new Gson().getAdapter(JsonElement.class); + + /** + * Constructs the object. + */ + public StandardTypeAdapter() { + super(); + } + + @Override + public void write(JsonWriter out, StandardCoderObject value) throws IOException { + elementAdapter.write(out, value.getData()); + } + + @Override + public StandardCoderObject read(JsonReader in) throws IOException { + return new StandardCoderObject(elementAdapter.read(in)); + } + } } diff --git a/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoderObject.java b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoderObject.java new file mode 100644 index 00000000..60c5f4ef --- /dev/null +++ b/utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoderObject.java @@ -0,0 +1,93 @@ +/* + * ============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.JsonElement; + +/** + * Object type used by the {@link StandardCoder}. Different serialization tools have + * different "standard objects". For instance, GSON uses {@link JsonElement}. This class + * wraps that object so that it can be used without exposing the object, itself. + */ +public class StandardCoderObject { + + /** + * Data wrapped by this. + */ + private final JsonElement data; + + /** + * Constructs the object. + */ + public StandardCoderObject() { + data = null; + } + + /** + * Constructs the object. + * + * @param data data wrapped by this object. + */ + protected StandardCoderObject(JsonElement data) { + this.data = data; + } + + /** + * Gets the data wrapped by this. + * + * @return the data wrapped by this + */ + protected JsonElement getData() { + return data; + } + + /** + * Gets a field's value from this object, traversing the object hierarchy. + * + * @param fields field hierarchy + * @return the field value or {@code null} if the field does not exist or is not a + * primitive + */ + public String getString(String... fields) { + + /* + * This could be relatively easily modified to allow Integer arguments, as well, + * which would be used to specify indices within an array. + */ + + JsonElement jel = data; + + for (String field : fields) { + if (jel == null) { + return null; + } + + if (jel.isJsonObject()) { + jel = jel.getAsJsonObject().get(field); + + } else { + return null; + } + } + + return (jel != null && jel.isJsonPrimitive() ? jel.getAsString() : null); + } +} diff --git a/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderObjectTest.java b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderObjectTest.java new file mode 100644 index 00000000..44086f30 --- /dev/null +++ b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderObjectTest.java @@ -0,0 +1,89 @@ +/* + * ============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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import org.junit.Before; +import org.junit.Test; + +public class StandardCoderObjectTest { + private static final Gson gson = new Gson(); + + private static final String PROP1 = "abc"; + private static final String PROP2 = "ghi"; + private static final String PROP2b = "jkl"; + private static final String VAL1 = "def"; + private static final String VAL2 = "mno"; + private static final String JSON = "{'abc':'def','ghi':{'jkl':'mno'}}".replace('\'', '"'); + + private StandardCoderObject sco; + + /** + * Creates a standard object, populated with some data. + * + * @throws Exception if an error occurs + */ + @Before + public void setUp() throws Exception { + sco = new StandardCoderObject(gson.fromJson(JSON, JsonElement.class)); + } + + @Test + public void testStandardCoderObject() { + assertNull(new StandardCoderObject().getData()); + } + + @Test + public void testStandardCoderObjectJsonElement() { + assertNotNull(sco.getData()); + assertEquals(JSON, gson.toJson(sco.getData())); + } + + @Test + public void testGetString() throws Exception { + // one field + assertEquals(VAL1, sco.getString(PROP1)); + + // multiple fields + assertEquals(VAL2, sco.getString(PROP2, PROP2b)); + + // not found + assertNull(sco.getString("xyz")); + + // read from null object + assertNull(new StandardCoderObject().getString()); + assertNull(new StandardCoderObject().getString(PROP1)); + + JsonElement obj = gson.fromJson("{'abc':[]}".replace('\'', '"'), JsonElement.class); + sco = new StandardCoderObject(obj); + + // not a primitive + assertNull(sco.getString(PROP1)); + + // not a JSON object + assertNull(sco.getString(PROP1, PROP2)); + } +} diff --git a/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java index 25cce748..7583d776 100644 --- a/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java +++ b/utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java @@ -22,6 +22,7 @@ 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.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -196,4 +197,51 @@ public class StandardCoderTest { assertThatThrownBy(() -> coder.decode(file, JsonElement.class)).isInstanceOf(CoderException.class) .hasCause(ioe); } + + @Test + public void testToStandard() throws Exception { + MyObject obj = new MyObject(); + obj.abc = "xyz"; + StandardCoderObject sco = coder.toStandard(obj); + assertNotNull(sco.getData()); + assertEquals("{'abc':'xyz'}".replace('\'', '"'), sco.getData().toString()); + + // class instead of object -> exception + assertThatThrownBy(() -> coder.toStandard(String.class)).isInstanceOf(CoderException.class); + } + + @Test + public void testFromStandard() throws Exception { + MyObject obj = new MyObject(); + obj.abc = "pdq"; + StandardCoderObject sco = coder.toStandard(obj); + + MyObject obj2 = coder.fromStandard(sco, MyObject.class); + assertEquals(obj.toString(), obj2.toString()); + + // null class -> exception + assertThatThrownBy(() -> coder.fromStandard(sco, null)).isInstanceOf(CoderException.class); + } + + @Test + public void testStandardTypeAdapter() throws Exception { + String json = "{'abc':'def'}".replace('\'', '"'); + StandardCoderObject sco = coder.fromJson(json, StandardCoderObject.class); + assertNotNull(sco.getData()); + assertEquals(json, sco.getData().toString()); + assertEquals(json, coder.toJson(sco)); + + // invalid json -> exception + assertThatThrownBy(() -> coder.fromJson(new StringReader("["), StandardCoderObject.class)); + } + + + private static class MyObject { + private String abc; + + @Override + public String toString() { + return "MyObject [abc=" + abc + "]"; + } + } } |