Skip to content

Commit

Permalink
Merge pull request #31 from hawkular/mazz/hawkular-477
Browse files Browse the repository at this point in the history
HAWKULAR-477  custom objectmapper for basic message subclasses
  • Loading branch information
jmazzitelli committed Jul 23, 2015
2 parents 7f64845 + 324deef commit 79b7b15
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
*/
package org.hawkular.bus.common;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Basic information that is sent over the message bus.
Expand Down Expand Up @@ -57,32 +57,66 @@ public abstract class BasicMessage {
* @return the message object that was represented by the JSON string
*/
public static <T extends BasicMessage> T fromJSON(String json, Class<T> clazz) {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Method buildObjectMapperForDeserializationMethod = findBuildObjectMapperForDeserializationMethod(clazz);
final ObjectMapper mapper = (ObjectMapper) buildObjectMapperForDeserializationMethod.invoke(null);
return mapper.readValue(json, clazz);
} catch (IOException e) {
} catch (Exception e) {
throw new IllegalStateException("JSON message cannot be converted to object.", e);
}
}

private static Method findBuildObjectMapperForDeserializationMethod(Class<? extends BasicMessage> clazz) {
try {
Method m = clazz.getDeclaredMethod("buildObjectMapperForDeserialization");
return m;
} catch (Exception e) {
// the given subclass doesn't have a method to build a mapper, maybe its superclass does.
// eventually we'll get to the BasicMessage class and we know it does have one.
return findBuildObjectMapperForDeserializationMethod((Class<? extends BasicMessage>) clazz.getSuperclass());
}
}

/**
* This is static, so really there is no true overriding it in subclasses.
* However, fromJSON will do the proper reflection in order to invoke
* the proper method for the class that is being deserialized. So subclasses
* that want to provide their own ObjectMapper will define their own
* method that matches the signature of this method and it will be used.
*
* @return object mapper to be used for deserializing JSON.
*/
protected static ObjectMapper buildObjectMapperForDeserialization() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}

/**
* Converts this message to its JSON string representation.
*
* @return JSON encoded data that represents this message.
*/
public String toJSON() {
final ObjectMapper mapper = buildObjectMapperForSerialization();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Object cannot be parsed as JSON.", e);
}
}

/**
* @return object mapper to be used to serialize a message to JSON
*/
protected ObjectMapper buildObjectMapperForSerialization() {
final ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Object cannot be parsed as JSON.", e);
}
return mapper;
}

