Passing anonymous type to annotation - java

I want to pass an anonymous type to an annotation. As I dont know how to explain my problem in words properly, I'll just explain my intention and my code.
I'm currently writing a serializer/deserializer for Java to JSON. Every class then needs to implement a specific interface to be serializable and every field also needs an annotation, so my serializer knows which fields to serialize.
#Retention(RetentionPolicy.RUNTIME)
#Target(FIELD)
public #interface SerializeField {
Class<? extends Formatter> formatter();
}
Now i added the ability to specify a formatter that generates a string for a field.
public interface Formatter<T> {
public abstract String format(T object);
}
This works fine when I just create a new class that implements Formatter but does not work when I create an anonymous class or use a lambda expression.
public class Test implements Javonable {
//This does not work
Formatter<BigDecimal> bigDecimalFormatter = (BigDecimal object) -> object.toString();
#SerializeField(formatter = bigDecimalFormatter.getClass())
private BigDecimal bigDecimal; // ^ The value for annotation attribute SerializeField.formatter must be a class literal
//This does work
#SerializeField(formatter = BigIntegerFormatter.class)
private BigInteger bigInteger;
public class BigIntegerFormatter implements Formatter<BigInteger> {
#Override
public String format(BigInteger object) {
return object.toString();
}
}
}
Is there something that I don't see or is there a solution for that? In the end I just want to be able to pass a Formatter as simple as possible without having to create a new class each time.

Related

How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?

