Guava ImmutableBiMap becomes LinkedHashMap and cause Spring autowiring mistake - java

I have ImmutableBiMap filled with 2 simple Spring beans.
OS: Manjaro Linux
JDK version: 1.8.0.102 Oracle
Spring version: 4.3.4.RELEASE from
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Athens-SR1</version>
Creating context throws:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: argument type mismatch
Caused by: java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
As following screen show, when exception is throw by Spring's BeanUtil argument is a LinkedHashMap instead of BiMap.
Minimal, Complete, and Verifiable example:
#Component
#Slf4j
public class TestControl {
private final BiMap<String, Integer> automatons;
#Autowired
public TestControl(BiMap<String, Integer> automatons) {
this.automatons = automatons;
log.info("automatons={}", automatons.keySet());
}
}
#Configuration
public class TextContext {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(
TextContext.class,
TestControl.class
);
BiMap bean = context.getBean(BiMap.class);
}
#Bean
BiMap<String, Integer> automatons() {
return ImmutableBiMap.of(
"Cellular Automaton", cellularAutomaton(),
"Monte Carlo Automaton", monteCarloAutomaton());
}
#Bean
Integer cellularAutomaton() {
return 6;
}
#Bean
Integer monteCarloAutomaton() {
return 5;
}
}

This is a side effect of how Spring handles some container types.
Even typed Maps can be autowired as long as the expected key type is
String. The Map values will contain all beans of the expected type,
and the keys will contain the corresponding bean names: [...]
A BiMap is a Map.
Spring isn't trying to inject your automatons bean into the TestControl. Instead, it's trying to find all beans of type Integer as the values, collecting them into a Map (LinkedHashMap as implementation of choice), and associating them with their bean name as the key.
In this case, it fails because the constructor expects a BiMap.
One solution is to inject by name.
#Autowired()
public TestControl(#Qualifier(value = "automatons") BiMap<String, Integer> automatons) {
this.automatons = automatons;
}
By specifying a qualifier with a name, Spring will instead try to find a bean (with the appropriate type) that's named automatons.
If you're not too attached to the final instance field, you could also inject the field with #Resource
#Resource(name = "automatons") // if you don't specify the name element, Spring will try to use the field name
private BiMap<String, Integer> automatons;
For reasons, this will only work 4.3+.
For beans that are themselves defined as a collection/map or array
type, #Resource is a fine solution, referring to the specific
collection or array bean by unique name. That said, as of 4.3,
collection/map and array types can be matched through Spring’s
#Autowired type matching algorithm as well, as long as the element
type information is preserved in #Bean return type signatures or
collection inheritance hierarchies. In this case, qualifier values can
be used to select among same-typed collections, as outlined in the
previous paragraph.
I would be OK with the behavior you're seeing in pre-4.3, but this does seem like a bug for Map. (The correct behavior occurs for List and array types.)
I've opened SPR-15117 to track it, which has now been resolved (2 day turnover, wow!).