protected BasicMessage() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.
*/
package org.hawkular.bus.common;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class BasicMessageObjectMapperTest {

@Test
public void testWithGetterSetterSupport() {
SomeMessage.FAIL_ON_UNKNOWN_PROPERTIES = true;
SomeMessage.SUPPORT_GETTER_SETTER = true;

AnotherMessage msg = new AnotherMessage("1", "2");
msg.setSomeAttrib("someValue");
msg.setAnotherAttrib("anotherValue");
assertNotNull(msg.getOne());
assertNotNull(msg.getTwo());
assertNotNull(msg.getSomeAttrib());
assertNotNull(msg.getAnotherAttrib());

String json = msg.toJSON();
System.out.println(json);
assertNotNull("missing JSON", json);

AnotherMessage msg2 = AnotherMessage.fromJSON(json, AnotherMessage.class);
assertNotNull("JSON conversion failed", msg2);
assertNotSame(msg, msg2);
assertNotNull(msg2.getOne());
assertNotNull(msg2.getTwo());
assertNotNull(msg2.getSomeAttrib());
assertNotNull(msg2.getAnotherAttrib());
assertEquals(msg.getOne(), msg2.getOne());
assertEquals(msg.getTwo(), msg2.getTwo());
assertEquals(msg.getSomeAttrib(), msg2.getSomeAttrib());
assertEquals(msg.getAnotherAttrib(), msg2.getAnotherAttrib());
}

@Test
public void testWithoutGetterSetterSupport() {
SomeMessage.FAIL_ON_UNKNOWN_PROPERTIES = true;
SomeMessage.SUPPORT_GETTER_SETTER = false;

AnotherMessage msg = new AnotherMessage("1", "2");
msg.setSomeAttrib("someValueNoSupport");
msg.setAnotherAttrib("anotherValueNoSupport");
assertNotNull(msg.getOne());
assertNotNull(msg.getTwo());
assertNotNull(msg.getSomeAttrib());
assertNotNull(msg.getAnotherAttrib());

String json = msg.toJSON();
System.out.println(json);
assertNotNull("missing JSON", json);

AnotherMessage msg2 = AnotherMessage.fromJSON(json, AnotherMessage.class);
assertNotNull("JSON conversion failed", msg2);
assertNotSame(msg, msg2);
assertNotNull(msg2.getOne());
assertNotNull(msg2.getTwo());
assertNull("Should not have been deserialized, getter/setter support was off", msg2.getSomeAttrib());
assertNull("Should not have been deserialized, getter/setter support was off", msg2.getAnotherAttrib());
assertEquals(msg.getOne(), msg2.getOne());
assertEquals(msg.getTwo(), msg2.getTwo());
assertNotEquals(msg.getSomeAttrib(), msg2.getSomeAttrib());
assertNotEquals(msg.getAnotherAttrib(), msg2.getAnotherAttrib());
}

@Test
public void testOverrideStaticDeserializingMapper() {
// This tests that BasicMessage is able to get the subclass' overriding ObjectMapper for deserialization
// which is obtained by invoking a static method on the subclass.
SomeMessage.SUPPORT_GETTER_SETTER = false;
String jsonWithAllKnownProperties = "{\"one\":\"1\",\"two\":\"2\"}";
String jsonWithUnknownProperties = "{\"one\":\"1\",\"two\":\"2\", \"wot\":\"gorilla\"}";

AnotherMessage msg;

// because we will not fail on unknown properties, no failures should occur
SomeMessage.FAIL_ON_UNKNOWN_PROPERTIES = false;
msg = AnotherMessage.fromJSON(jsonWithAllKnownProperties, AnotherMessage.class);
assertEquals("1", msg.getOne());
assertEquals("2", msg.getTwo());
assertNull(msg.getSomeAttrib());
assertNull(msg.getAnotherAttrib());

msg = AnotherMessage.fromJSON(jsonWithUnknownProperties, AnotherMessage.class);
assertEquals("1", msg.getOne());
assertEquals("2", msg.getTwo());
assertNull(msg.getSomeAttrib());
assertNull(msg.getAnotherAttrib());

// now we will fail on unknown properties
SomeMessage.FAIL_ON_UNKNOWN_PROPERTIES = true;
msg = AnotherMessage.fromJSON(jsonWithAllKnownProperties, AnotherMessage.class);
assertEquals("1", msg.getOne());
assertEquals("2", msg.getTwo());
assertNull(msg.getSomeAttrib());
assertNull(msg.getAnotherAttrib());

try {
msg = AnotherMessage.fromJSON(jsonWithUnknownProperties, AnotherMessage.class);
fail("Custom mapper should not have been able to deserialize this.");
} catch (Exception ok) {
}
}

}

class SomeMessage extends BasicMessage {
// we'll flip this in our tests
public static boolean FAIL_ON_UNKNOWN_PROPERTIES = false;
public static boolean SUPPORT_GETTER_SETTER = true;

public String one;

// this will be included in the JSON due to its getter/setter
private String someAttrib;

public SomeMessage() {
}

public SomeMessage(String one) {
this.one = one;
}

public String getOne() {
return this.one;
}

public String getSomeAttrib() {
return this.someAttrib;
}

public void setSomeAttrib(String value) {
this.someAttrib = value;
}

protected static ObjectMapper buildObjectMapperForDeserialization() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}

@Override
protected ObjectMapper buildObjectMapperForSerialization() {
final ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.PUBLIC_ONLY)
.withGetterVisibility(
(SUPPORT_GETTER_SETTER)
? JsonAutoDetect.Visibility.PUBLIC_ONLY
: JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(
(SUPPORT_GETTER_SETTER)
? JsonAutoDetect.Visibility.PUBLIC_ONLY
: JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
return mapper;
}
}

class AnotherMessage extends SomeMessage {

public String two;

// if our superclass supports getter/setter JSON, this will be included in the JSON
private String anotherAttrib;

public AnotherMessage() {
}

public AnotherMessage(String one, String two) {
super(one);
this.two = two;
}

public String getTwo() {
return this.two;
}

public String getAnotherAttrib() {
return this.anotherAttrib;
}

public void setAnotherAttrib(String value) {
this.anotherAttrib = value;
}
}

0 comments on commit 79b7b15

Please sign in to comment.