Thymeleaf 3 map access with dot notation - java

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

Related

When and how cglib-proxied component instance is created

I'd like to learn if there are some rules / conditions that a Spring component is wrapped (proxied) by CGLIB. For example, take this case:
#Component
public class TestComponent {
}
#Service
//#Transactional(rollbackFor = Throwable.class)
public class ProcessComponent {
#Autowired
private TestComponent testComponent;
public void doSomething(int key) {
// try to debug "testComponent" instance here ...
}
}
If we let it like this and debug the testComponent field inside the method, then we'll see that it's not wrapped by CGLIB.
Now if we uncomment the #Transactional annotation and debug, we'll find that the instance is wrapped: it's of type ProcessComponent$$EnhancerByCGLIB$$14456 or something like that. It's clearly because Spring needs to create a proxy class to handle the transaction support.
But I'm wondering, is there any way that we can detect how and when does this wrapping happen ? For example, some specific locations in Spring's source code to debug into to find more information; or some documentations on the rules of how they decide to create a proxy.
For your information, I need to know about this because I'm facing a situation where some component (not #Transactional, above example is just for demonstrating purpose) in my application suddenly becomes proxied (I found a revision a bit in the past where it is not). The most important issue is that this'll affect such components that also contain public final methods and another issue (also of importance) is that there must have been some unexpected changes in the design / structure of classes. For these kind of issues, of course we must try to find out what happened / who did the change that led to this etc...
One note is that we have just upgraded our application from Spring Boot 2.1.0RELEASE to 2.1.10RELEASE. And checking the code revision by revision up till now is not feasible, because there have been quite a lot of commits.
Any kind of help would be appreciated, thanks in advance.
You could debug into org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(Class, String, TargetSource).
If any advisor is found, the bean will be proxied.
If you use a #Lookup method injection it will also proxy the component class.

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.

Hibernate: Trouble using sessionFactory.getTypeHelper().custom(userType)

Hibernate.custom(userType) is gone in Hibernate 5.2.10.Final so I have to use sessionFactory.getTypeHelper().custom(userType). Is there any way to get TypeHelper without sessionFactory? Previously I was using hibernate 3.6.10.Final. I would rather not use sessionFactory but I can't really find a way around it.
The main goal is to take a org.hibernate.usertype.UserType and return a org.hibernate.type.Type.
I have this function in 3.6.10.Final
public Type getHibernateType() {
// Type is class that implements hibernates UserType
return Hibernate.custom(UserType)
}
in 5.2.10.Final I had to change it to something like
public Type getHibernateType() {
return sessionFactory.getTypeHelper().custom(UserType);
}
I don't really want to use sessionFactory if I can help it. So I was wondering if there was another way to get the Type.
Well, technically 5.2 still exposes the functionality via TypeFactory in a static method.
Type type = TypeFactory.custom( UserType.class, null, null );
However, be aware this method is marked #Deprecated and in fact, that entire class has been removed as a part of Hibernate 6.0's type system overhaul.
I would get used to the notion of using the SessionFactory to access this information because that is precisely how we have designed 6.0 to work at present.

Guava ImmutableBiMap becomes LinkedHashMap and cause Spring autowiring mistake

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.

Java Hibernate field access property or property based access

i am developing some improvements over a legacy system have some quite time
i have a class like this
class MyPersistentClazz
{
private String aTPlace;
public void setATPlace(.......){......}//yes mistyping
#Column(name="atPlaceOrder")
public String getATPlace(){return aTPlace;}
}
they usually load this class using this methods
final MyPersistentClazz clazz = (MyPersistentClazz)session.createCriteria(MyPersistentClazz.class).add(idEq(id)).uniqueResult();
and using load and get methods and works OK.
but the problem arise when i use projections.
final Projection p=Projections.projectionList().add(Projections.property("d.aTPlace"),"aTPlace");
throws
Exception in thread "main" org.hibernate.QueryException: could not resolve property:
my question is...
when using projections i think Hibernate is calling the setter of each property is this assertion OK?
when using criteria.uniqueResult or load or get Hibernate use individual field property access?
or why works with some and not work with others with the same setter?
we are using only annotations not XML.
thanks a lot.
How Hibernate works with your bean depends on how you annotated it. If you annotate instance variables then Hibernate will use direct injection and bypass your Set methods. Otherwise, it will use your Set methods.
Could it be that it is incorrectly converting your property name to a Set method name? Try changing the property name to something simpler (without that series of capital letters), and ensure that the case of the property in your projection is correct.

Categories