I am trying to implement a simple java event-handler lambda for AWS. It receives sqs events and should make appropriate updates to the dynamoDB table.
One of the attributes in this table is a status field that has 4 defined states; therefore I wanted to use an enum class in java and map it to this attribute.
Under AWS SDK v1 I could use the #DynamoDBTypeConvertedEnum annotation. But it does not exist anymore in v2. Instead, there is the #DynamoDbConvertedBy() which receives a converter class reference. There is also an EnumAttributeConverter class which should work nicely with it.
But for some reason, it does not work. The following is a snip from my current code:
#Data
#DynamoDbBean
#NoArgsConstructor
public class Task{
#Getter(onMethod_ = {#DynamoDbPartitionKey})
String id;
...
#Getter(onMethod_ = {#DynamoDbConvertedBy(EnumAttributeConverter.class)})
ExportTaskStatus status;
}
The enum looks as follows:
#RequiredArgsConstructor
public enum TaskStatus {
#JsonProperty("running") PROCESSING(1),
#JsonProperty("succeeded") COMPLETED(2),
#JsonProperty("cancelled") CANCELED(3),
#JsonProperty("failed") FAILED(4);
private final int order;
}
With this, I get the following exception when launching the application:
Class 'class software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter' appears to have no default constructor thus cannot be used with the BeanTableSchema
For anyone else coming here, it looks do me like just dropping the annotation from the enum altogether works just fine, i.e. the SDK applies the provided attribute converters implicitly. This is also mentioned in this Github issue. My own class looks like this (Brand is an enum here), and the enum is converted without any issues when fetching items.
#Value
#Builder(toBuilder = true)
#DynamoDbImmutable(builder = User.UserBuilder.class)
public class User {
#Getter(onMethod = #__({#DynamoDbPartitionKey}))
String id;
Brand brand;
...
}
How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?
Although the documentation doesn't state it, the DynamoDbConvertedBy annotation requires any AttriuteConverter you supply to contain a parameterles default constructor
Unfortunately for you and me, whoever wrote many of the built-in AttributeConverter classes decided to use static create() methods to instantiate them instead of a constructor (maybe they're singletons under the covers? I don't know). This means anyone who wants to use these helpful constructor-less classes like InstantAsStringAttributeConverter and EnumAttributeConverter needs to wrap them in custom wrapper classes that simple parrot the converters we instantiated using create. For a non-generic typed class like InstantAsStringAttributeConverter, this is easy. Just create an wrapper class that parrots the instance you new up with create() and refer to that instead:
public class InstantAsStringAttributeConverterWithConstructor implements AttributeConverter<Instant> {
private final static InstantAsStringAttributeConverter CONVERTER = InstantAsStringAttributeConverter.create();
#Override
public AttributeValue transformFrom(Instant instant) {
return CONVERTER.transformFrom(instant);
}
#Override
public Instant transformTo(AttributeValue attributeValue) {
return CONVERTER.transformTo(attributeValue);
}
#Override
public EnhancedType<Instant> type() {
return CONVERTER.type();
}
#Override
public AttributeValueType attributeValueType() {
return CONVERTER.attributeValueType();
}
}
Then you update your annotation to point to that class intead of the actual underlying library class.
But wait, EnumAttributeConverter is a generic typed class, which means you need to go one step further. First, you need to create a version of the converter that wraps the official version but relies on a constructor taking in the type instead of static instantiation:
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
public class EnumAttributeConverterWithConstructor<T extends Enum<T>> implements AttributeConverter<T> {
private final EnumAttributeConverter<T> converter;
public CustomEnumAttributeConverter(final Class<T> enumClass) {
this.converter = EnumAttributeConverter.create(enumClass);
}
#Override
public AttributeValue transformFrom(T t) {
return this.converter.transformFrom(t);
}
#Override
public T transformTo(AttributeValue attributeValue) {
return this.converter.transformTo(attributeValue);
}
#Override
public EnhancedType<T> type() {
return this.converter.type();
}
#Override
public AttributeValueType attributeValueType() {
return this.converter.attributeValueType();
}
}
But that only gets us half-way there-- now we need to generate a version for each enum type we want to convert that subclasses our custom class:
public class ExportTaskStatusAttributeConverter extends EnumAttributeConverterWithConstructor<ExportTaskStatus> {
public ExportTaskStatusAttributeConverter() {
super(ExportTaskStatus.class);
}
}
#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)
public ExportTaskStatus getStatus() { return this.status; }
Or the Lombok-y way:
#Getter(onMethod_ = {#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)})
ExportTaskStatus status;
It's a pain. It's a pain that could be solved with a little bit of tweaking and a tiny bit of reflection in the AWS SDK, but it's where we're at right now.
I am thinking that your annotations might actually be the problem here. I would remove all annotations that mention a constructor, and instead, write out your own constructor(s). For both Task and TaskStatus.
The dynamodb-enhanced SDK does this out of the box.
When you declare a #DynamoDbBean the DefaultAttributeConverterProvider provides a long list of possible ways to convert attributes between java types, including an EnumAttributeConverter which is used if type.rawClass().isEnum() is true. So you don't need to worry about it.
If you ever wanted to extend the number of converters, you would need to add the converterProviders annotation parameter, and declare the default one (or omit it), as well as any other providers you want.
Example:
#DynamoDbBean(converterProviders = { DefaultAttributeConverterProvider.class, MyCustomAttributeConverterProvider.class });
Solution based on watkinsmatthewp Answer:
public class TaskStatusConverter implements AttributeConverter<TaskStatus> {
#Delegate
private final EnumAttributeConverter<TaskStatus> converter;
public TaskStatusConverter() {
converter = EnumAttributeConverter.create(TaskStatus.class);
}
}
Task status attribute looks like this:
#Getter(onMethod_ = {#DynamoDbConvertedBy(TaskStatusConverter.class)})
TaskStatus status;

Write generic static method in enum base class that can extract subclass instance to do a Jackson conversion

I have some enum types that look like this:
public static enum Thingie {
ABC("abc"), DEF("def");
private String messageValue;
#JsonValue
public String getMessageValue() { return messageValue; }
private Thingie(String messageValue) { this.messageValue = messageValue; }
}
This will allow Jackson to properly marshal and unmarshal between string values and the enum type.
There may be times when I'd like to directly convert a string value to the enum value. This would be like the internal "fromValue()" method, but not quite the same:
public static Thingie messageValueOf(String messageValue) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(messageValue, Thingie.class);
}
I would like to convert this into a generic method AND put it into a base class, along with the "messageValue" property and accessor. The constructor would change to just call "super(messageValue)". Obviously, if I could do that, I would move the "mapper" to class level.
At this point, I've only attempted to write this as a generic method in the single enum type. I can't even get that working. I can't figure out how to extract the class from the template parameter. I've seen this particular question before, and there have been some answers, but I couldn't quite get it to work, and I imagine trying to do this in the base class would add additional complexity.
Let's assume I understood your problem (correct me if I am wrong).
The constructor would change to just call "super(messageValue)"
An enum can not extend a class, so you can't do that. But you can create an interface/class which you will delegate to for such queries (very simplistic code):
interface Test {
ObjectMapper MAPPER = new ObjectMapper();
static <T extends Enum<T>> T getIt(String s, Class<T> clazz) {
return MAPPER.convertValue(s, clazz);
}
}
public static void main(String[] args) {
Thingie abc = Test.getIt("abc", Thingie.class);
System.out.println(abc.ordinal());
}

