Spring Boot merge configuration properties - java

I've a use case where I need to bind configuration properties based on two prefixes, one of which is determined at runtime. Let's say the constant prefix is foo and the runtime prefix is bar.
Given a new instance of Java Bean class FooBar, the code should bind all environment variables FOO_, then overwrite with all environment variables BAR_.
There's a way to dynamically bind a prefix to a class, as I had stated in this ticket (sample code shown below). However, what's missing is the merging of the results.
var bindable = Bindable.of(FooBar.class);
var properties = ConfigurationPropertySources.get(env);
new Binder(properties)
.bind("prefix", bindable)
.orElse(new FooBar());
Example:
public class FooBar {
private Duration latency = Duration.ofMillis(500L);
// other properties
// getters, setters
}
If there are no environment variables FOO_LATENCY or BAR_LATENCY, FooBar.getLatency() is 500 ms. If only one of FOO_LATENCY and BAR_LATENCY is present, FooBar.getLatency() takes its value. If both FOO_LATENCY and BAR_LATENCY are present, FooBar.getLatency() takes the value of BAR_LATENCY.
Any idea how can this be done?

UPDATED
Just call bind again. It only assigns values that are found in the configuration properties, and last prefix bound will win, on a property-by-property basis.
Example
class FooBar {
private String a;
private String b = "B";
private String c;
private String d;
private String e = "E";
// Getter, setters, and toString here
}
Properties (YAML)
x.a: Hello
x.b: World
z.a: Goodbye
z.c: Test
Test
Binder binder = Binder.get(env);
FooBar fooBar = new FooBar();
System.out.println(fooBar);
fooBar = binder.bind("x", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);
fooBar = binder.bind("y", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);
fooBar = binder.bind("z", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);
Output
FooBar[a=null, b=B, c=null, d=null, e=E]
FooBar[a=Hello, b=World, c=null, d=null, e=E]
FooBar[a=Hello, b=World, c=null, d=null, e=E]
FooBar[a=Goodbye, b=World, c=Test, d=null, e=E]
As you can see, the third binding overrides the values from the first, but only for properties that are actually configured, which is why the second binding does nothing.
I also simplified the logic to skip the use of ConfigurationPropertySources.get().

Related

ModelMapper DTO-->Entity. How to skip unconditionally all fields not mapped

