DynamoDBTypeConverter for DynamoDB document field - java

I have a toy DynamoDB table with a hash key, and in it I want to store some simple items in DynamoDB document format.
Here's the class to represent the document:
#DynamoDBDocument
public class Simple {
private String key;
private String value;
public Simple() {
}
public Simple(
String key,
String value
) {
this.key = key;
this.value = value;
}
#DynamoDBAttribute
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
#DynamoDBAttribute
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
and here's a class to represent the entry in the table:
#DynamoDBDocument
public class SimpleRow {
private String hash;
private Simple entry;
public SimpleRow() {
}
public SimpleRow(
String hash,
Simple entry
) {
this.hash = hash;
this.entry = entry;
}
#DynamoDBHashKey
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
#DynamoDBAttribute
public Simple getEntry() {
return entry;
}
public void setEntry(Simple entry) {
this.entry = entry;
}
}
With this setup, I can save entries to and laod entries from the table without a problem, using a DynamoDBMapper:
DynamoDBMapper mapper = new DynamoDBMapper(
AmazonDynamoDBClientBuilder.standard().build(),
DynamoDBMapperConfig
.builder()
.withTableNameOverride(new DynamoDBMapperConfig.TableNameOverride("simple"))
.build()
);
mapper.save(new SimpleRow("the-hash", new Simple("foo", "bar")));
System.out.println(mapper.load(SimpleRow.class, "the-hash"));
which results in an entry looking like this:
{
"entry": {
"M": {
"key": {
"S": "foo"
},
"value": {
"S": "bar"
}
}
},
"hash": {
"S": "the-key"
}
}
However, I dislike the pollution of the Simple class with setters and zero-argument constructor - it should be an immutable data class. Therefore, I'd like to use a DynamoDBTypeConverter to construct the Simple objects.
The problem is I can't seem to write a converter that works, as I can't find a suitable source type:
Map, which would seem the most natural, fails with DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
String, which would seem to be a reasonable default, doesn't error but the string supplied to the convert() method is always empty
Object, which would seem to be a catch-all, isn't supported by Dynamo and fails with DynamoDBMappingException: could not resolve type of class SimpleConverter
Is it possible to write a DynamoDBTypeConverter for this type? If so, what type should I use? I've trawled the internet for exampels of this but nothing seems forthcoming.

You need to change Simple to Simple[] in SimleRow.java
import java.util.Arrays;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
#DynamoDBDocument
public class SimpleRow {
private String hash;
private Simple[] entry;
public SimpleRow() {
}
public SimpleRow(
String hash,
Simple[] entry
) {
this.hash = hash;
this.entry = entry;
}
#DynamoDBHashKey
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
#DynamoDBAttribute
public Simple[] getEntry() {
return entry;
}
public void setEntry(Simple[] entry) {
this.entry = entry;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SimpleRow [hash=");
builder.append(hash);
builder.append(", entry=");
builder.append(Arrays.toString(entry));
builder.append("]");
return builder.toString();
}
}
And call it in main class as new SimpleRow("the-hash", new Simple[] {new Simple("foo", "bar")})
mapper.save(new SimpleRow("the-hash", new Simple[] {new Simple("foo", "bar")}));

Related

How can I change a converter for testing purpose?

I am currently working on a project and have to deal with some test cases. We implementented cryptographie in our database to learn how it is build but now we have to update the tests. The crypto should be hidden or someway.
This is the converter we used and added #Convert in the entity classes like so
#Column(name ="name", nullable = false)
#Convert(converter = DBEncryptedStringConverter.class)
public EncryptedString getName() {
return this.name;
}
public void setName(EncryptedString name) {
this.name = name;
}
#Converter
#Slf4j
public class DBEncryptedStringConverter implements AttributeConverter<EncryptedString, String> {
private static final String ENC = "ENC__##__", RAW = "RAW__##__";
#Override
public String convertToDatabaseColumn(EncryptedString attribute) {
if(attribute == null) return null;
return (attribute.isEncrypted() ? ENC : RAW) + attribute.getString();
}
#Override
public EncryptedString convertToEntityAttribute(String dbData) {
if(dbData == null) return null;
if(dbData.startsWith(ENC)) {
return new EncryptedString(dbData.substring(ENC.length()), true);
} else if(dbData.startsWith(RAW)) {
return new EncryptedString(dbData.substring(RAW.length()), false);
} else {
log.warn("DB Entry without prefix found");
log.warn("Treating as RAW");
return new EncryptedString(dbData, false);
}
}
}
I would like to use this class for testing
#Converter
public class MockDBEncryptedStringConverter implements AttributeConverter<EncryptedString, String> {
#Override
public String convertToDatabaseColumn(EncryptedString attribute) {
return attribute.getString();
}
#Override
public EncryptedString convertToEntityAttribute(String dbData) {
return new EncryptedString(dbData, false);
}
}
I think mokcing with mokito would be a way to go, but I dont quiet understand how to use it

How can I combine polymorphism with unwrapping in Jackson?

I'm writing annotated model classes for json serialization/deserialization using jackson.
I have a json that contains a map, where the key is an enum and the value can be different types (including arrays) depending on key value.
A simplified example, this is what I need:
{
"key1": "string value",
"key2": [{"id":"1", "value": "test1"}, {"id":"2", "value": "test2"}]
}
I have tried, and I get this:
{
"KEY1": {"value": "string value"},
"KEY2": {"list": [{"id": "1", "value": "test1"}, {"id": "2", "value": "test2"}]}
}
So, unwrapping does not work.
Could anyone tell me what I am doing wrong ?
Here is the code:
public class Main {
public static void main(String[] args) throws Exception {
HashMap<Keys, ValueType> map = new HashMap<>();
map.put(Keys.KEY1, new StringValue("string value"));
map.put(Keys.KEY2, new ListValue( Arrays.asList(new Element[] {
new Element("1", "test1"),
new Element("2", "test2")
} )));
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(map);
System.out.println(s);
}
}
public enum Keys {
KEY1("key1"),
KEY2("key2");
private String value;
Keys(String s) {
this.value = s;
}
}
public interface ValueType {
}
public class StringValue implements ValueType {
#JsonUnwrapped
private String value;
public StringValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class ListValue implements ValueType {
#JsonUnwrapped
private List<Element> list;
public ListValue(List<Element> list) {
this.list = list;
}
public List<Element> getList() {
return list;
}
public void setList(List<Element> list) {
this.list = list;
}
}
public class Element {
#JsonProperty
private String id;
#JsonProperty
private String value;
public Element(String id, String value) {
this.id = id;
this.value = value;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
You can annotate the classes' getters methods with the JsonValue annotation that indicates that the value of annotated accessor is to be used as the single value to serialize for the instance, instead of the usual method of collecting properties of value:
public class StringValue implements ValueType {
#JsonUnwrapped
private String value;
public StringValue(String value) {
this.value = value;
}
#JsonValue //<-- added annotation to the original getter method
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class ListValue implements ValueType {
#JsonUnwrapped
private List<Element> list;
public ListValue(List<Element> list) {
this.list = list;
}
#JsonValue //<-- added annotation to the original getter method
public List<Element> getList() {
return list;
}
public void setList(List<Element> list) {
this.list = list;
}
}

Return object from a list as CustomObject not just raw <T> Object

I have a CustomObject declared as raw type of <T>. And when I populate a List<CustomObject> with new instances of it, I can't get them back as a CustomObject, only as an Object.
public class CustomObject<T> {
private String name;
private T value;
// getters and setters
}
But obviously when I use subclass, all is working as expecting;
public class CustomObject {
private class SubCustomObject<T> {
private String name;
private T value;
}
public CustomObject() {
this.customObject = new SubCustomObject();
private SubCustomObject customObject;
// getters and setters
}
Is there a way to make the first example to behave like the second one, and avoid using extra object and so I could do this:
public class CustomObject<T> {
private String name;
private T value;
private boolean isGroup;
// getters and setters
private void setValue(T value) {
if (value instanceof String) {
this.value = value;
this.isGroup = false;
}
if (value instanceof CustomObject) {
if (isGroup()) {
((List<CustomObject>) this.value).add((CustomObject) value);
} else {
this.value = (T) new ArrayList<CustomObject>();
this.isGroup = true;
setValue(value);
}
}
}
}
public void getItemByName(String name) {
// say the list is already populated
for (CustomObject object : listOfCustomObject) {
String nameField = object.getName();
if (name.equals(nameField) {
System.out.println(nameField);
}
}
}
Instead of this one:
public void getItemByName(String name) {
// say the list is already populated
for (Object object : listOfCustomObject) {
String nameField = ((CustomObject)object).getName();
if (name.equals(nameField) {
System.out.println(nameField);
}
}
}
// Apply that behavior to this and avoid to use inner class.
public class MetadataEntry {
public MetadataEntry() {
this.entity = new Entry();
}
private class Entry<T> {
private String name;
private T value;
private boolean isGroup;
private void setValue(T value) {
if (value instanceof String) {
this.value = value;
this.isGroup = false;
}
if (value instanceof MetadataEntry) {
if (isGroup()) {
((List<MetadataEntry>) this.value).add((MetadataEntry) value);
} else {
this.value = (T) new ArrayList<MetadataEntry>();
this.isGroup = true;
setValue(value);
}
}
}
}
private Entry entity;
public void setName(String name) {
this.entity.name = name;
}
public String getName() {
return this.entity.name;
}
public void setValue(String value) {
entity.setValue(value);
}
public void setValue(MetadataEntry value) {
entity.setValue(value);
}
public boolean isGroup() {
return this.entity.isGroup;
}
public List<MetadataEntity> getChildNodes() {
if (isGroup()) {
return (List<MetadataEntry>) this.entity.value;
}
return null;
}
public String getValue() {
if (!isGroup()) {
return (String) this.entity.value;
}
return null;
}
}
You can not make a list of different types X,Y,Z and put it in a single container of type W. You need to define a bounding parameter on your raw type so that your items and list are of same type. probably your T should be bounded by some interface type or it should extends some class.
Here’s my suggestion. I have abandoned the generics. Instead of just one inner class there is now an abstract inner class with two subclasses, one for groups and one for entries that are not groups. The good news: no cast is necessary anywhere.
public class MetadataEntry {
private String name;
static abstract class Entry {
abstract Entry setValue(String value);
abstract Entry setValue(MetadataEntry value);
abstract boolean isGroup();
abstract List<MetadataEntry> getChildNodes();
abstract String getSimpleValue();
}
static class SimpleEntry extends Entry {
private String value;
public SimpleEntry(String value) {
this.value = value;
}
#Override
Entry setValue(String value) {
this.value = value;
return this;
}
#Override
Entry setValue(MetadataEntry value) {
return new GroupEntry(value);
}
#Override
public boolean isGroup() {
return false;
}
#Override
public List<MetadataEntry> getChildNodes() {
return null;
}
#Override
public String getSimpleValue() {
return value;
}
}
static class GroupEntry extends Entry {
List<MetadataEntry> value;
public GroupEntry(MetadataEntry value) {
this.value = new ArrayList<>();
this.value.add(value);
}
#Override
Entry setValue(String value) {
return new SimpleEntry(value);
}
#Override
Entry setValue(MetadataEntry value) {
this.value.add(value);
return this;
}
#Override
public boolean isGroup() {
return true;
}
#Override
public List<MetadataEntry> getChildNodes() {
return value;
}
#Override
public String getSimpleValue() {
return null;
}
}
private Entry entity;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setValue(String value) {
entity = entity.setValue(value);
}
public void setValue(MetadataEntry value) {
entity = entity.setValue(value);
}
public boolean isGroup() {
return this.entity.isGroup();
}
public List<MetadataEntry> getChildNodes() {
return entity.getChildNodes();
}
public String getValue() {
return entity.getSimpleValue();
}
}
I have used an idea similar to what m 1987 said about a class that returns an instance of itself. I applied it to the inner classes only to free the users of the outer class from caring about this trickery. If you prefer, I am sure it could be applied to the outer class instead. Then you would have an abstrat class on the outer level with two subclasses, and would no longer need the inner classes. This is one of the things you asked for, so you may prefer it, but it comes at a cost: anyone calling setValue() on the outer class would need to remember that they got a new instance back.
I have a CustomObject declared as raw type of <T>.
That doesn't makes sense. You either have a raw type or a generic, not a raw type of a generic.
And when I populate a List with new instances of it, I can't get them back as a CustomObject, only as an Object.
Because your list is not generic (always bad). When you declare a List<Something> it will return Something on a get call. That Something can be generic or a raw type. A List<CustomObject<String>> will not accept a CustomObject<Integer> and using the raw type List<CustomObject> will end in disaster, hence the danger in raw types.
Now let's look at your code. The class
public class CustomObject<T> {
private String name;
private T value;
}
defines an object that behaves the same for any type. In essence what you have here is just a glorified Object with a String serving as its name attached to it.
However, now you do
private void setValue(T value) {
if (value instanceof String)
// ...
if (value instanceof CustomObject)
// ...
}
which separates the behavior for different types. and what happens if the generic type is not a String or a CustomObject?
Let's try to solve your problem. Since generics are meant to unify the behavior, let's look at what the unified behavior is that you're trying to get:
public void getItemByName(String name) {
for (CustomObject object : listOfCustomObject) {
String nameField = object.getName();
// ...
}
}
}
Basically, you require that all the items in listOfCustomObject implement a String getName() method. That's it as far as I can see from your question. That means that your CustomObject<T> should either implement an interface or extend a class (call it Superthing) with that method. Then you will just declare your list as List<? extends Superthing>.
As for the CustomObject itself, it doesn't need to be generic as you hint that there are only 2 types of generics you want to deal with (you have 2 ifs, but no else to deal with a general case). It looks like what you want are 2 different classes with different behaviors that both implement or extend a common supertype.
Try something like this:
abstract class AbstractEntry {
private String name;
protected boolean isGroup;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isGroup() {
return isGroup;
}
}
class MetaEntry extends AbstractEntry {
AbstractEntry value;
MetaEntry(AbstractEntry value) {
this.value = value;
// handle isGroup
}
public void setValue(AbstractEntry value) {
this.value = value;
}
public AbstractEntry getValue() {
if (!isGroup)
return value;
return null;
}
}
class StringEntry extends AbstractEntry {
String value;
StringEntry(String value) {
this.value = value;
isGroup = false;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
I think there is no need of list as it always hold only one element. As #Ole V.V mentioned, the requirement naturally calls for the use of composition and in fact, generic does not fit into your requirements. Here is how I would tackle your requirements:
public interface Named {
public String getName();
public String getValue();
}
public class CustomObject implements Named {
private String name;
private String value;
private boolean isGroup;
// getters and setters
private boolean isGroup() {
return isGroup;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class CustomObject2 implements Named {
private String name;
private CustomObject value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value.getValue();
}
public void setValue(CustomObject value) {
this.value = value;
}
}
public class DriverCustomObject {
public static void main(String arg[]) {
CustomObject t = new CustomObject();
t.setName("key1");
t.setValue("value1");
CustomObject2 t2 = new CustomObject2();
t2.setName("complex");
t2.setValue(t);
List<Named> list = new ArrayList<Named>();
list.add(t);
list.add(t2);
for (Named l : list) {
System.out.println(l.getName());
System.out.println(l.getValue());
}
}
}

How to deal with JSON response with same key "Value" its String and Array with Retrofit

In JSON Response there is key "Value" but its response have multiple forms like String and Array with same key "Value".
So how to make Retrofit model class to maintain String and Array with same key "Value".
{
"RespCode":"SUCCESS",
"RespText":"Transaction Details",
"Data":{
"Record":[
{
"group_title":"Seller Information",
"group_values":[
{
"key":"Listing Agent",
"value":[
{
"key":"Agent First Name",
"value":"Myks"
},
{
"key":"Agent Last Name",
"value":"Joe"
},
{
"key":"Company",
"value":"bdfjdlfdf"
},
{
"key":"Phone",
"value":"712.336.4967"
},
{
"key":"Email",
"value":"abc#gmail.com"
}
]
},
{
"key":"Cell Phone",
"value":"012.345.6789"
},
{
"key":"Email",
"value":"balt#gmail.com.com"
},
{
"key":"Preferred Contact Method",
"value":"Phone"
}
]
},
]
}
}
Just use an arraylist that contains multiple hashmaps maybe? Or... You have to define a pojo that has list of arrays with type map or something to that effect
Check this:
public class ModelBean {
private String RespCode;
private String RespText;
private DataBean Data;
public String getRespCode() {
return RespCode;
}
public void setRespCode(String RespCode) {
this.RespCode = RespCode;
}
public String getRespText() {
return RespText;
}
public void setRespText(String RespText) {
this.RespText = RespText;
}
public DataBean getData() {
return Data;
}
public void setData(DataBean Data) {
this.Data = Data;
}
public static class DataBean {
private List<RecordBean> Record;
public List<RecordBean> getRecord() {
return Record;
}
public void setRecord(List<RecordBean> Record) {
this.Record = Record;
}
public static class RecordBean {
private String group_title;
private List<GroupValuesBean> group_values;
public String getGroup_title() {
return group_title;
}
public void setGroup_title(String group_title) {
this.group_title = group_title;
}
public List<GroupValuesBean> getGroup_values() {
return group_values;
}
public void setGroup_values(List<GroupValuesBean> group_values) {
this.group_values = group_values;
}
public static class GroupValuesBean {
private String key;
private List<ValueBean> value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public List<ValueBean> getValue() {
return value;
}
public void setValue(List<ValueBean> value) {
this.value = value;
}
public static class ValueBean {
private String key;
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
}
}
}

Saving special characters in Enum data type

I would like to store and retrieve special characters (/, * etc) into/from DB table. This is my attempt:
public enum SpecialCharacters {
Hack("H/K"), Gk("G*M");
private String value;
private SpecialCharacters(String value) {
this.value = value;
}
public String toString() {
return this.value; // This will return , # or +
}
}
#Column(name = "Special_Char")
#Enumerated(EnumType.STRING)
private SpecialCharacters specialCharacters;
...
But in DB table it stores only enum field Names like Hack and Gk, not H/K and G*M
With the #Enumerated Annotation you are only able to store the name or ordinal of your enum. With JPA 2.1 you can use the #Converter Annotation.
See Chapter 11.1.10 of the specification.
Example:
Enum:
public enum SpecialCharacter {
Hack("H/K"),
Gk("G*M");
private String value;
private SpecialCharacter(String value) {
this.value = value;
}
public String toString() {
return this.value; // This will return , # or +
}
public static SpecialCharacter valueOfKey(String key) {
for (SpecialCharacter specialCharacter : values()) {
if (specialCharacter.toString().equals(key)) {
return specialCharacter;
}
}
throw new IllegalArgumentException("Illegal key");
}
}
ConverterClass:
#Converter(autoApply = true)
public class SpecialCharacterConverter implements AttributeConverter<SpecialCharacter, String> {
/**
* Converts a {#link SpecialCharacter} to the correspondig String that is used in the database
*/
#Override
public String convertToDatabaseColumn(SpecialCharacter specialCharacter) {
return specialCharacter.toString();
}
/**
* Converts a String from the database to the corresponding {#link SpecialCharacter}.
*/
#Override
public SpecialCharacter convertToEntityAttribute(String dbValue) {
return SpecialCharacter.valueOfKey(dbValue);
}
}
Entity-Object:
#Entity
public class SomeEntity {
#Column(name = "Special_Char")
private SpecialCharacter specialCharacter;
public SpecialCharacter getSpecialCharacter() {
return this.specialCharacter;
}
public void setSpecialCharacter(SpecialCharacter specialCharacter) {
this.specialCharacter = specialCharacter;
}
}

Categories