Programmatically accessing #JsonProperty from Java

I have the following POJO using Immutables+Jackson under the hood:
#JsonInclude(JsonInclude.Include.NON_NULL)
abstract class AbstractQueryRequest {
#JsonProperty("reqid")
public abstract String reqid();
#JsonProperty("rawquery")
public abstract String rawquery();
}
At some point I need to build another object based on the fields of the POJO, something along this line:
final HttpUrl.Builder urlBuilder = HttpUrl.parse(cfg.baseUrl()).newBuilder();
urlBuilder.addQueryParameter("reqid", request.reqid())
.addQueryParameter("rawquery", request.rawquery());
It's quite annoying to keep the POJO and this call aligned upon changes, I was wondering if it was possible to access programmatically each JsonProperty instead of typing the string manually.
Note that it is fine to write the getters by hand as I can easily refactor and I have the compiler double checking, but for strings I am worried for people down the line and I would like to "read" them from the POJO class somehow.
You can do it via reflection. You need to take method annotation values which annotated with JsonProperty. But I recommend you to use JsonProperty on fields, not methods.
Here is an example for your current requirement :
public class Main {
public static void main(String[] args) {
AbstractQueryRequest someType = new SomeType();
for(Method method : x.getClass().getSuperclass().getDeclaredMethods()) {
if (method.isAnnotationPresent(JsonProperty.class)) {
JsonProperty annotation = method.getAnnotation(JsonProperty.class);
System.out.println(annotation.value());
}
}
}
}
class SomeType extends AbstractQueryRequest {
#Override
public String reqid() {
return null;
}
#Override
public String rawquery() {
return null;
}
}
Output is :
rawquery
reqid

*Inject" interface method implementation during compilation

Consider the following:
public interface MyFilter {
#FilterAttribute
String getAttribute();
}
#Retention(RUNTIME)
#Target(METHOD)
#Implementor(AutoEnumFilterAttribute.class)
public #interface FilterAttribute{
}
public enum MyEnumType2Filter implements MyFilter{
TYPE0,
TYPE1;
}
public enum MyEnumType2Filter implements MyFilter{
TYPE20,
TYPE21;
}
and much more ...
the MyFilter.getAttribute() implementation will look something like this in all cases.
...
public String getAttribute() {
return "attribute."+name().toLowerCase()+".filter";
}
...
Which as we can see is unnecessary repetition implementation in every enum that implements this interface.
Of course i can have a separate helper class which i do something like this
public final class MyFilterHelp {
public static String getAttribute(MyFilter filter) {
//check that it is an enum then,
return "attribute."+filter.name().toLowerCase()+".filter";
}
}
or
public enum MyEnum implements MyFilter {
TYPE20;
private String attribute;
private MyEnum () {
this.attribute = "attribute."+name().toLowerCase()+".filter";
}
public Strign getAttribute() {
return this.attribute;
}
}
But this first options separates the attributes from the enum, and the section option still requires implementation for every enum that implements this interface.
My vision is to have.
public class AutoEnumFilterAttribute<MyFilter> {
public String getAttribute(MyFilter filter) {
//check that it is an enum then,
return "attribute."+filter.name().toLowerCase()+".filter";
}
}
And use my enum filters normally as (the AutoEnumFilterAttribute acting somewhat like a proxy):
MyFilter filter = ...
String attribute = filter.getAttribute();
I surmise i can do this by kind of "injecting" the implementation of the MyFilter.getAttribute() in every enum that implements this interface during compile time.
Any idea on how to go about this?
Considering jdk7 or below.
As answered, one can do this using java8 default method
If you're working with Java 8, you can go for a default method in your interface. If you combine that with specifying the enum method name(), then you're pretty close to what you seem to want.
interface MyFilter {
default String getAttribute() {
return "attribute."+name().toLowerCase()+".filter";
} // you no longer have to implement this in your enums
String name(); // you need to specify this method here in order to use it in the default method
}
Since every enum already implements name(), there is no penalty for your existing classes (i.e. no refactoring) and you get the API method you need in a concise way.
If you're working with an older version of Java, then your solution will be rather complicated. You may consider writing an Annotation Preprocessor. I myself haven't used them, but I've seen a demo at one time and have read a bit about it. Please remember that I might not get everything right.
A very short summary is that you can mark classes with a custom-made annotation, and let an Annotation Preprocessor pick these classes up and auto-generate some code for them. The best part is that your compiler will know this at compile time, because you can configure the preprocessor to run before the compiler. As a result, the compiler will not balk at any methods you added at 'pre-compile' time.
So my idea would be to create something like this:
interface MyFilter {
String getAttribute();
}
#AddGetAttribute
enum MyEnumType2Filter implements MyFilter {
TYPE20, TYPE21
}
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.CLASS)
#interface AddGetAttribute {}
#SupportedAnnotationTypes("com.blagae.AddGetAttribute")
#SupportedSourceVersion(SourceVersion.RELEASE_6)
public class AttributeProcessor extends AbstractProcessor {
public AttributeProcessor() {
super();
}
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
/* this method should let you add the source code for your enums
see links below for some basic examples
you will need to try and add a method to your existing class,
instead of creating a new class file as these links do.
*/
return false;
}
}
The links I promised:
using Velocity