Unless there is a giant bug in Spring (which I doubt) this must be a human/editor error.
I have re-created a somewhat simpler example, same basics I have just used String, Integer, Long, and Boolean since I didn't have your types - this simple example it works.
LinkedHashMap is not a BiMap, so it would be a bug if it is chosen as an autowire candidate. It almost sounds like the source and compiled code is out-of-sync, have you tried to delete the build folder and rebuild?
If rebuilding does not help, the only way to solve this is good old fashioned debugging.
Put a breakpoint inside LinkedHashMaps constructor and see where it is constructed, does it have anything to do with your beans?
Set a conditional breakpoint (so you only stop if beanName.equals( "automatonTypeSettingsControl") in org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor, and step through the method so you can see how spring finds the autowire candidate;
Make the simplest standalone example which fails, put it on Github and post a link, then some one else may be able to help you debug.
Observation: I have read a lot of StackOverflow post during the last month, and it looks like the average developer is not very good a debugging thirdparty code. You can actually learn a lot from debugging other peoples code, especially the spring framework code, which I find quite easy to read, considering the problem it is solving.
Edit This turned out to be a limitation in Spring as described in another answer. That said I ended up reproducing the error and reading trough the Spring code to find the exact code for this behavior in about 1 hour. I feel that many developers overlook debugging as a software discipline. For me it is one of the most important disciplins, since you probably spend most of your time working with code you did not write yourself.

Related

Using Lombok's SuperBuilder with Hibernate Validator (jakarta.validation.x) annotation on a container type leads to "Type mismatch"

I have a class ActivitiesModel which uses Lombok's SuperBuilder.
import jakarta.validation.NotBlank;
// other imports and statements omitted for brevity.
#Data
#SuperBuilder
#NoArgsConstructor
public class ActivitiesModel {
public static final String ACTIVITIES_NOT_NULL_MESSAGE = "Activities cannot be null";
public static final String ACTIVITY_NOT_BLANK_MESSAGE = "Activity cannot be blank";
#NotNull(message = ACTIVITIES_NOT_NULL_MESSAGE)
private List<#NotBlank(message = ACTIVITY_NOT_BLANK_MESSAGE) String> activities;
}
I am using this builder to create an object of ActivitiesModel, and then validating it using Hibernate's Validator interface:
// Somewhere else in the application.
// Create an object using the builder method.
ActivitiesModel activitiesModel = ActivitiesModel.builder()
.activities(List.of("hello", "world")) // <----- Point A
.build();
// Validate the object using Hibernate's validator.
validator.validate(activitiesModel);
However, running this code gives me the following error:
java.lang.Error:
Unresolved compilation problem:
Type mismatch: cannot convert from List<String> to List<E>
The stack trace seems to be pointing at Point A.
I have tried the following approaches:
Replacing the #SuperBuilder with #Builder and #AllArgsConstructor.
Replacing the message attribute with a string literal instead of a static final variable, i.e:
private List<#NotBlank(message = "Activity cannot be blank") String> activities;
1st approach seems to fix this error, however, it's not something I can use as I need to extend the builder functionality to a subclass of ActivitiesModel. Also, this issue is also present in another abstract class, so the super builder functionality for parent classes is definitely required.
2nd approach also works in solving the error. However, going with it is a bit problematic because I then need to have the same message string in the validation test for this model class, which is something I would like to avoid as it duplicates the string.
Another thing to note is that this error only seems to occur in the presence of an annotation on the generic type parameter of the container, which is NotBlank in this case. It is not influenced by any annotations which are present directly on the field itself (NotNull in this case).
So, all in all, these are the questions that I would like to get some answers to:
Somehow, Lombok is able to figure out the types in case of a string literal but not in case of a static final String. Why is that?
Am I going about this totally wrong? The problem occurs because I'm trying to store the message string in a variable, and I'm trying to re-use the same variable at two places: the annotation's message attribute, and in the validation test for the model class. Should I not be checking for the presence of the message in my validation tests, but be checking for something else instead?
For anyone who comes across this later on, the research for this issue has led me to believe that comparing message strings in tests is not the way to go about writing validation test cases. Another downside to this approach is that you might have different validation messages for different locales. In that case, the message string itself might be a template e.g. my.message.key with its values in a ResourceBundle provided to Hibernate, i.e. files such as ValidationMessages.properties and ValidationMessages_de.properties.
In such a scenario, you could compare message for one locale in your validation test case, however, a better approach might be to check the annotation and the field for which the validation has failed. We can get both of these pieces of information via the ConstraintViolation and subsequently the ConstraintDescriptor types, provided by Hibernate. This way we can circumvent checking the message itself, but rely on the actual validation annotation which has failed.
As for the solution to this question, it seems it was a build cache issue. Cleaning maven's build cache results in this code working perfectly fine, but VSCode still seems to have an issue. For now, I will choose to ignore that.

Accessing CDI from simple objects?

Assume I have a configuration class accessible via the stock CDI that defines some application-wide parameters:
#ApplicationScoped
class AppConfig {
public double getMaxAllowedBrightness() { ... }
};
And I have a simple class for my data objects:
class LightSource {
double brightness;
...
boolean isValid() {
double maxAllowedBrightness = ...; // Somehow use AppConfig#getMaxAllowedBrightness() here
return brightness <= maxAllowedBrightness;
}
}
How can my data object access the single AppConfig instance?
Somehow I hate the idea of autowiring AppConfig into every single data object (there are lots of them). Is there any other way to get access to AppConfig in the above example from my data object?
What's the best pattern to use here?
The simplest example is a runtime lookup akin to:
import jakarta.enterprise.inject.spi.CDI;
CDI.current().select(cls).get();
With cls being the class that you're looking up. (Note the package name, this is the latest version of CDI 2.x in the new jakarta namespace, the original is in javax.)
It gets more detailed from there, but that's the gist of it.
Note, that semantically there's little difference between autowiring something and doing a runtime lookup, especially for something mostly static at the instance level. It's still a dependency. You still have to touch the code of the classes to pull it off.
A nice thing of relying on the autowiring is that you can disable it situationally, and the class reverts to a simple bean, that you can do with what you will. Coding in the lookup, it's a little bit more than that.
Dynamic lookup is more for special circumstances.
On my current project, our team has been doing this using the #Value annotation. In our case, we have all the properties in a properties bean, which I'll call mainAppConfiguration. The bean is populated from a properties file like main-app-config.properties (which was read into the bean with a Properties prop = new Properties().load(mainAppConfigFilePath) method.
Assuming you have something like that set up, then we inject the properties into the classes that need them using a little SpEL magic something like:
private Integer refreshRateSeconds;
#Value("#{ mainAppConfiguration.getProperties()['funny-property-base-name.refreshRateSeconds'] }")
public void setRefreshRateSeconds(Integer refreshRateSeconds) {
if (refreshRateSeconds == null) {
throw new IllegalArgumentException("Required config property 'funny-property-base-name.refreshRateSeconds' was not found"));
}
this.refreshRateSeconds = refreshRateSeconds;
}
Baeldung has examples (without defaults) and more with defaults.