I have two classes (entity and DTO)
public class Deliver {
private Long id;
private String uri;
private Instant moment;
private DeliverStatus status; // enum PENDING,ACCEPTED,REJECTED
private String feedback; // feedback about received task
private Integer correctCount; // nr of correct questions
private Enrollment enrollment;
private Lesson lesson;
// constructors, getters and setters..
public class DeliverRevisionDto {
private DeliverStatus status;
private String feedback;
private Integer correctCount;
// constructors, getters and setters..
The goal is pretty simple, update the entity fields conveyed by Dto class I have the following code at Service layer (Spring Boot version 2.4.4):
#Service
public class DeliverService {
#Autowired
private DeliverRepository deliverRepository;
#Autowired
private ModelMapper modelMapper;
#Transactional
public void saveRevision(Long id, DeliverRevisionDto dto) {
Deliver deliver = deliverRepository.getOne(id);
System.out.println("BEFORE MAPPING: " + deliver.toString()); // # debug purpose
deliver = modelMapper.map(dto, Deliver.class);
// # debug purpose
TypeMap<DeliverRevisionDto, Deliver> tm = modelMapper.getTypeMap(DeliverRevisionDto.class, Deliver.class);
List<Mapping> list = tm.getMappings();
for (Mapping m : list)
{
System.out.println(m);
}
System.out.println("AFTER MAPPING: " + deliver.toString()); // # debug purpose
deliverRepository.save(deliver);
}
}
The console output is:
BEFORE MAPPING: Deliver [id=1, uri=``https://github/someone.com``, moment=2020-12-10T10:00:00Z, status=PENDING, feedback=null, correctCount=null, enrollment=com.devsuperior.dslearnbds.entities.Enrollment#7e0, lesson=com.devsuperior.dslearnbds.entities.Task#23]`
`PropertyMapping[DeliverRevisionDto.correctCount -> Deliver.correctCount]`
`PropertyMapping[DeliverRevisionDto.feedback -> Deliver.feedback]`
`PropertyMapping[DeliverRevisionDto.status -> Deliver.status]`
`AFTER MAPPING: Deliver [id=null, uri=null, moment=null, status=ACCEPTED, feedback=Muito bem cabra, tarefa aceita., correctCount=5, enrollment=null, lesson=null]
The mapping of the 3 fields in DTO is done correctly, BUT all the other fields of my entity are set to null. I know that I can skip fields according http://modelmapper.org/user-manual/property-mapping/
The problem is that I don´t want to couple the code with specific field names/getters/setters, that´s the reason I´m using ModelMapper. I wonder if there is any configuration that, upon mapping the modelmapper object says "Hey, the TARGET class have way more fields than the SOURCE class, I will left them untouched unconditionally (meaning I don´t need to say what fields are).
I'm trying to map fields between 2 classes with different set of fields (some are the same), and when I map the class with smaller set of fields to the one with bigger set of fields, the mapper set fields that don´t match with "null", I want these fields untouched (with original values) without I telling which one they are, after all, the mapper knows which ones match.
ModelMapper documentation is not the best part of that framework. Let us see what happens in your code.
Here you fetch the entity to be updated from the repo:
Deliver deliver = deliverRepository.getOne(id);
and log it having all the fields as should be. However this line:
deliver = modelMapper.map(dto, Deliver.class);
does a re-assignment to your variable deliver. This method creates a new instance of Deliver class and assigns it to variable deliver so discarding the entity fetched from repo.
This new instance will have all the fields that are not existing or not set in DTO null.
This is the API doc that my IDE provides, fotr these two different methods:
String org.modelmapper.ModelMapper.map(Object source, Class destinationType)
Maps source to an instance of destinationType. Mapping is performed according to the corresponding TypeMap. If no TypeMap exists for source.getClass() and destinationType then one is created.
Versus
void org.modelmapper.ModelMapper.map(Object source, Object destination)
Maps source to destination. Mapping is performed according to the corresponding TypeMap. If no TypeMap exists for source.getClass() and destination.getClass() then one is created.
It might not be clearly stated that the first method actually creates a new instance based on the type (Class) passed but it should be clear that ModelMapper cannot alter some arbitrary variable just by knowing the type. You need to pass the variable to alter as method parameter.

How To use Model OOP in Java

I have a Model Active Admin, I have created String ID setter and getter.
When I use setID in Login Form, I use this :
ActiveAdmin AA = new ActiveAdmin();
AA.setId(txtIdAdmin.getText());
When I test getter from login form, it works. When I test in another form, in another Java class, in different file, I can't get my string ID in Active Admin. I used:
AA.getId();
And the result is blank.
Build ActiveAdmin as a Singleton class, such that you have one instance for the entire application. Otherwise you will build a new object ActiveAdmin everytime when you use new ActiveAdmin().
Check link Java Singleton
Muhamad, setters and getters for properties typically take this form, based on what I think you're saying:
public class ActiveAdmin
{
public string Id { get; set; }
}
From another class, you would say "aA = new ActiveAdmin();"
Then aA.Id = "2"; and string aAId = aA.Id;

Java Jackson receive key with null value [duplicate]

What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
Summarizing excellent answers by Programmer Bruce and StaxMan:
Missing properties referenced by the constructor are assigned a default value as defined by Java.
You can use setter methods to differentiate between properties that are implicitly or explicitly set. Setter methods are only invoked for properties with explicit values. Setter methods can keep track of whether a property was explicitly set using a boolean flag (e.g. isValueSet).
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
For questions such as this, I like to just write a sample program and see what happens.
Following is such a sample program.
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// {"name":"Fred","id":42}
String jsonInput1 = "{\"name\":\"Fred\",\"id\":42}";
Bar bar1 = mapper.readValue(jsonInput1, Bar.class);
System.out.println(bar1);
// output:
// Bar: name=Fred, id=42
// {"name":"James"}
String jsonInput2 = "{\"name\":\"James\"}";
Bar bar2 = mapper.readValue(jsonInput2, Bar.class);
System.out.println(bar2);
// output:
// Bar: name=James, id=0
// {"id":7}
String jsonInput3 = "{\"id\":7}";
Bar bar3 = mapper.readValue(jsonInput3, Bar.class);
System.out.println(bar3);
// output:
// Bar: name=null, id=7
}
}
class Bar
{
private String name = "BLANK";
private int id = -1;
Bar(#JsonProperty("name") String n, #JsonProperty("id") int i)
{
name = n;
id = i;
}
#Override
public String toString()
{
return String.format("Bar: name=%s, id=%d", name, id);
}
}
The result is that the constructor is passed the default value for the data type.
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
One simple approach would be to check for a default value post deserialization processing, since if the element were present in the JSON but had a null value, then the null value would be used to replace any default value given the corresponding Java field. For example:
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFooToo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
// {"name":null,"id":99}
String jsonInput1 = "{\"name\":null,\"id\":99}";
BarToo barToo1 = mapper.readValue(jsonInput1, BarToo.class);
System.out.println(barToo1);
// output:
// BarToo: name=null, id=99
// {"id":99}
String jsonInput2 = "{\"id\":99}";
BarToo barToo2 = mapper.readValue(jsonInput2, BarToo.class);
System.out.println(barToo2);
// output:
// BarToo: name=BLANK, id=99
// Interrogate barToo1 and barToo2 for
// the current value of the name field.
// If it's null, then it was null in the JSON.
// If it's BLANK, then it was missing in the JSON.
}
}
class BarToo
{
String name = "BLANK";
int id = -1;
#Override
public String toString()
{
return String.format("BarToo: name=%s, id=%d", name, id);
}
}
Another approach would be to implement a custom deserializer that checks for the required JSON elements. And yet another approach would be to log an enhancement request with the Jackson project at http://jira.codehaus.org/browse/JACKSON
In addition to constructor behavior explained in #Programmer_Bruce's answer, one way to differentiate between null value and missing value is to define a setter: setter is only called with explicit null value.
Custom setter can then set a private boolean flag ("isValueSet" or whatever) if you want to keep track of values set.
Setters have precedence over fields, in case both field and setter exist, so you can "override" behavior this way as well.
I'm thinking of using something in the style of an Option class, where a Nothing object would tell me if there is such a value or not. Has anyone done something like this with Jackson (in Java, not Scala, et al)?
(My answer might be useful to some people finding this thread via google, even if it doesn't answer OPs question)
If you are dealing with primitive types which are omittable, and you do not want to use a setter like described in the other answers (for example if you want your field to be final), you can use box objects:
public class Foo {
private final int number;
public Foo(#JsonProperty Integer number) {
if (number == null) {
this.number = 42; // some default value
} else {
this.number = number;
}
}
}
this doesn't work if the JSON actually contains null, but it can be sufficient if you know it will only contain primitives or be absent
another option is to validate the object after deserialization either manually or via frameworks such java bean validation or, if you are using spring, the spring validation support.

EMF Eclipse: enumeration with custom fields (properties)

Ok, so in Java this is possible:
import org.eclipse.emf.common.util.Enumerator;
public enum MyEnum implements Enumerator {
LITERAL1(0, "Name", "Literal", "custom1", "custom2", "custom3"),
LITERAL2(0, "Name", "Literal", "custom1", "custom2", "custom3"),
LITERAL3(0, "Name", "Literal", "custom1", "custom2", "custom3"),
LITERAL4(0, "Name", "Literal", "custom1", "custom2", "custom3");
public static final int LITERAL1_VALUE = 0;
public static final int LITERAL2_VALUE = 1;
public static final int LITERAL3_VALUE = 2;
public static final int LITERAL4_VALUE = 3;
private static final MyEnum[] VALUES_ARRAY =
new MyEnum[] {
LITERAL1,
LITERAL2,
LITERAL3,
LITERAL4,
};
public static final List<MyEnum> VALUES =
Collections.unmodifiableList(Arrays.asList(VALUES_ARRAY));
private final int value;
private final String name;
private final String literal;
private final String custom1;
private final String custom2;
private final String custom3;
private MyEnum(int value, String name, String literal,
String custom1, String custom2, String custom3) {
this.value = value;
this.name = name;
this.literal = literal;
this.custom1 = custom1;
this.custom2 = custom2;
this.custom3 = custom3;
}
/*Getters for all of them*/
This is what's called an extended enum. I know it works - I tried and used it lots before. I know there could be discussion if this is what you should do with an enumeration - I think yes, as you still have your defined constants, but they just contain some more information (which is still sort of constant). (Also: I looked at this one, Custom fields on java enum not getting serialized, and I think they also follow my thinking in how to generate custom properties on enums).
Now, how on earth am I supposed to generate something like this from an Eclipse EMF model? I don't even know where to add extra properties to my enums in the .ecore model editor... I tried adding the extra properties as an annotation to ExtendedMetaData, which contains keys for all the custom properties. However when generating a .genmodel file that doesn't change the file (I know as I'm holding it against an earlier checked-in version in SVN, and SVN tells me nothing's changed). Ofcourse that also makes that there's no change in the generated model code.
Anyone? I know I can change generated model code by hand, but in the event I might change something to the model I'd lose those edits, that's obviously not what I'd want.
Thanks!
Update: Just to be all clear, this is how my .ecore looks like in the model editor:
MyEnum (EEnum)
LITERAL1 (EEnum Literal)
ExtendedMetaData (EAnnotation)
custom1 -> custom1
custom2 -> custom2
custom3 -> custom3
LITERAL2 (EEnum Literal)
ExtendedMetaData (EAnnotation)
custom1 -> custom1
custom2 -> custom2
custom3 -> custom3
LITERAL3 (EEnum Literal)
ExtendedMetaData (EAnnotation)
custom1 -> custom1
custom2 -> custom2
custom3 -> custom3
LITERAL4 (EEnum Literal)
ExtendedMetaData (EAnnotation)
custom1 -> custom1
custom2 -> custom2
custom3 -> custom3
Anyone? I know I can change generated model code by hand, but in the event I might change something to the model I'd lose those edits, that's obviously not what I'd want.
In fact, you can add your extended enum the way you always do. When your genmodel generates code from your model it adds a tag #generate to know which pieces of code have been created by it. If you add a piece of code, it would not have this flag. Then, if you need to update your model and so your generated code, EMF just modifies the pieces of code that have the #generated tag. In this way it will respect your code insertion and you will not lose what you have done.
For more information, you can search on the Eclipse Modeling Framework book wrote by Budinsky and company. I quote what the book says (p. 25):
You are expected to edit the generated classes to add methods and instance variables. You can always regenerate from the model as needed and your addition will be preserved during the regeneration. [...] Any method that doesn't have this #generated tag (that is, anything you add by hand) will be left alone during regeneration. If you already have a method in a class that conflicts with a generated method, then your version will take precedence and the generated one will be discarded.

XDoclet - check annotation from other class

I'm using XDoclet to generate code:
/**
* #diff.special
*/
public String myString;
Now I'd like to generate code depending on this annotation AND an annotation in an other class, i.e.
if annotation in class 1 = diff.special
===> decide what to do according to annotation in class 2
if annotation in class 1 = diff.normal
===> decide what to do according to annotation in class 3
How can I achieve that in my handler? When my ant task runs and is working on annotation in class 1, how can I get the annotations of class 2 or 3?
Thanks a LOT,
Haemi
Don't know if it's the best way to do, but I solved it the following way:
final XJavaDoc xJavaDoc = XDocletTagSupport.getXJavaDoc();
final XClass xClass = xJavaDoc.getXClass(clazz.getCanonicalName());
// iterate over all fields in the angebot class and...
for (Object field : xClass.getFields(true)) {
final XField xfield = (XField) field;
// ... check if it contains the corresponding field, ...
if (xfield.getName().contains(fieldNameToLookFor)) {
return xfield;
}
}

Categories

Resources