Jackson serialization: how to ignore superclass properties

I want to serialize a POJO class which is not under my control, but want to avoid serializing any of the properties which are coming from the superclass, and not from the final class. Example:
public class MyGeneratedRecord extends org.jooq.impl.UpdatableRecordImpl<...>,
example.generated.tables.interfaces.IMyGenerated {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
...
}
You can guess from the example that that this class is generated by JOOQ, and inherits from a complex base class UpdatableRecordImpl which also has some bean property-like methods, which cause problems during the serialization. Also, I have several similar classes, so it would be good to avoid duplicating the same solution for all of my generated POJOs.
I have found the following possible solutions so far:
ignore the specific fields coming from superclass using mixin technique like this: How can I tell jackson to ignore a property for which I don't have control over the source code?
The problem with this is that if the base class changes (e.g., a new getAnything() method appears in it), it can break my implementation.
implement a custom serializer and handle the issue there. This seems a bit overkill to me.
as incidentally I have an interface which describes exactly the properties I want to serialize, maybe I can mixin a #JsonSerialize(as=IMyGenerated.class) annotation...? Can I use this for my purpose?
But, from pure design point of view, the best would be to be able to tell jackson that I want to serialize only the final class' properties, and ignore all the inherited ones. Is there a way to do that?
Thanks in advance.
You can register a custom Jackson annotation intropector which would ignore all the properties that come from the certain super type. Here is an example:
public class JacksonIgnoreInherited {
public static class Base {
public final String field1;
public Base(final String field1) {
this.field1 = field1;
}
}
public static class Bean extends Base {
public final String field2;
public Bean(final String field1, final String field2) {
super(field1);
this.field2 = field2;
}
}
private static class IgnoreInheritedIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return m.getDeclaringClass() == Base.class || super.hasIgnoreMarker(m);
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new IgnoreInheritedIntrospector());
final Bean bean = new Bean("a", "b");
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(bean));
}
}
Output:
{
"field2" : "b"
}
You can override the superclass' methods which you'd like to prevent from being output and annotate them with #JsonIgnore. The override shifts the control of property creation to the subclass while enabling its ability to filter it from the output.
For instance:
public class SomeClass {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
#Override
#JsonIgnore
public String superClassField1(...){
return super.superClassField1();
};
#Override
#JsonIgnore
public String superClassField2(...){
return super.superClassField2();
};
...
}
You can use this as well instead of unnecessary overrides
#JsonIgnoreProperties({ "aFieldFromSuperClass"})
public class Child extends Base {
private String id;
private String name;
private String category;
}
The good use of inheritance is that the child classes extend or add functionality. So the usual way is to serialize the data.
A workarround would be to use a Value Object (VO) or Data Transfer Object (DTO) with the fields you need to serialize. Steps:
Create a VO class with the fields that should be serialized.
Use BeanUtils.copyProperties(target VO, source data) to copy the properties
Serialize the VO instance.
Add the following annotation in your Base Class :
#JsonInclude(Include.NON_NULL)

Categories