How to use the NestedBeanPropertyDefinition<T, V> in Vaadin 12.0.7?

I'm currently trying to use a binder in Vaadin to access a nested property of an object. After googling a bit I found a reference example which sums up my problem quite well (Original post).:
Assume you have 2 beans:
public class Bean {
private SubBean sub;
// + getter setter
}
public class SubBean {
private String name;
// + getter setter
You think you should be able to do smthing like this:
Binder<Bean> binder = new Binder<>(Bean.class);
binder.bind(new TextField(), "sub.name");
How ever this results in an exception. Following the discussion of Vaadins repository this issue was closed by something called NestedPropertyDefinitions (Potential solution referenced in the issue discussion which lead to closing the issue).
I was looking it up but merely found any information how to use it or how to easily access nested properties with the Vaadin binding system except for this one Documentation.
Can anyone explain to me how to use NestedPropertyDefinitions ?
I found out that this:
binder.forField(new TextField()).bind("sub.name")
works in Vaadin 12.0.7. It does for grids and binders as well. Apparently there is no need to use NestedPropertyDefinitions to achieve nested bindings. I had a bug on my backend side which caused an error that made me assume the binding did not work properly. So I still can't tell if there is another way of achieving this or what NestedPropertyDefinitionsdo but I'd assume that they are used by Vaadin internally.
According to Cashbees comment NestedPropertyDefinitions is only used internally and how to deal with nested properties is indirecetly referenced in this documentation.

Thymeleaf 3 map access with dot notation

While updating thymeleaf from 2.x to 3.x in a spring 4.3.x environment, I am facing the problem that the class org.thymeleaf.context.VariablesMap was removed.
I am using the following model structure
public interface Model extends Map<String, Object>, Serializable {...}
public class BaseModel extends VariablesMap<String, Object> implements Model {...}
VariablesMap implemented an OGNL MapPropertyAccessor which allowed to access the model with the dot notation, regardless of whether the key alternativeLanguages exists or not
${meta.alternativeLanguages}
Now with thymeleaf 3 the spring integration only uses SpringEL and the SpringEL MapAccessor throws an exception when the key alternativeLanguages does not exists
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 28): Property or field 'alternativeLanguages' cannot be found on object of type 'd.v.BaseModel' - maybe not public?
Is there a way to avoid the migration of all templates to
${meta['alternativeLanguages']}
This is only a partial answer as I haven't tried it, but I'm guessing you'll have to implement your own org.springframework.expression.PropertyAccessor based on (possibly inheriting from) the org.springframework.context.expression.MapAccessor that isn't quite doing what you want. You'd want to pretty much always return true for the canRead method, and handle the read method to return whatever it is you want it to return in the case that the map doesn't have the value in it.
Then I assume you'd need to somehow hook into how Thymeleaf creates its SpEL context to register that custom PropertyAccessor, which I would expect to be possible but I didn't see where in a quick look through.
I hope that gets you pointed in the right direction, though.
You could use safe null navigation in this case:
${meta?.alternativeLanguages}
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions-operator-safe-navigation

Java annotations: pass value of annotation attribute to another annotation

I have interface Resource and several classes implementing it, for example Audio, Video... Further, I have created custom annotation MyAnnotation with Class type param:
#MyAnnotation(type = Audio.class)
class Audio {
...
}
#MyAnnotation(type = Video.class)
class Video{
...
}
In some other place in code I have to use Interface Resource as a returned type:
public class Operations<T extends Resource> {
....
#OtherAnnotation(type = Audio.class (if audio), type = Video.class (if video) )
T getResource();
....
}
The question is how to appropriatelly annotate annotation #OtherAnnotation depending of what kind of Resource type will be returned ?
What you are asking is for dynamic values for annotation attributes.
However annotations can only be set at compile time which is the reason why their values can only be compile time constants. You may only read them at runtime.
There was a similar question in which someone tried to generate the annotation value , it's answer explains why there is no way to dynamically generate a value used in annotation in a bit more detail. In that question there was an attempt to use a final class variable generated with a static method.
There are annotation processors which offer a bit more flexibility by handling placeholders. However i don't think this fits your case, as you want the dynamic values at runtime.
This answer refers to spring's use of the expression language for the Value annotation in which the placeholder (#Value("#{systemProperties.dbName})") gets overrided with the data from one of the property sources defined ( example in spring boot )
In any case, you will have to rethink your architecture a bit.

Categories