Lombok misses field's annotation while auto generating constructor. Is there a way to retain field's annotation in constructor input params?
Class to generate constructor,
#RequiredArgsConstructor(onConstructor = #__(#Inject))
public class Test {
#Named("MyField")
private final String field;
#Named("MyHandler")
private final SomeHandler handler;
}
Generated class :
public class Test {
#Named("MyField")
private final String field;
#Named("MyField")
private final SomeHandler handler;
#Inject
public Test(final String field, final SomeHandler handler) {
this.field = field;
this.handler = handler;
}
}
Desired class :
public class Test {
#Named("MyField")
private final String field;
#Named("MyHandler")
private final SomeHandler handler;
#Inject
public Test(#Named("MyField")final String field,
#Named("MyHandler")final SomeHandler handler) {
this.field = field;
this.handler = handler;
}
}
In version v1.18.4 Lombok added support for copying specific annotations. Meaning, that if you put following setting to lombok.config:
lombok.copyableAnnotations += com.google.inject.name.Named
and apply following Lombok annotations to your class:
#RequiredArgsConstructor(onConstructor = #__(#Inject))
public class Hello {
#NonNull #Named("my-name") String name;
}
the #Named annotation should be copied to your generated constructor argument.
Limitations: this does not work when annotation can't be put on a field or annotation on a field overrides constructor initialization
There's no such feature and it looks like nobody cares. I proposed it once and started to implement it, but gave up (no demand and too much work).
It could look like
#RequiredArgsConstructor(onConstructor=#__(#Inject))
public class Something {
#OnConstructor(#Named("userName"))
private final String userName;
#OnConstructor(#Named("userPassword"))
private final String userPassword;
private final int anotherField;
private final int yetAnotherField;
}
or maybe just
#RequiredArgsConstructor(
onConstructor=#__(#Inject),
moveToConstructorArg=#__(#Named))
public class Something {
#Named("userName")
private final String userName;
#Named("userPassword")
private final String userPassword;
private final int anotherField;
private final int yetAnotherField;
}
or it could be controlled using lombok.config as you probably want all #Named annotations to be moved to the constructor.
I'm afraid, if you want it, then you have to do it yourself (my incomplete implementation might help you a bit).
FTR: There's a feature request now.
Related
I have below repository model class
class Model {
#Column(name="id")
private static Integer id;
#Column(name="column-to-be-converted")
#Convert(converter=Converter.class)
private static String columnToBeConverted;
#Column(name="apply-converter")
private static boolean applyConverter;
// Getters and Setters
}
Below is the Converter class
#Component
#Converter
public class PasswordConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String rawData) {
// logic goes here
}
#Override
public String convertToEntityAttribute(String convertedData) {
// logic goes here
}
}
I want to apply #Convert annotation to the field columnToBeConverted only if the field applyConverter is set to true
I tried investigating if the model object can be passed to Converter Class as argument or with using #Conditional
Please suggest how can this be achieved
Thank you!
I have a POJO used by an existing endpoint, this endpoint responds with all fields present in the POJO.
But I'm creating a new enpoint which should respond only some of the fields of the same POJO.
I want to avoid copying the same POJO file and deleting the parameters I don't need, is there a way to do this?
This is the POJO:
public class AgentChatStatus {
private UUID activeChat;
private List<AgentChat> chatRequests; //Object with less params on new endpoint
private List<AgentChat> chatsOnHold; //Object with less params on new endpoint
private Collection<Agent> agents;
private int totalChatRequests;
private int totalChatsOnHold;
private Preferences.AgentConsoleConfig config;
// ...
}
public class AgentChat implements Payload {
private UUID id;
private String queueId;
Lets say I only need to show "Id" in endpoint 2 but id and queueId in endpoint1.
I work with spring btw.
Thanks!
With Jackson you can take advantage of JSON Views. The first thing is to create the Views as follows:
public class Views {
public static class ViewEndpoint2 { // The name can be whatever you want
}
public static class ViewEndpoint1 extends ViewEndpoint2 {
}
}
Then you need to annotate the properties in your POJO with #JsonView so that you tell Jackson in which views such properties should be visible:
public class AgentChatStatus {
#JsonView(Views.ViewEndpoint2.class)
private UUID activeChat;
#JsonView(Views.ViewEndpoint2.class)
private List<AgentChat> chatRequests;
#JsonView(Views.ViewEndpoint2.class)
private List<AgentChat> chatsOnHold;
#JsonView(Views.ViewEndpoint2.class)
private Collection<Agent> agents;
#JsonView(Views.ViewEndpoint2.class)
private int totalChatRequests;
#JsonView(Views.ViewEndpoint2.class)
private int totalChatsOnHold;
#JsonView(Views.ViewEndpoint2.class)
private Preferences.AgentConsoleConfig config;
}
public class AgentChat implements Payload {
#JsonView(Views.ViewEndpoint2.class)
private UUID id;
#JsonView(Views.ViewEndpoint1.class)
private String queueId;
}
Finally, you need to annotate the endpoints with the corresponding view:
#JsonView(Views.ViewEndpoint1.class)
#RequestMapping("your-old-enpoint")
public void yourOldEndpoint() {
(...)
}
#JsonView(Views.ViewEndpoint2.class)
#RequestMapping("your-new-enpoint")
public void yourNewEndpoint() {
(...)
}
#JsonView(Views.ViewEndpoint1.class) basically means all properties in AgentChatStatus and AgentChat and #JsonView(Views.ViewEndpoint2.class) means only some of them (the ones annotated with #JsonView(Views.ViewEndpoint2.class)).
You can read more about this at https://www.baeldung.com/jackson-json-view-annotation.
I have a class that contain injections and mandatory(final) fields. For common I can use MicronautBeanFactory.getBean(type) OR BeanContext.getBean(type) to get bean from context, but in this situation I must pass type and args.
I've created simple test for this
#MicronautTest
public class ETLExecutorTest {
#Inject
private MicronautBeanFactory micronautBeanFactory;
#Test
void testGetBean() {
Object[] args = new Object[] {"name", "spec", 1L};
ObjectInstance instance = micronautBeanFactory.getBean(ObjectInstance.class, args);
}
}
Object(bean) code
#Prototype
public class ObjectInstance {
#Inject
private ObjectStorage objectStorage;
private final String name;
private final String spec;
private final Long id;
public ObjectInstance(String name, String spec, Long id) {
this.name = name;
this.spec = spec;
this.id = id;
}
}
When I run it I receive exception
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [name] of class: com.ObjectInstance
Message: Multiple possible bean candidates found: [java.lang.String, java.lang.String, java.lang.String]
Path Taken: new ObjectInstance([String name],String specName,Long accountId)
at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1016)
at com.$TableInstanceDefinition.build(Unknown Source)
at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1598)
at io.micronaut.context.DefaultBeanContext.getScopedBeanForDefinition(DefaultBeanContext.java:2076)
at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1991)
at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:610)
at io.micronaut.spring.context.factory.MicronautBeanFactory.getBean(MicronautBeanFactory.java:264)
Caused by: io.micronaut.context.exceptions.NonUniqueBeanException: Multiple possible bean candidates found: [java.lang.String, java.lang.String, java.lang.String]
at io.micronaut.context.DefaultBeanContext.findConcreteCandidate(DefaultBeanContext.java:1701)
at io.micronaut.context.DefaultApplicationContext.findConcreteCandidate(DefaultApplicationContext.java:395)
at io.micronaut.context.DefaultBeanContext.lastChanceResolve(DefaultBeanContext.java:2289)
at io.micronaut.context.DefaultBeanContext.findConcreteCandidateNoCache(DefaultBeanContext.java:2212)
at io.micronaut.context.DefaultBeanContext.lambda$findConcreteCandidate$57(DefaultBeanContext.java:2155)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.lambda$compute$0(ConcurrentLinkedHashMap.java:721)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.compute(ConcurrentLinkedHashMap.java:733)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.computeIfAbsent(ConcurrentLinkedHashMap.java:710)
at io.micronaut.context.DefaultBeanContext.findConcreteCandidate(DefaultBeanContext.java:2154)
at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1943)
at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1082)
at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1007)
Also I tried to do another test, but in this case I receive object without injected fields
#MicronautTest
public class ETLExecutorTest {
#Inject
private BeanContext beanContext;
#Test
void testGetBean() {
Object[] args = new Object[] {"name", "spec", 1L};
BeanDefinition<ObjectInstance> definition = beanContext.getBeanDefinition(ObjectInstance.class);
ObjectInstance instance = definition.getConstructor().invoke(args); // there are no injections here: ObjectStorage of instance = null.
}
}
Could you tell me, please, what I do wrong ???
micronaut trying to create bean ObjectInstance through the constructor but can't find String name to inject, looks like it’s just a simple field for the ObjectInstance and in this case, it works as expected:
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [name]
if you add a default constructor, then the ObjectInstance will be created and you can get bean via beanContext.getBean(ObjectInstance.class):
#Prototype
public class ObjectInstance {
#Inject
private ObjectStorage objectStorage;
private String name;
private String spec;
private Long id;
public ObjectInstance() {}
public ObjectInstance(String name, String spec, Long id) {
this.name = name;
this.spec = spec;
this.id = id;
}
}
Also pay attention to MicronautBeanFactory implements ListableBeanFactory, this is for integration with Spring
P.S. I would recommend you change your code structure, POJO should not contain beans
Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.
I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}
This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder
Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail
I want to store a property into the database as a Long, but use the object with helper methods in the code.
However the object type is a custom type I have that has an internal value (a long) that I want to store to the database.
public final class MyBean extends Number implements Serializable, Comparable<MyBean>
{
private long myValue;
public MyBean(Long value) { this.myValue = value; }
// Other helper methods and overrides
public MyBean valueOf(Long data)
{
return new MyBean(data);
}
#Override
public String toString()
{
return String.valueOf(myValue);
}
}
This is how I am using it:
#Entity
#Table(name = "mybeans")
public class MyBean implements Serializable
{
private static final long serialVersionUID = 1L;
MyBean myBean;
#Id
#Column(name = "mybean", nullable = false)
public MyBean getMyBean() { return myBean; }
public void setMyBean(MyBean value) { this.myBean = value; }
}
Deserializing this object calls toString and works fine (jax-rs/jersey). But when I try to pull it out of the database using my EJB, the error I get is:
The object [1,427,148,028,955], of class [class java.lang.Long], could
not be converted to [class com.MyBean]
Saving it produced the error
Can't infer the SQL type to use for an instance of com.MyBean. Use
setObject() with an explicit Types value to specify the type to use.
Which makes sense.
But what methods can I add in to male the EJB get the long as the value and use the long to set up a new object?
ANSWER:
Making the class #Embeddable and adding the following attributes worked.
#Embedded
#AttributeOverrides({
#AttributeOverride(name="value", column=#Column(name="mybean"))
})
(I didn't add EmbeddedId because I added a serial primary key id and just made this a column)
The one caveat is that it won't work with dynamic weaving. I had to add
<property name="eclipselink.weaving" value="static"/>
to my persistence.xml
You can try making MyBean an Embeddable to use that as an EmbeddedId, something like this:
#Embeddable
public final class MyBean extends Number implements Serializable, Comparable<MyBean> {
private Long myValue;
public MyBean(Long myValue) {
this.myValue = myValue;
}
// Other helper methods and overrides
public MyBean valueOf(Long data) {
return new MyBean(data);
}
#Override
public String toString() {
return String.valueOf(myValue);
}
}
In your entity, MyBean will be an EmbeddedId and will look like this:
#Entity
#Table(name = "mybeans")
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
private MyBean myBean;
#EmbeddedId
#AttributeOverride(name="myValue", #Column(name="mybean_id"))
public MyBean getMyBean() {
return myBean;
}
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}
Adjust MyBean as you need, such as making Transient some attributes.