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!
Related
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.
Troubling to specify dynamic env variable in the model class #Hashkey Redis annotation.
Model:
#RedisHash("${spring.redis.namespace}:Book")
public class Book {
#Id
private String id;
private String name;
}
My application.properties file:
spring.redis.namespace=local
The resulting key is "${spring.redis.namespace}:Book" instead of local:Book
Could anyone help me with this?
Please use Keyspaces to do it. There two ways. I use one way to finish your requirement.
#Configuration
#EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {
#Value("${spring.redis.namespace}:Book")
String myKey;
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
#Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Book.class, myKey));
}
}
}
I have a POJO Deatils having following fields.
1. String Name
2. String Add
3. String Phone
.
For profile ‘x’ first two fields should marshal in XML and for other profile first and last field should marshal in XML.
Note: no field will be null for any profile.
I feel like it would be better to leave the Marshaller alone. It may be tricky to extend and customize the marshaller to fit your needs per Spring profile.
If our goal is to serialize different payloads depending on Spring profile, I'd recommend maybe a converter/factory that takes in the POJO (that has all the fields) and spits out a Data Transfer Object (DTO) for the specific profile.
Perhaps the following could work unless there is another end-goal in mind.
public class Details { ... }
// essentially a marker interface
public interface MyDto {
}
public class DtoA implements MyDto {
private String name;
private String add;
}
public class DtoB implements MyDto {
private String name;
private String phone;
}
public interface DtoConverter {
MyDto convert(Details details);
}
public class DtoConverterA implements DtoConverter { ... }
public class DtoConverterB implements DtoConverter { ... }
#Configuration
public class MyConfiguration {
#Bean
#Profile("a")
public DtoConverter dtoConverterA() {
return new DtoConverterA();
}
#Bean
#Profile("b")
public DtoConverter dtoConverterB() {
return new DtoConverterB();
}
}
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.